- Seorang pengembang membagikan perjalanan teknis dan mental saat mengimplementasikan sendiri kompilator ASN.1 (dasn1) dengan bahasa D
- Proyek ini menargetkan implementasi sertifikat x.509 dan TLS 1.3, serta mendukung penanganan encoding DER ASN.1 yang kompleks
- Tulisan ini membahas secara rinci kerumitan struktural ASN.1, tingkat kesulitan implementasi spesifikasi x.680~x.683, serta cara memanfaatkan metaprogramming di bahasa D
- Dijelaskan secara konkret bagaimana fitur D seperti static import, mixin template, typeof(), alias this berguna untuk pembuatan kode dan perancangan AST/IR
- Tulisan ini menyampaikan dengan jujur bahwa “ASN.1 itu menyakitkan tetapi sangat mendidik”, sekaligus menggambarkan kesulitan nyata dan kepuasan dalam membuat kompilator
Gambaran proyek dan motivasi
- Penulis sedang mengembangkan framework I/O asinkron berbasis D bernama Juptune, dan untuk implementasi TLS ia perlu menangani encoding ASN.1 DER secara langsung
- Untuk mem-parsing struktur sertifikat x.509 pada TLS, perlu memahami cara representasi data ASN.1 yang rumit
- Proyek ini dimulai sebagai tantangan pribadi untuk belajar dan bersenang-senang, dan sudah mencapai tahap berhasil mem-parsing beberapa sertifikat
- ASN.1 adalah standar lama dari tahun 1990-an, tetapi masih digunakan luas di sistem modern seperti TLS, SNMP, dan LDAP
- Penulis menyebut bahwa “ASN.1 digunakan luas di dunia, tetapi kebanyakan pengembang bahkan tidak tahu ia ada”
Apa itu ASN.1
- ASN.1 (Abstract Syntax Notation One) adalah bahasa untuk mendefinisikan dan meng-encode struktur data, semacam “nenek moyang protobuf”
- Standarnya terdiri dari notasi (x.680~x.683) dan aturan encoding (BER, CER, DER, PER, XER, JER, dan lain-lain)
- BER: format TLV dasar, mendukung panjang tak terbatas
- CER: bentuk terbatas dari BER, selalu memakai panjang tak terbatas
- DER: subset deterministik dari BER, dipakai sebagai standar dalam kriptografi
- PER/OER: encoding kompresi berbasis bit
- XER/JER: encoding berbasis XML dan JSON
- Jenis encoding yang banyak membuatnya kompleks, tetapi juga memberi fleksibilitas dan ekstensibilitas yang tinggi
Kompleksitas notasi ASN.1
- Standar dasar ASN.1 adalah x.680, sementara spesifikasi ekstensi (x.681~x.683) ditulis dengan gaya akademis yang sangat sulit dipahami
- Implementasi dimungkinkan hanya dengan x.680, tetapi aturan transformasi makna dan variasi sintaksnya membuat tingkat kesulitannya tinggi
- x.681 mendefinisikan sistem Information Object Class, dan mendukung sintaks inisialisasi tersendiri
- Contoh:
CALLED &name [WHO IS &age YEARS OLD]
- x.682 mendefinisikan Table Constraint, dan x.683 mendefinisikan tipe parameterized
- Konsep ini mirip generic di bahasa D, karena bisa menerima tipe maupun nilai sebagai parameter
Fitur menarik ASN.1
- Sistem constraint: memungkinkan rentang nilai atau ukuran dinyatakan langsung saat mendefinisikan tipe
- Contoh:
UInt8 ::= INTEGER (0..255)
- Mendukung operator
SIZE, UNION(|), INTERSECTION(^)
- Sistem versioning: membedakan versi modul secara jelas melalui
OBJECT IDENTIFIER
- Contoh:
id-pkix1-implicit(19) vs id-mod-pkix1-implicit-02(59)
- Memungkinkan identifikasi modul yang jelas tanpa benturan nama
Mengapa bahasa D cocok untuk pembuatan kode
- Static import di D mencegah benturan nama, sambil memungkinkan nama tipe ASN.1 dipertahankan apa adanya
- Fitur module local lookup (.Type1) memperjelas pembatasan pencarian simbol
- typeof() memungkinkan inferensi tipe otomatis sehingga tidak perlu pengelolaan manual saat menghasilkan kode
- Dukungan trailing comma menyederhanakan pembuatan kode
- Berkat penggabungan konstanta saat compile time, penggabungan string tetap bisa dilakukan bahkan di fungsi
@nogc
Contoh implementasi yang memanfaatkan fitur bahasa D
Node AST berbasis mixin template
- Menggunakan fitur mixin template di D untuk mendefinisikan node pohon sintaks ASN.1 (AST)
- Setiap tipe node (
List, Container, OneOf) dapat dipakai ulang sebagai template
- Dibanding pewarisan yang kompleks, ini disederhanakan dengan penyalinan kode saat compile time
API berbasis template dan verifikasi saat compile time
- Node
Container memuat beberapa node turunan, dan melakukan verifikasi tipe saat compile time
- Akses aman dimungkinkan dalam bentuk
node.getNode!Asn1TagDefaultNode
- Node
OneOf menyimpan salah satu dari beberapa tipe, dan mendukung pattern matching melalui fungsi match
- Karena semua handler tipe wajib didefinisikan, keamanan compile time dapat dijamin
Pemanfaatan paket eksperimental pengelolaan memori D
- Menggunakan
std.experimental.allocator untuk mengimplementasikan pembuatan/penghapusan objek di lingkungan @nogc
- Menggabungkan komponen seperti
Region dan StatsCollector untuk membangun allocator kustom
- Namun, statusnya masih eksperimental bahkan setelah 10 tahun
Fitur alias this
- Menggunakan
alias this agar struct pembungkus dapat bertindak seperti field internalnya
- Contoh: casting ringkas seperti
cast(Asn1ValueReferenceIr)item
version(unittest)
- Kata kunci
version(unittest) dipakai untuk mendefinisikan fungsi khusus pengujian, dan tidak akan disertakan dalam build sebenarnya
Test harness dengan template + with()
- Logika pengujian umum dijadikan template, dan pernyataan
with() dipakai untuk menulis kode uji yang ringkas
- Bisa dipanggil sebagai
T() alih-alih Harness.T()
Kesulitan utama selama implementasi
Sintaks value sequence
- Berbagai bentuk sintaks nilai yang dimulai dengan
{} bersifat ambigu tergantung konteks
- Sampai-sampai ada komentar di parser yang menyatakan “ini tidak menyenangkan”
- Karena analisis sintaks dan analisis semantik dipisahkan, tingkat kesulitannya meningkat
Ketidakjelasan spesifikasi
- Ada perilaku yang tidak dijelaskan eksplisit di dokumen, misalnya aturan bahwa tag harus diperlakukan sebagai
EXPLICIT dalam kondisi tertentu
- Cara versioning modul juga tidak didefinisikan dengan jelas
Constraint perlu diimplementasikan tiga kali
- Untuk validasi sintaks
- Untuk validasi nilai
- Untuk pembuatan kode saat runtime
- Saat menangani UNION dan INTERSECTION, penyusunan pesan error juga menjadi rumit
Ilusi node IR yang immutable
- Penulis sempat mengira setelah AST diubah menjadi IR, tidak perlu ada modifikasi lagi,
tetapi proses transformasi makna seperti AUTOMATIC TAGS tetap membutuhkan perubahan data
Kompleksitas ASN.1 secara menyeluruh
- x.509 relatif sederhana karena hanya memakai sintaks lama, tetapi spesifikasi modern mewajibkan implementasi x.681~x.683
- Karena itu ASN.1 nyaris tidak dipakai di luar ranah akademik dan komersial tertentu
Masalah ANY DEFINED BY
ANY DEFINED BY adalah struktur yang tipenya berubah sesuai nilai field lain
- dasn1 tidak mengimplementasikannya, dan menggantinya dengan intrinsic kustom
Dasn1-Any
- Saat decoding nyata, penanganannya tetap perlu dilakukan manual
Kelebihan beban informasi
- Karena mengerjakan beberapa proyek sekaligus seperti ASN.1, x.68x, x.690, dan Juptune, menjaga konteks codebase menjadi sulit
Realitas membuat kompilator
- Ada ribuan visitor node, kode yang berulang, dan implementasi dengan perbedaan sangat kecil, sehingga ini menjadi pekerjaan yang membosankan dan melelahkan
- Namun, di setiap tahap ada rasa pencapaian besar dan efek belajar yang kuat
- Penulis mengenang bahwa “tak akan ada yang memakainya, tetapi saya benar-benar mendapatkan pengalaman kompilator yang nyata”
- Di akhir, tulisan ditutup dengan candaan: “jangan main ASN.1, hidupmu akan berubah”
Kesimpulan
- Meski sudah dikerjakan selama 1 tahun, dasn1 masih belum selesai, tetapi pengalaman ini menjadi momen untuk memahami secara mendalam potensi bahasa D dan kompleksitas ASN.1
- Penulis menutup tulisan dengan nada humoris, sambil bermimpi suatu hari bisa menulis “pengalaman membuat kompilator ASN.1 + implementasi TLS 1.3” di resume, dan meninjau kembali pertumbuhan seorang pengembang serta realitas industri
1 komentar
Komentar Hacker News
Singkatnya, penulis ingin membahas ASN.1, bahasa D, dan compiler itu sendiri
Namun karena tidak menemukan format yang konsisten, berbagai pemikiran terkait akhirnya dikumpulkan menjadi tulisan blog
Hasil akhirnya memang belum sepenuhnya matang, tetapi topiknya memang sulit dibahas secara singkat, jadi harap maklum
Secara matematis,
{0} ∪ ({2} ∩ {4,5,6,7,8}) = {0}, jadi pada akhirnya hanya satu nilai yang diperbolehkanSecara pribadi saya sangat menyukai D, tetapi secara realistis Go dan Rust jauh lebih banyak digunakan
Saya benar-benar bisa memahami perjuangan penulis
Saya suka D, tetapi sudah lama tidak menggunakannya
Saya juga pernah mengerjakan parser dan implementasi protokol, jadi rasanya makin menarik
“OMG ASN.1”, topik yang benar-benar menyenangkan untuk dilihat lagi
Saya ingat masa ketika internet sedang tumbuh dan IETF masih mengembangkan protokol
Saat itu perusahaan-perusahaan belum tertarik pada internet, dan dunia akademik serta IETF yang memimpin
Namun ketika perusahaan menyadari bahwa ini bisa menghasilkan uang, dimulailah Protocol Wars
ASN.1 adalah hasil dari perang itu sekaligus contoh benturan budaya korporat dan budaya akademik
Korporasi bisa dianalogikan sebagai ‘budaya resep’, sedangkan akademisi sebagai ‘budaya fungsi’
Perbedaan cara berpikir ini juga memberi perspektif menarik pada budaya pengembangan AI masa kini
Memikirkan bahwa kita mungkin saja berakhir dengan sistem alamat seperti “CN=wikipedia, OU=org, C=US” alih-alih internet yang sekarang terasa agak mengerikan
Yang sebenarnya menjadi pusat adalah ITU dan ISO
Lalu pada akhir 1990-an ada lagi ‘perang protokol’ lain, dan kali ini IETF yang kalah
ISO terlalu mengejar kesempurnaan sampai menjadi lambat, sedangkan IETF bergerak cepat dengan sikap “nanti diperbaiki belakangan”
Akibatnya, muncul masalah ketika protokol keburu membeku
Implementasi ASN.1 untuk C pada 1990-an yang sangat buruk juga menjadi masalah besar
Ada pepatah Turki yang berbunyi, “Ini bukan barang untuk dipakai manusia!”
Saya ingin menjadikannya moto filsafat desain
Dan seperti kalimat di Game of Thrones, “orang yang menjatuhkan vonis harus mengayunkan pedangnya sendiri,”
orang yang membuat spesifikasi harus mengimplementasikan parser-nya sendiri
Jika persetujuan spesifikasi baru diberikan setelah parser yang benar-benar berjalan dan test ikut diserahkan, kualitasnya sepertinya akan jauh lebih baik
Saya sangat menyukai bahasa D
Saya sedang membuat sendiri editor teks bergaya vim yang hanya bergantung pada Raylib
Kelebihan D antara lain sebagai berikut
version(unittest)Tiap kali membaca dokumentasi atau bertanya ke ChatGPT, saya selalu bisa menemukan solusi yang elegan
Dari sisi filosofi desain, bahasanya nyaris sempurna, tetapi jika tooling dan ekosistemnya setara dengan Rust atau Go, pasti akan jauh lebih sukses
Standard library Phobos punya terlalu banyak gangguan kecil, sampai akhirnya saya menyerah
Versi baru, Phobos V3, memang sedang dikembangkan, tetapi karena orangnya sedikit saya antara berharap dan khawatir
“Apakah saya pernah bilang ASN.1 itu rumit?”
Baik skema maupun format datanya memang kompleks, tetapi sebagian besar kerumitannya bisa diabaikan
Saya tidak memakai notasi skema ASN.1, melainkan menulis sendiri implementasi DER dalam C
Menurut saya DER adalah satu-satunya encoding standar yang benar-benar layak dipakai
Saya juga membuat format encoding sendiri seperti DSER, SDSER, dan TER
Struktur seperti
ANY DEFINED BYjuga masih saya gunakan dengan berguna,dan untuk encoding yang lebih efisien saya bahkan menambahkan fitur nonstandar OBJECT IDENTIFIER RELATIVE TO
Saya juga pernah membuat compiler ASN.1
Saya hanya mengimplementasikan sebagian fitur dari X.681 hingga X.683, tetapi saya membuatnya bisa mendekode seluruh sertifikat secara rekursif hanya dengan satu pemanggilan codec
ASN.1 bukan sekadar sintaks sederhana, melainkan sistem tipe yang kuat
Teknologi ini sering diremehkan, padahal sebenarnya sangat keren
Dulu saya pernah membuat compiler ASN.1 untuk Swift
Itu adalah proyek ASN1Codable, yang memanfaatkan Heimdal libasn1
ASN.1 diubah menjadi JSON AST agar parsing menjadi lebih sederhana
Kalimat “ubah saja jadi JSON” terdengar seperti jeritan developer yang sudah terluka 😄
Anehnya, bekerja dengan ASN.1 terasa menyenangkan
Suatu hari saya ingin membuat sendiri compiler ASN.1 untuk Rust
Implementasi Rust saat ini kebanyakan masih memakai derive macro atau chaining manual, dan itu terasa kurang memuaskan
Secara umum, saat mengimplementasikan standar, 80% fitur bisa selesai dalam 20% waktu,
tetapi 20% sisanya dari ASN.1 mungkin bisa memakan seumur hidup
Saya dulu pernah memperluas parser ASN.1 di codebase Netscape untuk menambahkan dukungan PKCS#12
Saya jadi tahu terlalu dalam soal standar RSA dan definisi ASN.1, dan agak menyesal karenanya,
tetapi saya tetap menghormati ketekunan dan sedikit sifat masokis dari penulis blog ini