Setiap request HTTP yang kita kirim ke API pembayaran atau layanan cloud melintasi jaringan yang tidak kita kendalikan: router kantor, ISP, backbone, dan load balancer milik orang lain. Tanpa enkripsi, siapa pun di jalur tersebut bisa membaca dan bahkan mengubah isi request. Protokol TLS menutup celah itu, dan satu kesalahan konfigurasi di sisi klien — menonaktifkan verifikasi — mengembalikan seluruh risikonya. Chapter ini merangkum apa yang dijamin TLS dan bagaimana menggunakannya dengan benar dari Python.

Apa yang Dijamin TLS #

TLS memberi tiga jaminan sekaligus dalam satu koneksi:

JaminanYang dicegahMekanisme
EnkripsiPenyadap membaca isi request dan responseKunci sesi yang dinegosiasikan secara asimetris
IdentitasPenyerang menyamar sebagai server tujuanSertifikat digital yang diterbitkan certificate authority
IntegritasIsi paket dimodifikasi di tengah jalanMessage authentication code pada setiap record

Sertifikat adalah jaminan yang paling sering disalahpahami: URL https:// saja belum berarti aman bila kode kita tidak memverifikasinya. Sertifikat mengikat sebuah nama domain ke kunci publik, dan verifikasinya memeriksa rantai penerbit hingga akar yang dipercaya — plus masa berlaku dan kecocokan hostname.

requests Sudah Memverifikasi secara Default #

Library requests memverifikasi sertifikat secara bawaan. Kita buktikan dengan mengunjungi server uji self-signed.badssl.com yang memakai sertifikat tanpa penerbit tepercaya:

python
import requests

try:
    requests.get("https://example.com", timeout=10)
    print("example.com: koneksi terenkripsi terverifikasi")
except requests.exceptions.SSLError as e:
    print("example.com: SERTIFIKAT DITOLAK ->", e)

try:
    requests.get("https://self-signed.badssl.com/", timeout=10)
    print("self-signed.badssl.com: koneksi berhasil (seharusnya gagal!)")
except requests.exceptions.SSLError as e:
    print("self-signed.badssl.com DITOLAK oleh requests:")
    print(e)

Output yang Dihasilkan:

text
example.com: koneksi terenkripsi terverifikasi
self-signed.badssl.com DITOLAK oleh requests:
HTTPSConnectionPool(host='self-signed.badssl.com', port=443): Max retries exceeded with url: / (Caused by SSLError(SSLCertVerificationError(1, '[SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: self-signed certificate (_ssl.c:1010)')))

Mari kita bedah kejadiannya:

  1. Baris 4-5 (requests.get("https://example.com", timeout=10)): request berhasil karena sertifikat example.com berantai ke certificate authority yang dikenali oleh kumpulan sertifikat tepercaya sistem operasi.
  2. Baris 9-14 (self-signed.badssl.com): sertifikat situs ini ditandatangani sendiri tanpa penerbit, sehingga requests memunculkan SSLError sebelum satu byte pun terkirim — koneksi ditolak lebih awal, bukan dienkripsi sambil mengabaikan identitas.
  3. Pesan error (CERTIFICATE_VERIFY_FAILED): teks ini menandakan verifikasi bekerja normal. Menemukannya di log produksi berarti server tujuan bermasalah, bukan library kita.

Perlu dicatat juga sertifikat pada mesin pembaca: Python versi tertentu (terutama dari python.org di macOS dan Windows) membawa kumpulan sertifikatnya sendiri melalui package certifi — pip install --upgrade certifi adalah perbaikan pertama saat muncul galat verifikasi yang tidak semestinya.

Anti-Pattern: verify=False #

Saat menghadapi server internal bersertifikat self-signed, banyak pengembang memilih jalan pintas:

python
import requests

# ANTI-PATTERN: jangan ditiru
requests.get("https://server-internal.example.com", verify=False)

Solusi yang benar untuk server internal adalah mempercayai certificate authority internal organisasi: sertifikat CA-nya didaftarkan ke sistem operasi, atau file tersebut diteruskan lewat requests.get(url, verify="/path/ca-bundle.crt").

Module ssl dan Konteks Produksi #

Untuk koneksi tingkat rendah — socket sendiri atau klien kustom — standard library menyediakan module ssl. Pintu masuk yang benar adalah ssl.create_default_context(), yang menyiapkan pengaturan aman bawaan:

python
import ssl

context = ssl.create_default_context()
print("Verifikasi sertifikat aktif? ", context.verify_mode == ssl.CERT_REQUIRED)
print("Pemeriksaan hostname aktif?  ", context.check_hostname)

Output yang Dihasilkan:

text
Verifikasi sertifikat aktif?  True
Pemeriksaan hostname aktif?   True

Context hasil create_default_context() memverifikasi sertifikat (CERT_REQUIRED) dan mencocokkan hostname tujuan — dua pemeriksaan yang harus tetap menyala. Konteks ini bisa diteruskan ke socket_terbungkus = context.wrap_socket(sock, server_hostname=host) saat membungkus socket biasa menjadi koneksi TLS.

Dua catatan penutup. Certificate pinning — membandingkan sidik jari sertifikat server dengan nilai yang dikunci di kode — hanya untuk kasus khusus seperti aplikasi perbankan berisiko tinggi, karena pembaruan sertifikat server akan memutus klien kita. Dan layanan internal antar server sekalipun sebaiknya berjalan di atas TLS: jaringan privat tidak menutup kemungkinan penyusup bergerak menyamping setelah menembus satu titik. Dengan TLS terverifikasi di semua jalur, sisa pekerjaan kita adalah membuktikan bahwa seluruh pertahanan ini benar-benar bekerja — lewat test pada Chapter Security Testing.