ASN.1 adalah standar (ISO, ITU-T, GOST) dari bahasa yang menggambarkan informasi terstruktur, serta aturan penyandian untuk informasi ini. Bagi saya sebagai seorang programmer, ini hanyalah format lain untuk membuat serial dan menyajikan data, bersama dengan JSON, XML, XDR dan lainnya. Ini sangat umum dalam kehidupan kita sehari-hari, dan banyak orang menjumpainya: dalam seluler, telepon, komunikasi VoIP (UMTS, LTE, WiMAX, SS7, H.323), dalam protokol jaringan (LDAP, SNMP, Kerberos), dalam segala hal Mengenai kriptografi (X.509, CMS, standar PKCS), dalam kartu bank dan paspor biometrik, dan banyak lagi.
Artikel ini membahas
pustaka PyDERASN : Python ASN.1 yang digunakan secara aktif dalam proyek-proyek yang terkait dengan kriptografi di
Atlas .
Bahkan, tidak layak merekomendasikan ASN.1 untuk tugas kriptografi: ASN.1 dan codec-nya yang kompleks. Ini berarti bahwa kode tidak akan sederhana, tetapi selalu merupakan vektor serangan tambahan. Lihat saja
daftar kerentanan di pustaka ASN.1. Bruce Schneier, dalam
rekayasa Kriptografinya, juga tidak merekomendasikan penggunaan standar ini karena kerumitannya: "Pengkodean TLV yang paling terkenal adalah ASN.1, tetapi sangat rumit dan kami menghindarinya." Tetapi, sayangnya, saat ini kami memiliki
infrastruktur kunci publik di mana
sertifikat X.509 , CRL, OCSP, TSP, CMP,
CMC , pesan
CMS , dan banyak standar
PKCS digunakan secara aktif. Oleh karena itu, Anda harus dapat bekerja dengan ASN.1 jika Anda melakukan sesuatu yang berhubungan dengan kriptografi.
ASN.1 dapat dikodekan dalam berbagai cara / codec:
- BER (Aturan Pengkodean Dasar)
- CER (Aturan Penyandian Canonical)
- DER (Aturan Pengodean Terhormat)
- GSER (Aturan Penyandian String Generik)
- JER (JSON Encoding Rules)
- LWER (Aturan Pengodean Ringan)
- OER (Aturan Penyandian Oktet)
- PER (Aturan Pengkodean Terkemas)
- SER (Signaling Rules Encoding Rules)
- XER (Aturan Pengkodean XML)
dan sejumlah lainnya. Namun dalam tugas kriptografis dalam praktiknya, dua digunakan: BER dan DER. Bahkan dalam dokumen XML yang ditandatangani (
XMLDSig ,
XAdES ) masih akan ada objek ASN.1 DER yang dikodekan oleh Base64, serta dalam protokol
ACME berbasis JSON dari Let's Encrypt. Anda dapat lebih memahami semua codec ini dan prinsip pengkodean BER / CER / DER dalam artikel dan buku:
ASN.1 dengan kata-kata sederhana ,
ASN.1 - Komunikasi antara sistem heterogen oleh Olivier Dubuisson ,
ASN.1 Lengkap oleh Prof John Larmouth .
BER adalah format TLV yang berorientasi byte biner (misalnya, PER, populer dalam komunikasi seluler - berorientasi bit). Setiap elemen dikodekan dalam bentuk: tag (
T ag) yang mengidentifikasi jenis elemen yang dikodekan (integer, string, tanggal, dll.), Panjang (
L ength) dari konten dan konten itu sendiri (
V alue). BER opsional memungkinkan Anda untuk tidak menentukan nilai panjang dengan menetapkan nilai panjang tak terbatas khusus dan berakhir dengan pesan Akhir Oktet. Selain pengkodean panjang, BER memiliki banyak variabilitas dalam cara pengkodean tipe data, seperti:
- INTEGER, IDENTIFIER OBYEK, BIT STRING dan panjang elemen mungkin tidak dinormalisasi (tidak dikodekan dalam bentuk minimal);
- BOOLEAN berlaku untuk semua konten yang bukan nol;
- BIT STRING mungkin mengandung "ekstra" nol bit;
- BIT STRING, OCTET STRING, dan semua jenis string turunannya, termasuk tanggal / waktu, dapat dibagi menjadi beberapa bagian (panjang) dengan panjang variabel, panjang yang selama (de) pengkodean tidak diketahui sebelumnya;
- UTCTime / GeneralizedTime dapat memiliki metode berbeda untuk mengatur offset zona waktu dan "ekstra" nol fraksi detik;
- Nilai-nilai URUTAN DEFAULT mungkin dikodekan atau tidak;
- Nilai yang disebutkan dari bit terakhir dalam BIT STRING dapat secara opsional dikodekan;
- URUTAN (OF) / SET (OF) dapat memiliki urutan elemen yang sewenang-wenang.
Untuk semua hal di atas, tidak selalu mungkin untuk menyandikan data sehingga identik dengan bentuk aslinya. Oleh karena itu, bagian dari aturan diciptakan: DER adalah peraturan ketat dari hanya satu metode pengkodean yang valid, yang sangat penting untuk tugas kriptografi, di mana, misalnya, mengubah satu bit akan membatalkan tanda tangan atau checksum. DER memiliki kelemahan yang signifikan: panjang semua elemen harus diketahui sebelumnya selama penyandian, yang tidak memungkinkan aliran serialisasi data. Codec CER bebas dari kelemahan ini, juga menjamin penyajian data yang jelas. Sayangnya (atau untungnya, kami tidak memiliki decoder yang lebih kompleks?), Itu tidak menjadi populer. Oleh karena itu, dalam praktiknya, kami menemukan penggunaan data ber-enkode BER dan DER "campuran". Karena CER dan DER adalah subset dari BER, setiap decoder BER dapat memprosesnya.
Masalah dengan pyasn1
Di tempat kerja, kami menulis banyak program Python yang terkait dengan kriptografi. Dan beberapa tahun yang lalu praktis tidak ada pilihan perpustakaan gratis: baik ini perpustakaan tingkat sangat rendah yang memungkinkan Anda untuk menyandikan / mendekode, misalnya, bilangan bulat dan header struktur, atau ini adalah perpustakaan
pyasn1 . Kami hidup di sana selama beberapa tahun dan pada awalnya sangat puas, karena memungkinkan Anda untuk bekerja dengan struktur ASN.1 sebagai objek tingkat tinggi: misalnya, objek sertifikat X.509 yang didekodekan memungkinkan Anda untuk mengakses bidangnya melalui antarmuka kamus: cert ["tbsCertificate"] ["SerialNumber"] akan menunjukkan nomor seri sertifikat ini kepada kami. Demikian pula, Anda dapat "mengumpulkan" objek kompleks dengan bekerja dengannya seperti dengan daftar, kamus, dan kemudian cukup memanggil fungsi pyasn1.codec.der.encoder.encode dan mendapatkan representasi serial dokumen.
Namun, kelemahan, masalah dan keterbatasan terungkap. Ada dan, sayangnya, kesalahan masih tetap ada di pyasn1: pada saat penulisan, di pyasn1, salah satu tipe dasar, GeneralizedTime,
salah didekodekan dan disandikan.
Dalam proyek kami, untuk menghemat ruang, kami sering menyimpan hanya path ke file, offset dan panjang dalam byte dari objek yang ingin kita rujuk. Misalnya, file bertanda tangan sewenang-wenang kemungkinan besar akan terletak di struktur CMS SignedData ASN.1:
0 [1,3,1018] ContentInfo SEQUENCE 4 [1,1, 9] . contentType: ContentType OBJECT IDENTIFIER 1.2.840.113549.1.7.2 (id_signedData) 19-4 [0,0,1003] . content: [0] EXPLICIT [UNIV 16] ANY 19 [1,3, 999] . . DEFINED BY id_signedData: SignedData SEQUENCE 23 [1,1, 1] . . . version: CMSVersion INTEGER v3 (03) 26 [1,1, 19] . . . digestAlgorithms: DigestAlgorithmIdentifiers SET OF [...] 47 [1,3, 769] . . . encapContentInfo: EncapsulatedContentInfo SEQUENCE 51 [1,1, 8] . . . . eContentType: ContentType OBJECT IDENTIFIER 1.3.6.1.5.5.7.12.2 (id_cct_PKIData) 65-4 [1,3, 751] . . . . eContent: [0] EXPLICIT OCTET STRING 751 bytes OPTIONAL 751 820 [1,2, 199] . . . signerInfos: SignerInfos SET OF 823 [1,2, 196] . . . . 0: SignerInfo SEQUENCE 826 [1,1, 1] . . . . . version: CMSVersion INTEGER v3 (03) 829 [0,0, 22] . . . . . sid: SignerIdentifier CHOICE subjectKeyIdentifier [...] 956 [1,1, 64] . . . . . signature: SignatureValue OCTET STRING 64 bytes . . . . . . C1:B3:88:BA:F8:92:1C:E6:3E:41:9B:E0:D3:E9:AF:D8 . . . . . . 47:4A:8A:9D:94:5D:56:6B:F0:C1:20:38:D2:72:22:12 . . . . . . 9F:76:46:F6:51:5F:9A:8D:BF:D7:A6:9B:FD:C5:DA:D2 . . . . . . F3:6B:00:14:A4:9D:D7:B5:E1:A6:86:44:86:A7:E8:C9
dan kita bisa mendapatkan file yang ditandatangani asli dengan offset 65 byte, panjang 751 byte. pyasn1 tidak menyimpan informasi ini dalam objek yang diterjemahkan. Yang disebut TLVSeeker ditulis - perpustakaan kecil yang memungkinkan Anda untuk men-decode tag dan panjang objek, di antarmuka yang kami pesan "pergi ke tag berikutnya", "masuk ke dalam tag" (masuk ke dalam URUTAN objek), "buka tag berikutnya", "beri tahu Anda mengimbangi dan panjang objek di mana kita berada. " Itu adalah "manual" berjalan pada ASN.1 data DER-serial. Tetapi tidak mungkin untuk bekerja dengan data berseri-BER dengan cara ini, karena, misalnya, string byte OCTET STRING dapat dikodekan sebagai beberapa chunk-s.
Kelemahan lain untuk tugas pyasn1 kami adalah ketidakmampuan untuk memahami dari objek yang didekodekan apakah bidang yang diberikan hadir dalam URUTAN atau tidak. Misalnya, jika struktur berisi bidang URUTAN KETIGA Bidang OPSIONAL, maka bisa sepenuhnya tidak ada dalam data yang diterima (OPSIONAL), tetapi bisa hadir, tetapi pada saat yang sama menjadi panjang nol (daftar kosong). Dalam kasus umum, ini tidak dapat diklarifikasi. Dan ini diperlukan untuk pemeriksaan ketat keabsahan data yang diterima. Bayangkan bahwa beberapa otoritas sertifikasi akan mengeluarkan sertifikat dengan data yang "tidak sepenuhnya" valid dari sudut pandang skema ASN.1! Sebagai contoh, otoritas sertifikasi TÜRKTRUST Elektronik Sertifika Hizmet Sağlayıcısı dalam sertifikat rootnya melampaui batas
RFC 5280 yang diizinkan untuk panjang komponen subjek - tidak dapat secara jujur didekodekan sesuai dengan skema. Codec DER mensyaratkan bahwa bidang yang nilainya DEFAULT tidak dikodekan selama transmisi - dokumen tersebut ditemukan dalam kehidupan, dan versi pertama PyDERASN bahkan secara sadar mengizinkan perilaku yang tidak valid (dari sudut pandang DER) demi kompatibilitas ke belakang.
Keterbatasan lain adalah ketidakmampuan untuk dengan mudah menemukan dalam bentuk mana (BER / DER) satu atau objek lain dikodekan dalam struktur. Sebagai contoh, standar CMS mengatakan bahwa pesan dikodekan BER, tetapi bidang signedAttrs, di mana tanda tangan kriptografi terbentuk, harus dalam DER. Jika kita mendekode dengan DER, maka kita akan jatuh pada pemrosesan CMS itu sendiri, jika kita decode dengan BER, kita tidak akan tahu dalam bentuk apa yang ditandatangani Autodes. Akibatnya, TLVSeeker perlu (analog yang tidak ada dalam pyasn1) untuk mencari lokasi masing-masing bidang yang ditandatangani, dan harus diterjemahkan secara terpisah oleh DER dari tampilan serial.
Kemungkinan pemrosesan otomatis bidang DEFINED BY, yang sangat umum, sangat diinginkan bagi kami. Setelah mendekode struktur ASN.1, kami mungkin memiliki banyak bidang APA PUN yang tersisa, yang harus diproses lebih lanjut sesuai dengan skema yang dipilih berdasarkan OBJECT IDENTIFIER yang ditentukan dalam bidang struktur. Dalam kode Python, ini berarti menulis jika dan kemudian memanggil dekoder untuk bidang APA PUN.
Munculnya PyDERASN
Di Atlas, kami secara teratur, setelah menemukan masalah atau memodifikasi program gratis yang digunakan, mengirim tambalan ke atas. Di pyasn1, kami mengirim perbaikan beberapa kali, tetapi kode pyasn1 bukan yang termudah untuk dipahami, dan terkadang perubahan API yang tidak kompatibel terjadi di dalamnya, yang langsung menimpa kami. Plus, kita terbiasa menulis tes dengan pengujian generatif, yang tidak terjadi di pyasn1.
Suatu hari, saya memutuskan bahwa saya harus menanggung ini dan sudah waktunya untuk mencoba menulis perpustakaan saya sendiri dengan __slot __s, offset s, dan gumpalan yang ditampilkan dengan indah! Hanya membuat ASN.1 codec tidak akan cukup - Anda perlu mentransfer semua proyek dependen kami ke sana, dan ini adalah ratusan ribu baris kode di mana ada banyak pekerjaan dengan struktur ASN.1. Itu adalah salah satu persyaratan untuk itu: kemudahan terjemahan kode pyasn1 saat ini. Setelah menghabiskan seluruh liburan saya, saya menulis perpustakaan ini, memindahkan semua proyek ke sana. Karena mereka memiliki cakupan hampir 100% melalui tes, ini juga berarti perpustakaan itu sepenuhnya operasional.
PyDERASN, juga, memiliki cakupan tes hampir 100%. Pengujian generatif digunakan dengan pustaka
hipotesis yang luar biasa. Juga
mengelabui piringan pada 32 mesin nuklir juga dilakukan. Terlepas dari kenyataan bahwa kita hampir tidak memiliki kode Python2 yang tersisa, PyDERASN masih mempertahankan kompatibilitas dengannya dan karena ini ia memiliki
enam ketergantungan tunggal. Selain itu, ini diuji terhadap
ASN.1: 2008 test suite kepatuhan .
Prinsip bekerja dengannya mirip dengan pyasn1 - bekerja dengan objek Python tingkat tinggi. Deskripsi sirkuit ASN.1 serupa.
class TBSCertificate(Sequence): schema = ( ("version", Version(expl=tag_ctxc(0), default="v1")), ("serialNumber", CertificateSerialNumber()), ("signature", AlgorithmIdentifier()), ("issuer", Name()), ("validity", Validity()), ("subject", Name()), ("subjectPublicKeyInfo", SubjectPublicKeyInfo()), ("issuerUniqueID", UniqueIdentifier(impl=tag_ctxp(1), optional=True)), ("subjectUniqueID", UniqueIdentifier(impl=tag_ctxp(2), optional=True)), ("extensions", Extensions(expl=tag_ctxc(3), optional=True)), )
Namun, PyDERASN memiliki kemiripan dalam pengetikan yang kuat. Dalam pyasn1, jika bidangnya bertipe CMSVersion (INTEGER), maka itu bisa diberikan int atau INTEGER. PyDERASN secara ketat mensyaratkan bahwa objek yang ditugaskan tepat CMSVersion. Selain menulis kode Python3, kami menggunakan
anotasi pengetikan , sehingga fungsi kami tidak akan memiliki argumen yang tidak dapat dipahami seperti def func (serial, konten), tetapi def func (serial: CertificateSerialNumber, konten: EncapsulatedContentInfo), dan PyDERASN membantu melacak kode.
Pada saat yang sama, PyDERASN memiliki konsesi yang sangat nyaman untuk mengetik ini. pyasn1 tidak mengizinkan subtitle SubjectKeyIdentifier (). (implitTag = Tag (...)) untuk menetapkan objek SubjectKeyIdentifier () (tanpa TAG IMPLICIT yang diperlukan) dan sering harus menyalin dan membuat ulang objek hanya karena tag IMPLICIT / EXPLICIT yang diubah. PyDERASN secara ketat hanya mengamati tipe dasar - itu akan secara otomatis mengganti tag dari struktur ASN.1 yang ada. Ini sangat menyederhanakan kode aplikasi.
Jika kesalahan terjadi selama decoding, maka dalam pyasn1 tidak mudah untuk memahami persis di mana itu terjadi. Sebagai contoh, dalam sertifikat Turki yang telah disebutkan, kita mendapatkan kesalahan ini: UTF8String (tbsCertificate: penerbit: rdnSequence: 3: 0: value: DEFINED OLEH 2.5.4.10:utf8String) (at 138) batas tidak puas: 1 ⇐ 77 ⇐ 64 Saat menulis ASN .1 struktur orang dapat membuat kesalahan, dan itu membantu lebih mudah untuk men-debug aplikasi atau menemukan masalah dokumen yang disandikan dari sisi yang berlawanan.
Versi pertama PyDERASN tidak mendukung pengkodean BER. Itu muncul jauh kemudian dan pemrosesan UTCTime / GeneralizedTime dengan zona waktu masih tidak didukung. Ini akan datang di masa depan, karena proyek ini ditulis terutama di waktu luang.
Juga di versi pertama tidak ada pekerjaan dengan bidang DEFINED BY. Beberapa bulan kemudian
kesempatan ini
muncul dan mulai digunakan secara aktif, secara signifikan mengurangi kode aplikasi - dalam satu operasi decoding, dimungkinkan untuk membuat seluruh struktur dibongkar sampai sangat dalam. Untuk melakukan ini, dalam skema, bidang mana yang didefinisikan yang "menentukan". Misalnya, deskripsi skema CMS:
class ContentInfo(Sequence): schema = ( ("contentType", ContentType(defines=((("content",), { id_authenticatedData: AuthenticatedData(), id_digestedData: DigestedData(), id_encryptedData: EncryptedData(), id_envelopedData: EnvelopedData(), id_signedData: SignedData(), }),))), ("content", Any(expl=tag_ctxc(0))), )
mengatakan bahwa jika contentType berisi OID dengan id_signedData, maka bidang konten (terletak di SEQUENCE yang sama) perlu diterjemahkan dengan menggunakan skema SignedData. Mengapa ada begitu banyak tanda kurung? Bidang dapat "mendefinisikan" beberapa bidang sekaligus, seperti halnya dalam struktur EnvelopedData. Bidang yang ditentukan diidentifikasi oleh yang disebut jalur decode - ini menetapkan lokasi yang tepat dari setiap elemen di semua struktur.
Tidak selalu diinginkan atau tidak selalu memungkinkan untuk segera memperkenalkan definisi ini ke dalam rangkaian. Mungkin ada kasus khusus aplikasi di mana OID dan struktur hanya diketahui dalam proyek pihak ketiga. PyDERASN menyediakan kemampuan untuk menentukan definisi ini tepat pada saat mendekode struktur:
ContentInfo().decode(data, ctx={"defines_by_path": (( ( "content", DecodePathDefBy(id_signedData), "certificates", any, "certificate", "tbsCertificate", "extensions", any, "extnID", ), ((("extnValue",), { id_ce_authorityKeyIdentifier: AuthorityKeyIdentifier(), id_ce_basicConstraints: BasicConstraints(), [...] id_ru_subjectSignTool: SubjectSignTool(), }),), ),)})
Di sini kita mengatakan bahwa dalam CMS SignedData untuk semua sertifikat yang dilampirkan, dekode semua ekstensi mereka (AuthorityKeyIdentifier, BasicConstraints, SubjectSignTool, dll.). Kami menunjukkan melalui jalur decode yang elemen untuk "menggantikan" mendefinisikan, seolah-olah itu didefinisikan dalam rangkaian.
Akhirnya, PyDERASN memiliki kemampuan untuk bekerja dari
baris perintah untuk memecahkan kode file ASN.1 dan memiliki
pencetakan yang cukup kaya. Anda dapat mendekodekan ASN.1 yang sewenang-wenang, atau Anda dapat menentukan skema yang jelas dan melihat sesuatu seperti ini:

