9.17. Şifreli soketler ve TLS

Buraya kadar ele alınan her şey baytları açık bir şekilde taşıyor. Kamera ile sunucu arasındaki yoldaki herhangi bir cihaz – ev yönlendiricisi, internet servis sağlayıcısı, bir kafedeki kötü niyetli bir erişim noktası – prensipte içinden geçenleri okuyabilir veya değiştirebilir. İnternet trafiğinin çoğu için bu kabul edilemez. Standart çözüm, bağlantıyı bir şifreleme katmanıyla sarmaktır: TLS, yani Transport Layer Security protokolü. Bir tarayıcıdaki “HTTPS” kilit simgesi, TCP üzerinde çalışan TLS’tir ve başka herhangi bir internet protokolünü “güvenli” kılan da aynı sarmalamadır. Kameranın ssl modülü, bir socket nesnesini TLS ile saran şeydir.

9.17.1. TLS’in eklediği ve kameranın hazır geldiği şeyler

TLS, TCP ile uygulama arasında yer alır – uygulama TLS ile sarılmış bir sokete bayt yazar, TLS bunları şifreler ve sonucu TCP’ye verir, süreç karşı tarafta tersine işler. Tam haliyle TLS, düz TCP’nin üzerine üç garanti ekler:

  • Gizlilik. Yol üzerindeki dinleyiciler, iki uç noktanın alışveriş ettiği şeyleri okuyamaz.

  • Bütünlük. İletim sırasındaki herhangi bir trafik değişikliği tespit edilir; bağlantı, kurcalanmış veri teslim etmek yerine kopar.

  • Kimlik doğrulama. Sunucu, bir sahtekâr değil, adlandırılan sunucu olduğunu kanıtlar (ve isteğe bağlı olarak, istemci de kendisinin kim olduğunu kanıtlar).

İlk ikisi, şifrelemenin kendisinden gelir. Üçüncüsü ise en az bir tarafta sertifikalar ve bu sertifikaları doğrulamak için önceden güvenilen bir şey gerektirir. OpenMV kamera hiçbir yerleşik sertifika deposu olmadan gelir: yeni flaşlanmış bir kamera hiçbir sertifika otoritesine güvenmez, kendine ait bir sunucu sertifikası yoktur ve varsayılan doğrulama modu (ssl.CERT_NONE) eşin sertifikasını hiçbir şeye karşı kontrol etmez. Yani kutudan çıktığı haliyle, kameradaki TLS size ilk iki garantiyi verir – pasif bir gözlemci tarafından gerçekleştirilen dinleme ve kurcalamaya karşı şifreleme – ancak üçüncüsünü vermez.

9.17.2. Giden bir bağlantıyı şifreleme

En basit kullanım, giden bir TCP bağlantısını sarmaktır. Akış şöyledir: normal bir TCP soketi açın, bunu ssl.wrap_socket() fonksiyonuna verin, ardından sarılmış soket üzerinden tıpkı düz soketle yapacağınız gibi okuyup yazın:

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()

Sarmalama, TLS el sıkışmasını gerçekleştirir; bundan sonra s.send üzerinden geçen her bayt giderken şifrelenir ve s.recv üzerinden gelen her bayt hat üzerinde şifrelenmişti. Hiçbir sertifika yapılandırılmadı, hiçbir güven çıpası sağlanmadı – TLS yalnızca yanıt veren sunucuyla geçici bir oturum anahtarı üzerinde anlaşır ve onu kullanır.

"client" ve "server" olarak etiketlenmiş iki sütunlu bir şema. Üstte yer alan kesik çizgili yatay bir çizgi "TCP connection already open" olarak etiketlenmiştir. Bunun altında, üç ok TLS el sıkışmasını gösterir: istemciden sunucuya "ClientHello", geriye doğru "ServerHello + certificate + key share" ve tekrar ileriye doğru "Finished". Bunun altındaki ikinci kesik çizgili yatay çizgi "TLS session open -- everything after this is encrypted" olarak etiketlenmiştir. Altındaki iki kalın çift yönlü ok "encrypted data" taşır.

ssl.wrap_socket() fonksiyonunun çalıştırdığı TLS el sıkışması. Önceki şekildeki halihazırda açık olan TCP bağlantısının üzerinde yer alır; her iki taraf da Finished gönderdiğinde, konuşmanın geri kalanı her iki yönde de şifrelenir.

Uyarı

Bu yalnızca şifrelemedir, kimlik doğrulamalı TLS değildir. Kamera, TCP bağlantısının diğer ucunda yanıt veren her ne ise onunla güvenli bir şekilde konuşur. Bir ortadaki adam (man-in-the-middle) bağlantıyı kendi kontrol ettiği bir sunucuya yönlendirirse ve o sunucu herhangi bir sertifika sunarsa, el sıkışma yine de başarılı olur ve kamera saldırganla güvenli bir şekilde konuşmaya başlar. Bu modu yalnızca ortadaki adamın tehdit modelinin bir parçası olmadığı durumlarda kullanın – kapalı bir yerel ağ, bir geliştirme ortamı, kameranın aynı donanımda çalışan bir hizmetle konuşması – genel internete erişirken değil.

Gerçek kimlik doğrulama için – kameranın bir genel sunucuyu doğrulaması, kameranın bir TLS sunucusu olarak davranması veya karşılıklı TLS – sertifikaları cihaza getirmeniz gerekir. Konunun tamamı TLS sertifikalarıyla çalışma içindedir.

