Document Signing CA
On this page
Document Signing CA adalah certificate authority penerbit (issuing CA) berbahasa Rust yang memberi tiap orang penanda tangan dokumen PDF sertifikatnya sendiri, lalu terus menjawab pertanyaan yang diajukan setiap pembaca PDF saat membuka file bertanda tangan: apakah sertifikat ini masih sah? Ia menerbitkan sertifikat X.509 di bawah root privat, menerbitkan daftar pencabutan, menjalankan OCSP responder, dan menyerahkan key beserta rantai sertifikat tiap penanda tangan kepada service penanda tangan sebagai satu bundel PKCS#12.
Saya membangunnya karena tanda tangan hanya bernilai sebesar rantai di belakangnya. Pembaca PDF di luar organisasi, bertahun-tahun sesudah penandatanganan, harus bisa berjalan dari sertifikat penanda tangan sampai ke root, mengetahui apakah sertifikat itu dicabut, dan melakukan keduanya tanpa akun maupun rahasia. Membuat setiap field, ekstensi, dan respons tepat persis bagi pembaca-pembaca itu adalah sebagian besar pekerjaannya.
Sekilas
| Bahasa & framework | Rust, dengan actix-web, sqlx di atas PostgreSQL, dan OpenSSL — lewat binding Rust-nya, dan lewat interface C-nya di tempat binding itu berhenti |
|---|---|
| Arsitektur | Flat Layered |
| Versi | 2.11.2 |
| Status | Stabil, berjalan di production |
| Bentuk | Service HTTP mandiri: endpoint pencabutan publik untuk validator mana pun, dan API terautentikasi untuk aplikasi penanda tangan |
| Kode sumber | Privat |
Yang dikerjakannya
A. Menerbitkan sertifikat tanda tangan
Aplikasi penanda tangan meminta sertifikat atas nama seseorang, dengan field subject yang diwajibkan organisasi. CA menyiapkan request-nya, melindungi key penanda tangan sehingga hanya penanda tangan itu yang bisa membukanya, dan menahan request itu berstatus menunggu sampai disetujui. Persetujuan menandatangani request dengan key issuing CA dan langsung mengembalikan sertifikatnya.
B. Menjawab "apakah sertifikat ini masih baik?"
OCSP responder menjawab pertanyaan pencabutan tentang sertifikat mana pun yang diterbitkan CA ini, untuk client mana pun, tanpa autentikasi: baik, dicabut beserta alasan dan waktunya, atau tak dikenal. Tiap jawaban ditandatangani sertifikat responder khusus dan membawa issuing CA, jadi validator bisa memeriksanya sendiri.
C. Menerbitkan daftar pencabutan
Untuk validator yang lebih suka daftar ketimbang bertanya, CA menerbitkan certificate revocation list di alamat yang tertulis di setiap sertifikat terbitannya, lengkap dengan nomor urut, alasan per entri, dan header cache yang memberi tahu client kapan harus mengambilnya lagi.
D. Mengirim key dan rantai sekaligus
Saat tiba waktunya menandatangani, service penanda tangan menerima sertifikat penanda tangan, sertifikat issuing CA, dan private key penanda tangan sebagai satu bundel PKCS#12 yang hanya terbuka dengan passphrase penanda tangan. PDF yang ditandatangani dengannya membawa rantai lengkap yang dibutuhkan pembaca.
E. Mengatur siapa yang boleh bertanya
Semua endpoint selain endpoint pencabutan publik mewajibkan access token bertanda tangan. User punya role, token bisa dicabut sebelum kedaluwarsa, dan setiap penerbitan, pencabutan, dan pemeriksaan pencabutan dicatat: siapa yang meminta, dari mana, dan apa yang terjadi.
Antarmuka
| Kemampuan | Akses |
|---|---|
| Sertifikat issuing CA dalam DER, di alamat yang ditunjuk sertifikat | publik |
| Certificate revocation list | publik |
| OCSP, lewat GET dan POST | publik |
| Readiness, mencakup database dan root CA | publik |
| Meminta sertifikat; menyetujui request yang menunggu | token |
| Mencabut sertifikat dengan alasan RFC 5280 | token |
| Mengekspor sertifikat, rantainya, dan key-nya sebagai PKCS#12 | token |
| Login; mengelola user, role, password, dan access token | token, menurut role |
Client standar mana pun bisa bertanya ke responder. Begini pemeriksaan yang biasa dijalankan validator, dengan host contoh:
Standar
| Standar | Yang dijaminnya di sini |
|---|---|
| X.509 v3 dan RFC 5280 | sertifikat dan daftar pencabutan yang bisa dibaca validator mana pun dan ditelusuri sampai root |
| RFC 6960, OCSP | jawaban pencabutan lewat GET atau POST, dengan nonce, dari responder yang didelegasikan |
| RFC 7292, PKCS#12 | satu bundel terenkripsi berisi key, sertifikat, dan rantai tanpa trust anchor-nya |
| PKCS#10 | certificate request diverifikasi sebelum ditandatangani |
| Authority Information Access dan CRL Distribution Points | sertifikat yang memberi tahu validator di mana issuer dan status pencabutannya bisa ditemukan |
| JSON Web Token dengan RS256 | access token yang ditandatangani key yang bisa dirotasi CA |
| W3C Trace Context dan OpenTelemetry | trace, log, dan metrics yang menyambung trace pemanggil |
Di dalamnya
CA ini mengikuti bentuk Flat Layered yang saya pakai untuk service Rust yang harus cepat jadi: route, controller, service, repository, dan helper berjajar satu tingkat, dan setiap operasi — request, approve, revoke, export, menjawab OCSP, membangun daftar pencabutan — adalah satu file di folder services. Jalur dari route ke database jadi pendek dan mudah diikuti. PostgreSQL tinggal di foldernya sendiri di dalam repositories, siap bila kelak ada storage lain. Helper yang membangun key, request, sertifikat, dan respons dijadikan stateless secara bertahap: versi kedua tiap helper berjalan berdampingan dengan yang pertama sampai hasilnya sama, baru kemudian menggantikannya, dan berakhir dengan build tanpa error maupun warning.
Keputusan rancangan
Setiap sertifikat dibentuk untuk pembaca yang akan menilainya. Sertifikat tanda tangan berversi 3 karena membawa ekstensi; satu angkatan awal yang terbit sebagai versi 1 dengan ekstensi versi 3 mengajari saya untuk menetapkan versinya secara eksplisit. Key usage-nya critical dan terbatas pada digital signature dan non-repudiation, extended key usage-nya menyebut document signing, dan authority key identifier-nya hanya membawa key identifier, karena bentuk panjangnya menunjuk root lewat nama dan membingungkan validator. Field subject, urutannya, dan aturannya — panjang pasti, format e-mail, minimum dan maksimum — tinggal di konfigurasi, begitu pula pilihan apakah alamat e-mail ikut masuk subject alternative name. Profil organisasi baru tak butuh kode baru. Sertifikat berlaku tiga tahun di bawah issuing CA yang berlaku sepuluh tahun dan root yang berlaku dua puluh tahun, dengan path length yang mencegah rantai bertambah panjang.
Kepercayaan berawal dari satu root, dan issuing CA membuktikan dirinya terhadap root itu. Sebelum service menerima request, sertifikat issuing CA diverifikasi terhadap root itu, dan setiap keraguan berakhir dengan penolakan.
Menandatangani tak boleh menuntut penanda tangan memasang apa pun. Tepat saat penandatanganan, service penanda tangan menerima bundel PKCS#12 siap pakai yang hanya bisa dibuka passphrase milik penanda tangan. Request diverifikasi sebelum ditandatangani, persetujuan berlangsung dalam satu transaksi dengan satu timestamp untuk sertifikat dan catatannya, dan satu request hanya bisa diterbitkan sekali.
Bundel membawa rantai, dan tak lebih. Ekspor PKCS#12 berisi sertifikat penanda tangan dan issuing CA, tapi tidak root: root adalah trust anchor yang harus sudah dipegang pembaca, bukan barang yang diserahkan lewat file. Sebelum perubahan ini, service penanda tangan melihat rantai sepanjang satu lalu mencoba — dan gagal — mengambil issuer yang hilang sendiri.
Pemeriksaan pencabutan bersifat publik, karena validator adalah orang asing. Pembaca PDF yang memeriksa tanda tangan milik siapa pun yang menerima dokumen, bukan milik organisasi yang menandatanganinya. Karena itu OCSP responder dan daftar pencabutan menjawab siapa saja, lewat GET maupun POST. Keduanya sengaja diletakkan di luar scope terlindung: pernah lapisan autentikasi memblokir responder, dan perbaikan yang memisahkan keduanya sempat membuat semua route lain menjawab 404, sampai kedua endpoint publik itu didaftarkan di tingkat teratas aplikasi.
Jawaban OCSP harus bisa diperiksa sendiri dan tahan terhadap jam yang tak sempurna. Jawaban ditandatangani sertifikat responder tersendiri, bertanda critical untuk OCSP signing, dan CA memperbaruinya secara otomatis. Tiap jawaban membawa issuing CA, memantulkan nonce client, dan mencocokkan issuer memakai algoritma yang sama dengan client. Awal masa berlaku jawaban dimundurkan sedikit, supaya client yang jamnya sedikit terlambat tak membuangnya sebagai "belum berlaku". Request yang cacat atau gagal menerima status error OCSP yang ditandatangani, bukan HTTP 500, karena itulah yang dipahami validator; jawaban tanpa nonce membawa header cache yang kedaluwarsa pada sembilan puluh persen masa berlakunya.
Di tempat library Rust berhenti, interface C melanjutkan. Binding OpenSSL untuk Rust tak bisa membangun daftar pencabutan maupun respons OCSP, jadi CA memanggil interface C OpenSSL secara langsung, lewat binding yang dibuat hanya untuk daftar fungsi yang sempit. Daftar pencabutan berversi 2, versi yang dipakai pembaca PDF Adobe; ia membawa nomor urut, authority key identifier, dan alasan untuk tiap entri. Daftar itu hanya disajikan di alamat yang disebut sertifikat: endpoint itu pernah rusak tanpa ada yang menyadari, dan saat memperbaikinya saya menghapus path terpisah yang dulu membangun daftar, jadi hanya ada satu alamat untuk diperiksa dan itulah alamat yang dipakai validator.
Alamat pencabutan memakai HTTP biasa. Alamat yang ditulis di setiap sertifikat — tempat issuer, daftar pencabutan, dan responder ditemukan — memakai HTTP, bukan HTTPS: memeriksa pencabutan lewat TLS berarti lebih dulu memeriksa pencabutan sertifikat TLS-nya, lingkaran yang dihindari validator. Sertifikat issuing CA sendiri disajikan dalam DER di alamatnya, dan sertifikat responder hanya menunjuk issuer-nya, tak pernah menunjuk dirinya sendiri. Saat satu sertifikat lama ternyata membawa alamat HTTPS, penyebabnya konfigurasi yang usang, bukan kode; sertifikat tak bisa disunting, jadi jawabannya menerbitkan ulang.
CA mengautentikasi pemanggilnya sendiri. Aplikasi penanda tangan memanggil CA secara langsung, jadi akses memakai token bertanda tangan. Token membawa key identifier, sehingga merotasi key penanda tangan token tak membatalkan token yang sudah terbit, dan token mana pun bisa dicabut sebelum kedaluwarsa; user bisa memegang beberapa role di tingkat berbeda, dan password hanya disimpan dalam bentuk hash.
Setiap keputusan tentang sertifikat meninggalkan bukti. Telemetry pindah dari menulis langsung ke Elasticsearch menjadi OpenTelemetry lewat collector, sambil melanjutkan trace pemanggil. Penerbitan, pencabutan, pemeriksaan OCSP, dan daftar pencabutan menghasilkan event audit yang menyebut siapa yang bertindak, dari mana, atas apa, dan dengan hasil apa. Nilai sensitif diredaksi dari log, sementara identitas orang yang bertindak dijaga secara eksplisit — satu field yang tak boleh hilang dari audit tanda tangan. Metrics memakai delta temporality, yang diwajibkan exporter Elasticsearch milik collector; alamat client diambil dari header forwarded hanya bila datang dari trusted proxy, dengan rantai mentahnya disimpan sebagai bukti.
Skema ikut bersama binary yang membutuhkannya. Migration menjadi bagian dari image dan dijalankan oleh langkah deployment terpisah. Dulu migration hanya berjalan saat database kosong pertama kali dibuat, sehingga perubahan berikutnya tak pernah sampai ke staging maupun production. Menyimpannya di repository lain saya tolak, karena lupa menyalinnya akan gagal tanpa suara. Readiness baru melapor sehat bila database dan root CA sama-sama menjawab, dan service berjalan sebagai user non-root.
Spesifikasi
| Masukan | Request, persetujuan, pencabutan, dan ekspor sertifikat dari aplikasi penanda tangan; pertanyaan OCSP dan unduhan daftar dari validator mana pun |
|---|---|
| Hasil | Sertifikat X.509 v3, bundel PKCS#12, jawaban OCSP bertanda tangan, dan daftar pencabutan versi 2 |
| Key | RSA 4096 untuk penanda tangan, responder, dan CA; SHA-256 secara default |
| Masa berlaku | Penanda tangan tiga tahun; responder diperbarui otomatis; jawaban OCSP dan daftar pencabutan satu hari |
| Penyimpanan | PostgreSQL, dengan migration yang dikirim di dalam image |
| Diverifikasi dengan | Tool OCSP dan CRL milik OpenSSL serta pembaca PDF yang memvalidasi tanda tangannya |
| Observability | Trace, log, metrics, dan event audit OpenTelemetry lewat OTLP |
| Berjalan sebagai | Image container, sebagai user non-root, dengan konfigurasi di-mount dari volume |
Terkait
Toolkit yang menjalankan migration CA saat satu rilis sampai ke production.
Ke mana event audit, trace, dan metrics CA pergi setelah meninggalkan service.
Istilah
- Certificate authority
- Service yang menjamin satu public key dengan menandatangani sertifikat yang mengikat key itu pada satu nama.
- Issuing CA
- Certificate authority yang sertifikatnya ditandatangani root, dan yang menandatangani sertifikat yang benar-benar dipakai orang.
- OCSP
- Online Certificate Status Protocol: client menanyakan apakah satu sertifikat masih sah dan menerima jawaban bertanda tangan.
- Certificate revocation list
- Daftar bertanda tangan berisi semua sertifikat yang telah dicabut CA, diterbitkan di alamat yang tertulis di sertifikatnya.
- PKCS#12
- Format file yang membundel private key dengan sertifikat dan rantainya, dilindungi password.
- Trust anchor
- Sertifikat root yang sudah dipercaya validator, tempat setiap rantai yang diperiksanya harus berakhir.
Technologies covered
- Rust
- PostgreSQL
- OpenTelemetry
- OCSP
- X.509