Mengapa saya tidak lagi memakai sertifikat gaya lama di situs https saya
(rachelbythebay.com)- Penulis selama bertahun-tahun merasa enggan terhadap protokol ACME karena kompleksitas dan risiko implementasinya
- Klien ACME yang sudah ada banyak yang memiliki kode yang berisiko dari sisi keamanan atau sulit dipahami, sehingga enggan dijalankan sendiri
- Namun, karena kualitas registrar domain Gandi menurun dan harganya naik, ia akhirnya mengimplementasikan alat pembaruan sertifikat sendiri
- Setelah banyak trial and error, ia berhasil menyelesaikan alat untuk menerbitkan sertifikat sendiri melalui Let's Encrypt
- Pada paruh akhir tulisan, dijelaskan secara rinci proses kerja nyata protokol ACME serta detail implementasi tingkat rendah seperti JSON, base64, tanda tangan, dan lain-lain
Why I no longer have an old-school cert on my https site
Latar belakang dan pemicu
- Pada awal 2023, ia menjelaskan alasan tetap mempertahankan sertifikat gaya lama, tetapi pada 2025 ia membagikan alasan akhirnya meninggalkan pendekatan tersebut
- Penolakan terhadap protokol ACME sudah ada sejak 2018, dan teknologi web yang rumit serta skema encoding yang sulit dipahami menjadi hambatan besar
- Sebagian besar klien ACME memiliki kode yang sulit dipercaya, dan dinilai terlalu berisiko untuk dijalankan dengan hak root
- Setelah Gandi diakuisisi oleh private equity, kualitasnya menurun dan harga naik, sehingga tidak ada lagi alasan untuk mempertahankan sertifikat lama
Awal implementasi mandiri
- Tanpa memakai alat yang sudah ada, ia mulai mengimplementasikan sendiri fungsi-fungsi utilitas kecil satu per satu
- Ia memulai dari membungkus pustaka JSON C bernama jansson agar bisa digunakan di C++
- Ia meninjau berbagai pustaka untuk membuat JWK (struktur key), tetapi kebanyakan tidak membantu, sehingga memutuskan untuk membuatnya sendiri
- Di tengah jalan, ia beberapa kali berhenti lalu memulai lagi, sambil secara bertahap menghubungkan komponen-komponen kecil tersebut
Lingkungan pengujian dan penerapan nyata
-
Agar tidak langsung menyentuh server produksi Let's Encrypt, ia menggunakan server ACME pengujian bernama "pebble" di lingkungan terpisah
-
Setelah banyak kegagalan, ia menyelesaikan alat awal yang menerima CSR lalu menerbitkan sertifikat, dan
- berhasil diuji di server staging Let's Encrypt
- berhasil juga di lingkungan produksi
- sudah diterapkan ke situs web nyata
Penjelasan rinci protokol ACME
- Membuat key RSA dan CSR (Certificate Signing Request) yang mencakup CN serta SAN
- Mem-parsing JSON dari URL direktori ACME untuk mengekstrak endpoint seperti newNonce, newAccount, newOrder
- Dari private key, mengekstrak modulus dan public exponent, lalu mengubahnya ke encoding base64url yang sesuai untuk web
- Setelah membuat JWK, melakukan penandatanganan RSA SHA256 bersama payload JSON
- Mengambil Nonce melalui permintaan HTTP HEAD, lalu mengirim permintaan POST yang telah ditandatangani untuk membuat akun
- Header respons
Locationdigunakan bukan sebagai redirect sungguhan, melainkan sebagai URL pengenal akun
Kompleksitas protokol ACME
- Meskipun hanya untuk penerbitan sertifikat sederhana, tetap melibatkan
- hash SHA256, base64web, struktur JSON di dalam JSON, tanda tangan RSA
- permintaan HEAD, identifikasi akun melalui header Location, kebutuhan Nonce sekali pakai, dan lain-lain
- Disebutkan bahwa tahap pemesanan sertifikat, pembuktian kepemilikan domain (seperti record TXT), dan penyelesaian validasi bahkan belum sempat dibahas
- Beberapa klien ternyata tetap berjalan meski implementasi encoding publicExponent-nya salah, sehingga ia juga menyoroti longgarnya penerapan standar
Kesimpulan
- ACME sangat kompleks, dan mengimplementasikannya sendiri menuntut banyak trial and error serta usaha besar
- Meski begitu, ia membagikan bahwa dirinya berhasil meninggalkan sertifikat gaya lama dan beralih ke pendekatan otomatis penuh
- Ia juga menambahkan candaan bahwa kompleksitas ini mungkin saja dirancang untuk menjamin pekerjaan seseorang
1 komentar
Komentar Hacker News
Saya adalah tech lead di tim SRE/infra Let’s Encrypt, jadi saya termasuk orang yang banyak memikirkan masalah seperti ini
JSON Web Signature memang format yang sangat merepotkan, dan ACME API juga sangat bersikeras untuk benar-benar RESTful
Kalau saya yang merancangnya sendiri, saya tidak akan membuatnya seperti ini
Saya rasa latar belakang terbentuknya struktur seperti ini adalah keinginan IETF untuk banyak memanfaatkan standar IETF, ditambah desain ala komite
Dengan beberapa library untuk JSON, JWS, dan HTTP, situasinya memang jadi jauh lebih baik, tetapi terutama di C, bahkan library-library itu sendiri tidak mudah digunakan
Bahasa RFC sendiri juga rumit dan sering merujuk ke dokumen lain, jadi kami sedang membuat klien interaktif dan dokumentasi terpisah untuk membantu hal ini
Saya kurang paham dengan pernyataan bahwa JSON Web Signature adalah format yang merepotkan
Dari sudut pandang saya yang banyak menangani hal-hal kompleks seperti ASN.1, Kerberos, dan PKI, saya tidak merasa JWS adalah format yang sesulit itu
Bahkan jika menulisnya langsung dalam kode, menurut saya itu masih jauh lebih mudah daripada S/MIME, CMS, atau Kerberos
Perlu penjelasan lebih lanjut di bagian mana JWS itu dianggap ‘merepotkan’
Kalau masalahnya JWT, saya justru merasa inti persoalannya adalah belum ada ketentuan yang jelas secara standar tentang bagaimana user agent HTTP harus menerima atau meminta JWT
Saya melihat ada orang yang berkata “kalau mau menerbitkan lebih dari 3 sertifikat harus bayar”, tetapi selama 5 tahun terakhir saya memakainya, saya tidak pernah sekali pun menerima tagihan, jadi ini tampaknya sekadar salah paham atau informasi yang keliru
Saat membahas bagian yang memakai “e=AQAB” alih-alih “e=65537”, dijelaskan bahwa penyebabnya adalah sifat JSON yang tidak bisa menangani angka dengan baik
Jika nilai yang sangat besar seperti 4723476276172647362476274672164762476438 diberikan ke parser JSON, kebanyakan parser JSON akan diam-diam memotongnya menjadi integer 64-bit atau float, atau kalau beruntung malah mengeluarkan error
Bahasa seperti Common Lisp mungkin bisa menanganinya dengan baik, tetapi pada praktiknya tidak banyak orang yang mengembangkan di lingkungan seperti itu
Jadi untuk mengirim angka besar secara andal di JSON, mengubahnya menjadi array byte lalu base64 justru terasa lebih masuk akal
Walau sekilas terlihat tidak masalah, hal ini menjadi sumber berbagai isu keamanan, jadi menurut saya masuk akal jika semua angka dalam protokol ditangani dengan cara seperti ini
Hanya saja, kekurangannya adalah keterbacaan JSON yang ramah manusia jadi hilang, dan secara pribadi saya merasa S-Expression yang distandardisasi akan menjadi pilihan yang jauh lebih baik
Tapi dunia memilih JSON
Kalau tidak mengerti kenapa dunia memilih JSON, saya rasa itu sama saja dengan sengaja mengabaikannya
JSON memungkinkan kebanyakan data ditulis, diedit, dan dibaca manusia dengan mudah
Sebaliknya, Canonical S-Expression mengharuskan informasi panjang ditambahkan di depan setiap elemen, jadi sangat merepotkan jika dikerjakan manual
Untuk menulis S-Expression, kita harus menghitung karakter satu per satu dan mengubah prefix-nya juga, jadi sangat menyebalkan
Bertentangan dengan dugaan, justru kemudahan penulisan dan pengeditan manual seperti inilah yang membuat JSON bertahan
Sebagai catatan, parser JSON di Ruby menangani angka besar dengan baik
Saya pernah dibuat pusing oleh bug di aplikasi C# ketika serializer JSON mengeluarkan BigInt sebagai angka, lalu JS menerimanya dan salah menafsirkannya secara diam-diam
Saya masih heran bahwa overflow adalah perilaku standar alih-alih error
Sejak itu, saya membiasakan diri untuk selalu menangani angka yang lebih besar dari 32-bit sebagai string
Perbandingan antara {"e":"AQAB"} dan {"e":65537} memang ada benarnya, tetapi kalau dibandingkan dengan {"e":"65537"}, hasil penanganannya juga sama di semua parser JSON
Entah angka atau string, keduanya tetap dikonversi dengan jelas
Tentu saja kalau nilainya terlalu besar sampai tidak muat sebagai double, itu sendiri adalah masalah bahasa atau parser, tetapi menurut saya itu terpisah dari cara representasinya
Saya rasa masalah JSON bukan pada formatnya sendiri, melainkan karena parser-nya sejak awal dibuat untuk pemetaan tipe JS
Beberapa parser mungkin bisa menanganinya dengan baik, tetapi kalau begitu portabilitas JSON justru hilang
Mengubahnya ke Base64 pun menimbulkan masalah yang sama (karena tidak sesuai standar)
Parsing kustom dengan replacer dan reviver memang mungkin, tetapi tidak semua lingkungan menjamin fitur itu ada
Pada akhirnya, asumsi bahwa JSON bisa diinterpretasikan dengan parser standar itulah akar kesalahannya
Kalau ini disebut format lain, bukan JSON, mungkin masalah seperti ini akan berkurang, tetapi orang-orang tetap saja akan cenderung memasukkannya ke parser begitu melihat bentuknya seperti JSON
Bahasa Go memiliki tipe
json.Numberyang memungkinkan angka didekodekan sebagai string tanpa kehilangan informasiSaya juga memperkenalkan salah satu tipe decimal presisi arbitrer yang hampir jadi ‘favorit’ saya https://github.com/ncruces/decimal?tab=readme-ov-file#decimal-arithmetic
Setengah bercanda, saya tidak terlalu paham kenapa dalam kasus ini S-Expression dianggap lebih baik
Bahkan di antara LISP pun ada yang tidak mendukung aritmetika presisi arbitrer
Saya heran kenapa penulis begitu kritis terhadap ACME dan berbagai kliennya
Rasanya ini bukan sekadar soal kemampuan memakai alat, jadi saya menduga ada semacam antipati terhadap konsep ACME itu sendiri atau seluruh ekosistem alat di sekitarnya
Sejak 2019 kami juga menerapkannya di beberapa situs berbasis LE, dan selama itu sudah mencoba berbagai klien ACME
Misalnya Crypt-LE cukup cocok untuk kebutuhan kami, dan saat mencoba integrasi dengan Sectigo ACME, le64 terasa kurang sehingga kami mencoba certbot, lego, posh-acme, dan lain-lain
Pada akhirnya kami memakai certbot setelah memperbaiki masalah lingkungan GHA, dan posh-acme juga bagus
Setelah dibaca lagi, nada tajam penulis tampaknya bukan ditujukan ke ACME atau kliennya, melainkan ke spesifikasinya sendiri
Kesimpulannya, ide ACME bagus, tetapi implementasi dan penerapan nyatanya mengecewakan
Saya rasa pandangan saya mirip dengan penulis
Saya mengutip ucapan penulis: ‘banyak klien yang ada itu kodenya berbahaya, dan saya tidak cukup percaya untuk menjalankannya sebagai root di server saya’
Untuk pekerjaan yang sensitif terhadap keamanan, menurut saya sikap hati-hati seperti ini sangat masuk akal
Untuk memberi konteks kepada orang yang kesulitan memahami nada tulisan aslinya, diperkenalkan tautan ke postingan lama
Ada banyak orang yang memang tidak suka menjalankan sesuatu di server yang mereka sendiri tidak pahami, dan saya juga bisa memahami pemikiran itu
Tetapi bidang keamanan pada dasarnya adalah permainan kucing dan tikus, jadi sifatnya memang terus berubah dan pada akhirnya kita harus mengikutinya
Untungnya, ACME memberi kebebasan untuk membuat klien sendiri
Tidak wajib memakai certbot, dan ini juga bukan struktur seperti TPM yang mengunci sumber daya kita
Kalau ingin mengimplementasikan klien ACME dari nol, pengalaman saya menunjukkan bahwa membaca RFC (beserta dokumen terkait seperti JOSE) ternyata lebih mudah dari yang dibayangkan
Saya juga pernah mengimplementasikannya sendiri dan menulis ringkasan untuk memahami alur ACME v2, lalu membagikannya di https://www.arnavion.dev/blog/2019-06-01-how-does-acme-v2-work/
Tulisan ringkasan ini memang tidak menggantikan RFC resmi, tetapi bagus dijadikan referensi seperti flowchart dan indeks berdasarkan jenis pendekatan
Saya bahkan pernah mengimplementasikan klien ACME sebagai proyek akhir kelas keamanan MIT https://css.csail.mit.edu/6.858/2023/labs/lab5.html
Ada sindiran terhadap kenyataan aneh bahwa alih-alih membaca manual satu per satu, menulis postingan panjang di Hacker News dalam bahasa Inggris yang menjelaskan seluruh proses justru bisa menghasilkan lebih banyak internet point
Penulis mengucapkan terima kasih karena ada yang menyoroti makin kompleksnya protokol infrastruktur web
Standar-standar seperti ini memang bukan cuma membebani developer yang harus sekadar memakai tool atau klien, tetapi juga bekerja seperti ‘hambatan regulatif’ yang pada akhirnya membuat hanya perusahaan besar yang sudah mapan mampu memenuhi syarat untuk mengoperasikan internet
ACME saja mungkin belum menjadi hambatan masuk yang tak terlewati, tetapi semua ini menumpuk dan akhirnya menjadi tembok
OpenBSD memiliki klien ACME yang sangat sederhana dan ringan yang sudah disertakan dalam base OS
Katanya ini dibuat baru karena alternatif yang ada terlalu berat dan bertentangan dengan filosofi Unix
Agak disayangkan penulis tampaknya tidak mempertimbangkan opsi ini
Mungkin dengan sedikit usaha, ini juga bisa di-port ke OS lain
Saya justru merasa klien OpenBSD ini adalah contoh bahwa filosofi OpenBSD tidak benar-benar memahami kenapa keamanan bisa serumit ini
Klien ini dipasang dan dipakai pada mesin yang bersangkutan, lalu dibuat dengan struktur terpisah agar tiap komponen tidak saling memengaruhi
Tetapi protokol ACME sendiri sebenarnya memungkinkan pemisahan total (air-gapping), sehingga web server, peminta sertifikat, dan server DNS bisa saja berada di lingkungan yang berbeda
Kalau tidak memakai klien terintegrasi OpenBSD, mungkin memang lebih rumit, tetapi dari sudut prinsip desain keamanan, pendekatan itu menurut saya lebih unggul
‘Cukup pasang OpenBSD dan selesai’ hanyalah cara yang lebih mudah
uacme (https://github.com/ndilieto/uacme) juga diperkenalkan
Ini adalah kode C yang ringan, dan setelah terus-terusan bermasalah dengan klien Python LE karena isu battery, saya memakainya sebagai alternatif dengan stabil
Saya sendiri memakai klien ACME OpenBSD secara langsung, dan pengalaman saya itu berjalan sangat baik
Rekomendasi untuk membuat private key RSA 4096-bit justru hanya menimbulkan masalah penurunan kecepatan bagi pengunjung, sementara keamanan riilnya tetap setara level 2048-bit
Ditekankan bahwa memakai sertifikat leaf RSA 2048-bit lebih baik
Ditanyakan apakah 4096 bit tidak lebih kuat terhadap passive capture/dekripsi di masa depan
Juga ada rasa penasaran apakah keamanan sertifikat intermediate turut terpengaruh oleh serangan asinkron semacam itu
Karena web host hanya mendukung kunci RSA, saya sengaja memakai RSA 4096-bit agar mereka terdorong untuk lebih cepat mendukung kunci EC
Mencoba hal seperti ini sendiri memang bisa meningkatkan kemampuan, tetapi nada tulisan penulis tampak seperti sedang melampiaskan kekesalan terhadap protokol atau proses penyiapan Let’s Encrypt
Dengan library ACME yang ringan (https://github.com/jmccl/acme-lw dan sebagainya) seharusnya otomatisasi pun sudah cukup memungkinkan, jadi saya penasaran kenapa harus dibuat sesulit ini
Masalah flat/bitfield semuanya merupakan warisan sejarah ASN.1/X.509, kompleksitas matematisnya sangat parah, dan semua library serta software masih terikat pada keterbatasan teknologi era 80-an
Saat adopsi LetsEncrypt atau kemunculan HTTP/2 sebenarnya ada peluang terakhir untuk membereskan kekacauan ini, tetapi pada kenyataannya ACME CA cukup dirangkai dari shell script, OpenSSL, dan alkohol, ditambah masalah kompatibilitas dengan software lama, sehingga lompatan besar itu tidak pernah terjadi
Dibagikan pengalaman bahwa tekanan untuk beralih ke HTTPS terus makin kuat
Misalnya, tautan HTTP di WhatsApp sekarang bahkan tidak bisa lagi dibuka
Disarankan bahwa dengan memakai proxy dan caching, beban trafik bisa dikurangi, dan itu adalah cara yang baik untuk server kecil
Ditekankan bahwa sekompleks apa pun ACME, itu tetap jauh lebih baik daripada tidak mendukung TLS sama sekali
“Kunci RSA, digest SHA256, tanda tangan RSA, base64 yang sebenarnya bukan base64, penggabungan string, JSON di dalam JSON, memakai header Location sebagai identifier alih-alih redirect 301, request HEAD hanya untuk satu nilai header, perlu request terpisah untuk nonce di setiap request, dan seterusnya”
“Masih ada langkah yang lebih rumit lagi, seperti membuat order sertifikat, menangani otorisasi dan challenge, key thumbprint, menyusun record TXT, dan lain-lain”
Ini benar-benar kompleksitas yang sulit dipercaya, dan disampaikan pesan dukungan serta terima kasih karena sudah membagikan rangkumannya