Aynı sarmalama, sunucu protokolünü seçerek ve ssl.wrap_socket() fonksiyonuna server_side=True geçirerek gelen TCP trafiği için de çalışır. Yukarıdaki uyarı hâlâ geçerlidir: kendine ait bir sertifikası olmadan kamera, istemciye kim olduğunu kanıtlayamaz ve meraklı bir istemci, çoğu TLS yığınında bir “sertifika yok” el sıkışma hatası görür. Üretim tarafındaki sertifika iş akışı, kamerayı yararlı bir şekilde TLS sunucusu olarak çalıştırmanın önünü açan şeydir.

9.17.3. asyncio ile

asyncio bölümü, düz TCP istemcileri için asyncio.open_connection() fonksiyonunu göstermişti. Aynı çağrı, bağlantıyı yine herhangi bir sertifika kurulumu olmadan TLS ile saran bir ssl=True anahtar sözcüğünü kabul eder:

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())

Bir TLS bağlantısının arkasındaki okuyucu/yazıcı çifti, düz bir TCP bağlantısındakiyle aynı biçimdedir – yalnızca kurulum farklıdır. Kimlik doğrulamaya ilişkin aynı uyarı geçerlidir: tek başına ssl=True yalnızca şifreleme sağlar, doğrulama sağlamaz.

9.17.4. DTLS – UDP üzerinde TLS

Şimdiye kadar tartışılan TLS, TCP’nin üzerinde çalışır. UDP için paralel protokol DTLS (Datagram TLS) olup, kameranın ssl modülü onu aynı şekilde destekler. TLS bir TCP bağlantısını tek bir şifrelenmiş bayt akışına dönüştürürken, DTLS tek bir UDP soketini şifrelenmiş, ayrı ayrı teslim edilen datagramların bir akışına dönüştürür – böylece UDP – bir paket gönder, en iyisini um sayfasındaki UDP’nin kayıp / sıra dışı / akış denetimi yok özellikleri, artık her datagramın içindeki baytlar şifrelenmiş olarak hepsi aktarılır.

Sarmalama, TLS durumundakiyle aynı görünür, sadece bir SOCK_DGRAM soketi ve DTLS protokol sabitleriyle:

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()

(Bir UDP soketinde connect() çağırmak bir bağlantı açmaz – yalnızca varsayılan bir hedefi hatırlar, böylece sonraki send() / recv() çağrılarının bunu tekrarlamasına gerek kalmaz. DTLS, el sıkışmasını yürütmek için bu sabit hedefe ihtiyaç duyar.)

El sıkışma, yukarıdaki TLS şemasıyla aynı biçimdedir; fark, her el sıkışma mesajının kendisinin bir UDP datagramı olması ve her iki tarafın da kayıp durumunda yeniden denemesidir.

Not

Paket kaybetmek şifrelemeyi bozar mı? Hayır. Her DTLS paketi bir sıra numarası taşır ve şifreleme, her paket için farklı çıktı üretmek üzere bu numarayı kullanır – böylece aynı girdi asla iki kez aynı baytlara şifrelenmez ve herhangi bir paket, öncekinin varmış olmasına gerek kalmadan kendi başına çözülebilir. Kayıp veya sıra dışı paketler iki tarafı senkronizasyondan çıkarmaz. (El sıkışmanın kendisi güvenilir bir şekilde ulaşması gereken tek kısımdır ve DTLS bunu kendi yeniden iletimiyle halleder.)

Yukarıdan gelen aynı sertifikasız-yalnızca-şifreleme uyarısı geçerlidir: bir CERT_NONE eşine karşı yapılan bir DTLS el sıkışması trafiği şifreler ancak karşı tarafın kim olduğunu doğrulamaz. DTLS iş akışının tamamı – sertifikalar, sunucu tarafındaki sahtekârlık önleyici çerez, bunun protokol sabitleri dışında TLS ile aynı yüzey olması – TLS materyaliyle birlikte TLS sertifikalarıyla çalışma içinde ele alınır.

asyncio sürümü, asyncio ile soketler sayfasındaki aynı bloklamayan UDP desenini kullanır. El sıkışmayı önceden senkron olarak yapın, soketi bloklamayan moda geçirin, ardından bir eşyordam içinde yoklayın:

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)

El sıkışma, bu eşyordamın olay döngüsünü bloklayan tek yeridir; bundan sonra her s.send / s.recv hemen geri döner (veya OSError fırlatır) ve await asyncio.sleep_ms programın geri kalanının çalışmaya devam etmesini sağlar.

9.17.5. Daha ileriye gitmek

Yalnızca-şifreleme TLS’inin ötesindeki her şey – bir genel HTTPS sunucusunun sertifikasını doğrulamak, kamerayı kimlik doğrulamalı bir TLS sunucusu olarak çalıştırmak, kamera ile bir arka uç arasında karşılıklı TLS, anahtarları ve anahtar türlerini seçmek, sertifika süresinin dolmasıyla başa çıkmak – TLS sertifikalarıyla çalışma içindedir. O bölüm, yerel test için kendinden imzalı sertifikaların nasıl üretileceğini, üretim için CA imzalı sertifikaların nasıl edinileceğini, bunların doğru formatta (DER) kameraya nasıl alınacağını, kamera istemci olduğunda bir genel sunucunun nasıl doğrulanacağını, bir saldırganın parçalarına ayırabileceği bir cihazda anahtar korumasının nasıl düşünüleceğini ve sertifikanın süresinin dolacağı gün için nasıl plan yapılacağını ele alır.

ssl API başvurusunun tamamı için – desteklenen TLS sürümleri, şifre paketleri ve bağlam seçenekleri – ssl — SSL/TLS modülü sayfasına bakın.