14.4.5. Memverifikasi server publik (kamera sebagai klien)

Semua yang ada di halaman sebelumnya tentang klien yang "sudah memiliki root" berlaku untuk browser, ponsel, dan PC -- hal itu tidak berlaku untuk kamera. ssl MicroPython dikirimkan tanpa trust store bawaan: kamera yang baru di-flash tidak mempercayai CA sama sekali, dan nilai default (ssl.CERT_NONE) tidak memverifikasi apa pun dan terbuka lebar terhadap serangan man-in-the-middle. Jadi ketika kamera bertindak sebagai klien yang terhubung ke server TLS publik (API HTTPS, broker MQTT, ...) dan Anda ingin benar-benar memverifikasi server tersebut, Anda harus menyediakan trust anchor sendiri.

Mekanismenya sama dengan contoh klien self-signed di Sertifikat yang ditandatangani sendiri; satu-satunya perbedaan adalah file yang Anda muat adalah sertifikat CA nyata, bukan sertifikat peer itu sendiri:

  1. Dapatkan sertifikat CA yang menjangkarkan rantai server. "Menjangkarkan" berarti sertifikat di (atau dekat) bagian atas rantai server yang Anda pilih sebagai titik awal kepercayaan. Server TLS mengirimkan sertifikat leaf dan biasanya intermediate-nya; server tidak pernah mengirimkan root-nya. Anda harus mendapatkan trust anchor tersebut sendiri dan secara independen dari server -- sekadar mempercayai apa yang diberikan server akan menggagalkan seluruh tujuan verifikasi.

    Pertama, cari tahu CA mana yang sebenarnya menerbitkan sertifikat server. Misalnya, terhadap openmv.io

    openssl s_client -connect openmv.io:443 -showcerts < /dev/null
    

    Blok Certificate chain mencantumkan setiap sertifikat dengan subject (s:) dan issuer (i:)-nya; OpenSSL terbaru juga mencetak baris a: (jenis kunci) dan v: (validitas) yang bisa diabaikan di sini:

    Certificate chain
     0 s:CN=openmv.io
       i:C=US, O=Let's Encrypt, CN=E8
     1 s:C=US, O=Let's Encrypt, CN=E8
       i:C=US, O=Internet Security Research Group, CN=ISRG Root X1
    

    Entri 0 adalah leaf (openmv.io), diterbitkan oleh intermediate E8. Entri 1 adalah intermediate tersebut, diterbitkan oleh root ISRG Root X1. Issuer (i:) dari entri teratas menamai root -- di sini ISRG Root X1. (Intermediate-nya adalah E8 bukan R10 / R11 yang mungkin pernah Anda lihat di tempat lain karena openmv.io menggunakan sertifikat ECDSA; Let's Encrypt menandatangani leaf ECDSA dengan intermediate seri E-nya dan leaf RSA dengan seri R-nya. Keduanya berantai ke ISRG Root X1.)

    OpenSSL juga mencetak baris depth= dan mungkin melaporkan root dengan Verification: OK. Hal itu terjadi hanya karena PC Anda sudah mempercayai ISRG Root X1 -- server tidak mengirimkannya (server tidak pernah mengirimkan root-nya), dan kamera, yang tidak memiliki trust store, juga tidak akan memilikinya. Itulah tepatnya mengapa Anda harus menyediakannya.

    Unduh root tersebut dari root yang dipublikasikan oleh CA sendiri. Let's Encrypt mengkatalogkan semua miliknya di halaman sertifikat Let's Encrypt; file langsung untuk ISRG Root X1 adalah isrgrootx1.pem (mereka juga menawarkannya yang sudah dienkode sebagai isrgrootx1.der). CA lain mempublikasikan miliknya di halaman "root certificates" / "repository" serupa; kumpulan publik kanonik adalah Mozilla CA program (CCADB). Konfirmasi Anda mengambil file yang benar dengan membandingkan sidik jarinya terhadap nilai yang dipublikasikan CA (tambahkan -inform DER jika Anda mengunduh .der):

    openssl x509 -in isrgrootx1.pem -noout -subject -fingerprint -sha256
    

    Jika Anda tidak ingin melacak root, Anda bisa menyalin intermediate langsung dari output -showcerts (blok -----BEGIN CERTIFICATE----- kedua), mempercayainya, dan menerima bahwa Anda harus memperbarui setiap kali CA merotasi intermediate -- jauh lebih sering daripada root (lihat trade-off di bawah).

  2. Konversi ke DER, persis seperti sebelumnya:

    openssl x509 -in isrgrootx1.pem -outform DER -out ca.der
    
  3. Salin ca.der ke kamera (filesystem atau ROMFS) dan muat sebagai trust anchor:

    import socket
    import ssl
    import ntptime
    
    ntptime.settime()                  # validity check needs the clock
    
    addr = socket.getaddrinfo("api.example.com", 443)[0][-1]
    sock = socket.socket()
    sock.connect(addr)
    
    ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
    ctx.verify_mode = ssl.CERT_REQUIRED
    ctx.load_verify_locations(cafile="ca.der")
    ssock = ctx.wrap_socket(sock, server_hostname="api.example.com")
    

    server_hostname diperlukan di sini: ia mendorong SNI dan merupakan nama yang diperiksa terhadap subjectAltName sertifikat server.

Tip

Pintasan untuk kasus umum. Let's Encrypt adalah CA publik yang paling banyak digunakan, dan sertifikat RSA maupun ECDSA-nya saat ini berantai ke ISRG Root X1 (seperti yang ditunjukkan contoh openmv.io di atas). Jika server yang diakses kamera Anda menggunakan Let's Encrypt, Anda bisa melewati inspeksi sepenuhnya: cukup letakkan isrgrootx1.der di kamera dan panggil load_verify_locations untuk memuat.

Ini tidak membuat TLS bekerja ke setiap situs. Server yang sertifikatnya berasal dari CA berbeda (DigiCert, Google Trust Services, Amazon, Sectigo, ...) tetap akan gagal verifikasi, dan karena kamera mempercayai satu sertifikat DER per ssl.SSLContext Anda tidak bisa membundel setiap root seperti yang dilakukan browser. Jika ragu, identifikasi CA server yang sebenarnya seperti yang ditunjukkan di atas dan percayai root tersebut.

Sertifikat mana yang Anda percayai adalah sebuah trade-off:

  • Root (direkomendasikan). Berumur panjang -- sering kali puluhan tahun -- sehingga ca.der jarang berubah. Ini mengharuskan server mengirimkan intermediate-nya agar mbedTLS dapat membangun jalur leaf → intermediate → root tepercaya Anda; hampir setiap server publik yang dikonfigurasi dengan benar melakukan ini.

  • Intermediate. Juga berfungsi, dan tetap berfungsi bahkan jika server menghilangkan intermediate, tetapi intermediate dirotasi jauh lebih sering daripada root, sehingga Anda harus memperbarui ca.der lebih sering.

  • Leaf itu sendiri (certificate pinning). Paling ketat, tetapi leaf berubah setiap pembaruan -- sekitar setiap 90 hari untuk Let's Encrypt -- sehingga ini hanya masuk akal ketika Anda juga mengontrol server dan dapat mendorong pin baru ke setiap kamera secara bersamaan. Inilah tepatnya yang dilakukan contoh klien self-signed.

Catatan

ssl.SSLContext.load_verify_locations() mengambil satu sertifikat CA berenkode DER, sehingga kamera mempercayai tepat satu anchor pada satu waktu. Untuk menjangkau server di bawah CA yang berbeda, gunakan ssl.SSLContext terpisah per anchor. Dan karena sertifikat tersebut pada akhirnya akan kedaluwarsa atau dirotasi oleh CA, perlakukan seperti sertifikat lain apa pun di perangkat.