9.17. Socket terenkripsi dan TLS¶
Semua yang dibahas sejauh ini memindahkan byte secara terbuka. Setiap perangkat di jalur antara kamera dan server -- router rumah, penyedia layanan internet, titik akses berbahaya di kedai kopi -- pada prinsipnya dapat membaca atau memodifikasi apa yang melewatinya. Untuk sebagian besar lalu lintas internet, hal itu tidak dapat diterima. Solusi standarnya adalah membungkus koneksi dalam lapisan enkripsi: TLS, protokol Transport Layer Security. Ikon kunci "HTTPS" di browser adalah TLS yang berjalan di atas TCP, dan pembungkusan yang sama inilah yang membuat protokol internet lainnya menjadi "aman". Modul ssl pada kamera adalah yang membungkus socket dalam TLS.
9.17.1. Apa yang ditambahkan TLS, dan apa yang dikirimkan kamera¶
TLS berada di antara TCP dan aplikasi -- aplikasi menulis byte ke socket yang dibungkus TLS, TLS mengenkripsinya dan menyerahkan hasilnya ke TCP, dan proses tersebut dibalik di sisi lain. Dalam bentuk penuhnya, TLS memberikan tiga jaminan di atas TCP biasa:
Kerahasiaan. Penyadap di jalur tidak dapat membaca apa yang dipertukarkan oleh dua endpoint.
Integritas. Setiap modifikasi lalu lintas dalam transit terdeteksi; koneksi terputus daripada mengirimkan data yang telah dirusak.
Autentikasi. Server membuktikan bahwa ia adalah server yang disebutkan, bukan penipu (dan, secara opsional, klien juga membuktikan siapa dirinya).
Dua yang pertama berasal dari enkripsi itu sendiri. Yang ketiga membutuhkan sertifikat di setidaknya satu sisi, ditambah sesuatu yang dipercaya sebelumnya untuk memverifikasi sertifikat tersebut. Kamera OpenMV dikirimkan tanpa penyimpanan sertifikat bawaan sama sekali: kamera yang baru di-flash tidak mempercayai otoritas sertifikat mana pun, tidak memiliki sertifikat server sendiri, dan mode verifikasi default (ssl.CERT_NONE) tidak memeriksa sertifikat peer terhadap apa pun. Jadi secara bawaan, TLS pada kamera memberikan dua jaminan pertama -- enkripsi terhadap penyadapan dan perusakan oleh pengamat pasif -- tetapi tidak yang ketiga.
9.17.2. Mengenkripsi koneksi keluar¶
Penggunaan paling sederhana adalah membungkus koneksi TCP keluar. Alurnya adalah: buka socket TCP biasa, serahkan ke ssl.wrap_socket(), lalu baca dan tulis melalui socket yang dibungkus persis seperti yang Anda lakukan pada socket biasa:
import socket
import ssl
addr = socket.getaddrinfo("example.com", 443)[0][-1]
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.connect(addr)
s = ssl.wrap_socket(sock)
s.send(b"GET / HTTP/1.0\r\nHost: example.com\r\n\r\n")
print(s.recv(4096))
s.close()
Pembungkusan melakukan jabat tangan TLS; setelah itu setiap byte melalui s.send dienkripsi saat keluar dan setiap byte dari s.recv telah dienkripsi di jaringan. Tidak ada sertifikat yang dikonfigurasi, tidak ada jangkar kepercayaan yang disediakan -- TLS hanya menegosiasikan kunci sesi sementara dengan server mana pun yang menjawab dan menggunakannya.
Jabat tangan TLS yang dijalankan ssl.wrap_socket(). Ini berada di atas koneksi TCP yang sudah terbuka dari gambar sebelumnya; setelah kedua sisi mengirimkan Finished, sisa percakapan dienkripsi di kedua arah.¶
Peringatan
Ini adalah enkripsi saja, bukan TLS yang terautentikasi. Kamera berkomunikasi secara aman dengan apa pun yang menjawab di ujung lain koneksi TCP. Jika seorang man-in-the-middle mengarahkan ulang koneksi ke server yang ia kendalikan dan server tersebut menyajikan sertifikat apa pun, jabat tangan tetap berhasil dan kamera akhirnya berkomunikasi secara aman dengan penyerang. Gunakan mode ini hanya ketika man-in-the-middle bukan bagian dari model ancaman -- jaringan lokal tertutup, lingkungan pengembangan, kamera yang berbicara ke layanan yang berjalan pada perangkat keras yang sama -- bukan ketika menjangkau internet publik.
Untuk autentikasi nyata -- kamera memverifikasi server publik, kamera bertindak sebagai server TLS, atau TLS bersama -- Anda perlu membawa sertifikat ke perangkat. Cerita lengkapnya ada di Bekerja dengan sertifikat TLS.
Pembungkusan yang sama bekerja untuk lalu lintas TCP masuk, dengan memilih protokol server dan meneruskan server_side=True ke ssl.wrap_socket(). Peringatan di atas masih berlaku: tanpa sertifikat sendiri, kamera tidak dapat membuktikan siapa dirinya kepada klien, dan klien yang penasaran akan melihat kegagalan jabat tangan "tanpa sertifikat" pada sebagian besar tumpukan TLS. Alur kerja sertifikat sisi produksi adalah yang membuka pemblokiran menjalankan kamera sebagai server TLS dengan cara yang berguna.
9.17.3. Dengan asyncio¶
Bab asyncio menunjukkan asyncio.open_connection() untuk klien TCP biasa. Panggilan yang sama menerima kata kunci ssl=True yang membungkus koneksi dalam TLS, sekali lagi tanpa pengaturan sertifikat apa pun:
import asyncio
async def main():
reader, writer = await asyncio.open_connection(
"example.com", 443, ssl=True,
)
writer.write(b"GET / HTTP/1.0\r\nHost: example.com\r\n\r\n")
await writer.drain()
print(await reader.read(4096))
writer.close()
await writer.wait_closed()
asyncio.run(main())
Pasangan pembaca/penulis di belakang koneksi TLS memiliki bentuk yang sama seperti untuk koneksi TCP biasa -- hanya pengaturannya yang berbeda. Peringatan yang sama tentang autentikasi berlaku: ssl=True saja hanya memberikan enkripsi, bukan verifikasi.
9.17.4. DTLS -- TLS di atas UDP¶
TLS seperti yang telah dibahas sejauh ini berjalan di atas TCP. Protokol paralel untuk UDP adalah DTLS (Datagram TLS), dan modul ssl kamera mendukungnya dengan cara yang sama. Di mana TLS mengubah satu koneksi TCP menjadi satu aliran byte terenkripsi, DTLS mengubah satu socket UDP menjadi aliran datagram terenkripsi yang dikirimkan secara individual -- sehingga properti kehilangan / out-of-order / tanpa-kontrol-aliran dari UDP dari UDP -- kirim paket, harap yang terbaik semuanya terbawa, dengan byte di dalam setiap datagram kini terenkripsi.
Pembungkusannya terlihat sama seperti kasus TLS, hanya dengan socket SOCK_DGRAM dan konstanta protokol DTLS:
import socket
import ssl
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.connect(socket.getaddrinfo("example.com", 4433)[0][-1])
ctx = ssl.SSLContext(ssl.PROTOCOL_DTLS_CLIENT)
s = ctx.wrap_socket(sock)
s.send(b"ping")
print(s.recv(64))
s.close()
(Memanggil connect() pada socket UDP tidak membuka koneksi -- ia hanya mengingat tujuan default sehingga panggilan send() / recv() berikutnya tidak harus mengulanginya. DTLS membutuhkan tujuan tetap itu untuk menjalankan jabat tangannya.)
Jabat tangannya memiliki bentuk yang sama seperti diagram TLS di atas; perbedaannya adalah bahwa setiap pesan jabat tangan itu sendiri adalah datagram UDP, dan salah satu sisi akan mencoba lagi jika terjadi kehilangan.
Catatan
Apakah kehilangan paket merusak enkripsi? Tidak. Setiap paket DTLS membawa nomor urut, dan enkripsi menggunakan nomor tersebut untuk menghasilkan output yang berbeda untuk setiap paket -- sehingga input yang sama tidak pernah dienkripsi menjadi byte yang sama dua kali, dan setiap paket dapat didekripsi sendiri tanpa paket sebelumnya harus tiba. Paket yang hilang atau tidak berurutan tidak mendesinkronisasi dua sisi. (Jabat tangan itu sendiri adalah satu-satunya bagian yang harus mendarat dengan andal, dan DTLS menangani itu dengan retransmisi sendiri.)
Peringatan enkripsi-saja-tanpa-sertifikat yang sama dari atas berlaku: jabat tangan DTLS terhadap peer CERT_NONE mengenkripsi lalu lintas tetapi tidak memverifikasi siapa sisi lainnya. Alur kerja DTLS lengkap -- sertifikat, cookie anti-spoofing sisi server, bagaimana ini adalah permukaan yang sama dengan TLS selain konstanta protokol -- dibahas bersamaan dengan materi TLS di Bekerja dengan sertifikat TLS.
Versi asyncio menggunakan pola UDP-non-blokir yang sama dari Socket dengan asyncio. Lakukan jabat tangan secara sinkron di depan, alihkan socket ke non-blokir, lalu polling di dalam coroutine:
import asyncio
import socket
import ssl
async def dtls_ping(target_addr, period_ms):
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.connect(target_addr)
# Handshake while still blocking, then switch to async polling.
ctx = ssl.SSLContext(ssl.PROTOCOL_DTLS_CLIENT)
s = ctx.wrap_socket(sock)
s.setblocking(False)
while True:
try:
s.send(b"ping")
except OSError:
pass
await asyncio.sleep_ms(period_ms)
Jabat tangan adalah satu tempat di mana coroutine ini memblokir event loop; setelah itu, setiap s.send / s.recv kembali segera (atau memunculkan OSError), dan await asyncio.sleep_ms menjaga sisa program tetap berjalan.
9.17.5. Melangkah lebih jauh¶
Semua yang lebih dari TLS enkripsi-saja -- memverifikasi sertifikat server HTTPS publik, menjalankan kamera sebagai server TLS yang terautentikasi, TLS bersama antara kamera dan back-end, memilih kunci dan tipe kunci, menangani kedaluwarsa sertifikat -- ada di Bekerja dengan sertifikat TLS. Bagian tersebut mencakup cara menghasilkan sertifikat yang ditandatangani sendiri untuk pengujian lokal, cara mendapatkan sertifikat yang ditandatangani CA untuk produksi, cara menaruhnya ke kamera dalam format yang tepat (DER), cara memverifikasi server publik ketika kamera adalah klien, cara memikirkan perlindungan kunci pada perangkat yang dapat dibongkar penyerang, dan cara merencanakan untuk hari sertifikat kedaluwarsa.
Untuk referensi API ssl lengkap -- versi TLS yang didukung, cipher suite, dan opsi konteks -- lihat Modul ssl --- SSL/TLS.