Informasi yang ditampilkan: offset objek, panjang tag, panjang tag, panjang konten, keberadaan EOC (akhir oktet), bendera enkode BER, bendera enkode panjang tidak terbatas, panjang tag EXPLICIT dan offset (jika ada), kedalaman objek bersarang di struktur, nilai tag IMPLICIT / EXPLICIT, nama objek sesuai dengan skema, tipe ASN.1 dasarnya, nomor seri di dalam URUTAN / SET, nilai PILIHAN (jika ada), nama yang dapat dibaca manusia INTEGER / ENUMERATED / BIT STRING sesuai dengan skema, nilai dari semua tipe dasar , Bendera DEFAULT / OPTIONAL dari sirkuit, tanda bahwa objek secara otomatis diterjemahkan sebagai DITETAPKAN oleh dan setelah gm dari OID-dan itu terjadi, chelovekochitaemy OID.
Sistem pencetakan cantik dibuat khusus sehingga menghasilkan urutan objek PP yang sudah divisualisasikan dengan cara yang terpisah. Tangkapan layar memperlihatkan renderer dalam teks berwarna polos. Ada renderers dalam format JSON / HTML sehingga ini dapat dilihat dengan menyoroti di browser ASN.1 seperti dalam proyek
asn1js .
Perpustakaan lain
Ini bukan tujuan, tetapi PyDERASN secara signifikan
lebih cepat daripada pyasn1. Misalnya, mendekode file CRL dengan ukuran megabyte dapat berlangsung sangat lama sehingga Anda harus memikirkan format perantara untuk menyimpan data (cepat) dan mengubah arsitektur aplikasi. pyasn1 menerjemahkan CRL
CACert.org di laptop saya selama lebih dari 20 menit, sementara PyDERASN hanya dalam 28 detik! Ada proyek
asn1crypto yang bertujuan untuk bekerja dengan cepat dengan struktur kriptografi: ia menerjemahkan (sepenuhnya, tidak malas) CRL yang sama dalam 29 detik, tetapi mengkonsumsi RAM hampir dua kali lebih banyak ketika berjalan di bawah Python3 (983 MiB versus 498), dan 3,5 kali di bawah Python2 (1677 melawan 488), sementara pyasn1 mengkonsumsi sebanyak 4,3 kali lebih banyak (2093 melawan 488).
asn1crypto, yang saya sebutkan, kami tidak mempertimbangkan, karena proyek ini masih dalam masa pertumbuhan, dan kami belum pernah mendengarnya. Sekarang mereka tidak akan mulai melihat ke arahnya juga, karena saya segera menemukan bahwa GeneralizedTime yang sama tidak mengambil tampilan sewenang-wenang, dan ketika serialisasi, diam-diam menghilangkan sebagian kecil dari satu detik. Ini dapat diterima untuk bekerja dengan sertifikat X.509, tetapi secara umum itu tidak akan berfungsi.
Saat ini, PyDERASN adalah yang paling ketat dari dekoder Python / Go DER gratis yang saya tahu. Di perpustakaan pengkodean / asn1 Go favorit saya, tidak
ada pemeriksaan ketat pada OBJECT IDENTIFIER dan string UTCTime / GeneralizedTime. Kadang-kadang keketatan dapat mengganggu (pertama-tama, karena kompatibilitas ke belakang dengan aplikasi lama yang tidak akan diperbaiki oleh siapa pun), sehingga dalam PyDERASN selama decoding Anda dapat melewati
berbagai pengaturan yang melemahkan pemeriksaan.
Kode proyek berusaha sesederhana mungkin. Seluruh perpustakaan adalah satu file. Kode ini ditulis dengan penekanan pada kemudahan pemahaman, tanpa kinerja yang tidak perlu dan optimasi kode KERING. Itu tidak, seperti yang sudah saya katakan, mendukung decoding BER penuh dari string UTCTime / GeneralizedTime, serta REAL, OEL RELATIF, EKSTERNAL, MASING-MASING, EMBEDDED PDV, CHARACTER STRING tipe data. Dalam semua kasus lain, secara pribadi, saya tidak melihat alasan untuk menggunakan perpustakaan lain dengan Python.
Seperti semua proyek saya, seperti
PyGOST ,
GoGOST ,
NNCP ,
GoVPN , PyDERASN benar-benar
perangkat lunak gratis yang didistribusikan di bawah ketentuan
LGPLv3 + , dan tersedia untuk diunduh gratis. Contoh penggunaan ada di
sini dalam
tes PyGOST .
Sergey Matveev ,
bank sandi , anggota
Yayasan Yayasan Masyarakat Terbuka , Pengembang Python / Go, kepala spesialis
FSUE "Pusat Ilmiah dan Teknis" Atlas " .