PyDERASN: cumu aghju scrittu una biblioteca ASN.1 cù slots è blobs

ASN.1 Questu hè un standard (ISO, ITU-T, GOST) di una lingua chì descrive l'infurmazioni strutturate, è e regule per codificà sta informazione. Per mè, cum'è programatore, questu hè solu un altru furmatu per serializà è presentà dati, cù JSON, XML, XDR è altri. Hè estremamente cumuni in a nostra vita di ogni ghjornu, è parechje persone l'incontranu: in cellulari, telefoni, cumunicazioni VoIP (UMTS, LTE, WiMAX, SS7, H.323), in protokolli di rete (LDAP, SNMP, Kerberos), in tuttu ciò chì cuncerna a criptografia (X.509, CMS, standard PKCS), in carte bancarie è passaporti biometrici, è assai più.

Stu articulu tratta PyDERASN: A biblioteca Python ASN.1 hè attivamente utilizata in prughjetti ligati à a criptografia in Atlas.

PyDERASN: cumu aghju scrittu una biblioteca ASN.1 cù slots è blobs
In generale, ASN.1 ùn vale a pena ricumandemu per i travaglii criptografici: ASN.1 è i so codecs sò cumplessi. Questu significa chì u codice ùn serà micca simplice, è questu hè sempre un vettore d'attaccu extra. Basta à vede à a lista vulnerabilità in librerie ASN.1. Bruce Schneier in u so Ingegneria di criptografia Cunsigliu ancu di usà stu standard per via di a so cumplessità: "A codificazione TLV più famosa hè ASN.1, ma hè incredibilmente cumplessa è ne sguassemu". Ma, sfurtunatamenti, oghje avemu infrastrutture à chjave publica in quale sò attivamente utilizati certificati X.509, CRL, OCSP, TSP, protokolli CMP, CMC, missaghji CMS, è assai standard PKCS. Per quessa, avete da esse capace di travaglià cù ASN.1 sè vo fate qualcosa ligata à a criptografia.

ASN.1 pò esse codificata in una varietà di modi / codecs:

  • BER (Reguli di codificazione basica)
  • CER (Regula di codificazione canonica)
  • DER (Reguli di codificazione distinti)
  • GSER (Regula di codificazione di stringa generica)
  • JER (Reguli di codificazione JSON)
  • LWER (Regula di codificazione di pesu ligeru)
  • OER (Reguli di codificazione di l'octettu)
  • PER (Reguli di codificazione imballati)
  • SER (Règuli di codificazione specifichi di signalazione)
  • DISCIPOLI (Reguli di codificazione XML)

è parechji altri. Ma in i travaglii criptografici, in pratica, sò usati dui: BER è DER. Ancu in documenti XML firmati (XMLDSig, XAdES) ci saranu sempre oggetti ASN.64 DER codificati in Base1, cum'è in u protocolu orientatu à JSON. acme da Let's Encrypt. Pudete capisce megliu tutti questi codecs è principii di codificazione BER / CER / DER in articuli è libri: ASN.1 in parolle simplici, ASN.1 — A cumunicazione trà sistemi eterogenei di Olivier Dubuisson, ASN.1 Complete by Prof John Larmouth.

BER hè un formatu TLV binari orientatu à byte (per esempiu PER, populari in cumunicazioni cellulari - bit-oriented). Ogni elementu hè codificatu cum'è: tag (Tag), identificendu u tippu di l'elementu da codificà (integer, string, data, etc.), length (Length) cuntenutu è u cuntenutu stessu (Valu). BER optionally permette di ùn specificà micca un valore di lunghezza per stabilisce un valore speciale di lunghezza indefinita è finisce u messagiu End-Octets cù una marca End-Octets. In più di a codificazione di lunghezza, BER hà assai variabilità in a manera di codifica tipi di dati, cum'è:

  • INTEGER, OBJECT IDENTIFIER, BIT STRING è a lunghezza di l'elementu ùn ponu esse nurmalizzati (micca codificati in forma minima);
  • BOOLEAN hè veru per qualsiasi cuntenutu micca zero;
  • BIT STRING pò cuntene "extra" zero bits;
  • BIT STRING, OCTET STRING è tutti i so tipi di stringa derivati, cumprese a data / l'ora, ponu esse spartuti in pezzi di lunghezza variabile, a durata di quale ùn hè micca cunnisciuta in anticipu à u mumentu di a (de)codificazione;
  • UTCTime / GeneralizedTime pò avè diverse manere di specificà l'offset di u fusu orariu è "extra" zero frazioni di seconde;
  • I valori DEFAULT SEQUENCE ponu esse codificati o micca;
  • I valori chjamati di l'ultimi bits in una BIT STRING ponu opzionalmente esse micca codificati;
  • SEQUENCE (OF) / SET (OF) pò avè ogni ordine di elementi.

Per via di tuttu ciò chì sopra, a codificazione di dati in modu chì hè identica à a forma originale ùn hè micca sempre pussibule. Per quessa, hè statu inventatu un sottumessu di regule: DER - rigulamentu strettu solu un metudu di codificazione validu, chì hè criticu per i travaglii criptografici induve, per esempiu, cambià un bit rende a firma o checksum invalidu. DER hà un svantaghju significativu: e lunghezze di tutti l'elementi devenu esse cunnisciute in anticipu à u tempu di codificazione, chì ùn permettenu micca a serializazione di flussu di e dati. U codec CER ùn hà micca stu inconveniente, chì guarantisci ancu una rapprisintazioni senza ambiguità di dati. Sfurtunatamente (o hè furtunatu chì ùn avemu micca ancu decodificatori più cumplessi?), ùn hè micca diventatu populari. Per quessa, in pratica avemu scontru un usu "mistu" di dati codificati BER è DER. Siccomu CER è DER sò un subset di BER, qualsiasi decodificatore BER pò trattà.

Prublemi cù pyasn1

À u travagliu scrivimu assai prugrammi Python ligati à a criptografia. È uni pochi d'anni fà ùn ci era praticamente micca scelta di biblioteche libere: sia queste sò biblioteche di livellu assai bassu chì permettenu di codificà / decodificà solu, per esempiu, un integeru è un capu di struttura, o sta biblioteca. piasn1. Avemu campatu annantu à questu per parechji anni è prima eramu assai cuntenti, postu chì vi permette di travaglià cù strutture ASN.1 cum'è cù l'uggetti d'altu livellu: per esempiu, un oggettu di certificatu X.509 decodificatu permette di accede à i so campi attraversu. una interfaccia di dizziunariu: cert["tbsCertificate"] ["serialNumber"] ci mostrarà u numeru di serie di stu certificatu. In listessu modu, pudete "assemble" l'uggetti cumplessi travagliendu cun elli cum'è listi, dizziunari, è dopu chjamate a funzione pyasn1.codec.der.encoder.encode è uttene una rapprisintazioni serializzata di u documentu.

Tuttavia, mancanze, prublemi è limitazioni sò stati rivelati. Ci era è, sfurtunatamenti, ci sò sempre errori in pyasn1: à u mumentu di a scrittura, unu di i tipi basi in pyasn1 hè GeneralizedTime, incorrecte decodificati e codificati.

In i nostri prughjetti, per salvà u spaziu, spessu guardamu solu u percorsu di u schedariu, l'offset è a lunghezza in bytes di l'ughjettu chì vulemu riferite. Per esempiu, un schedariu firmatu arbitrariu serà probabilmente situatu in a struttura 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

è pudemu avè u schedariu originale firmatu à offset 65 bytes, 751 bytes long. pyasn1 ùn guarda micca sta informazione in i so oggetti decodificati. U chjamatu TLVSeeker hè statu scrittu - una piccula biblioteca chì vi permette di decodificà tag è lunghezze di l'ughjettu, in l'interfaccia di quale avemu urdinatu "andà à u prossimu tag", "andà in u tag" (andà in l'ughjettu SEQUENCE). "Vai à a prossima tag", "Dì u vostru offset è a durata di l'ughjettu induve simu". Questu era un passaghju "manuale" attraversu i dati serializzati ASN.1 DER. Ma era impussibile di travaglià cù dati seriali BER in questu modu, postu chì, per esempiu, a stringa di byte OCTET STRING puderia esse codificata in forma di parechji pezzi.

Un altru inconveniente per i nostri compiti pyasn1 hè l'incapacità di capisce da l'uggetti decodificati se un campu determinatu era presente in a SEQUENZA o micca. Per esempiu, s'è a struttura cuntene un campu SEQUENCE OF Smth OPTIONAL campu, allura si pudia esse cumpletamenti assenti da i dati entrata (OPTIONAL), o si pudia esse prisente, ma esse di lunghezza zero (lista viotu). In generale, questu ùn pò micca esse determinatu. È questu hè necessariu per una verificazione stretta di a validità di i dati ricevuti. Imagine chì una certa autorità di certificazione emette un certificatu cù dati "micca cumpletamente" validi da u puntu di vista di i schemi ASN.1! Per esempiu, l'autorità di certificazione "TÜRKTRUST Elektronik Sertifika Hizmet Sağlayıcısı" hà superatu i limiti permessi in u so certificatu radice. RFC 5280 limiti nantu à a durata di u cumpunente di u sughjettu - ùn pò micca esse decoded onestamente secondu u schema. U codec DER richiede chì un campu chì u valore hè uguale à DEFAULT ùn hè micca codificatu durante a trasmissione - tali documenti si verificanu in a vita, è a prima versione di PyDERASN hà ancu permessu deliberatamente tali comportamenti invalidi (da u puntu di vista DER) per u scopu di cumpatibilità retrocede.

Un'altra limitazione hè l'incapacità di scopre facilmente in quale forma (BER / DER) un ughjettu particulari hè statu codificatu in a struttura. Per esempiu, u standard CMS dice chì u missaghju hè codificatu BER, ma u campu signedAttrs, nantu à quale a firma criptografica hè generata, deve esse in DER. Se decodificamu cù DER, falleremu in u processu di u CMS stessu; se decodemu cù BER, ùn sapemu micca in quale forma era u signedAttrs. In u risultatu, TLVSeeker (chì ùn hà micca analogu in pyasn1) duverà circà u locu di ognunu di i campi signedAttrs, è separatamente, pigliendu da a rapprisintazioni serializzata, decode cù DER.

A capacità di processà automaticamente i campi DEFINED BY, chì si trovanu assai spessu, era assai desideratu per noi. Dopu à decodificà a struttura ASN.1, pudemu esse lassatu cù assai campi ANY chì deve esse trattatu più secondu u schema sceltu basatu annantu à l'IDENTIFICATORE OBJECT specifiatu in u campu di a struttura. In u codice Python, questu significa scrive se è dopu chjamà u decodificatore per ANY field.

L'emergenza di PyDERASN

Atlas, mandemu regularmente patches in cima quandu truvamu qualchi prublemi o migliurà i prugrammi gratuiti chì usemu. Avemu sottumessu migliure à pyasn1 parechje volte, ma u codice di pyasn1 ùn hè micca u più faciule da capisce è qualchì volta ci sò stati cambiamenti incompatibili di l'API chì ci anu battutu. In più, simu abituati à scrive testi cù teste generative, chì ùn era micca u casu in pyasn1.

Un bellu ghjornu aghju decisu chì ne avia abbastanza di questu è era ora di pruvà à scrive a mo propria biblioteca cù __slot__s, offsets è blobs belli visualizati! Simply crià un codec ASN.1 ùn saria micca abbastanza - avemu bisognu di trasfiriri tutti i nostri prughjetti dipindenti à questu, è questi sò centinaie di millaie di linee di codice chì sò pieni di travagliu cù strutture ASN.1. Questu hè, unu di i requisiti per questu: facilità di traduzzione di u codice pyasn1 attuale. Dopu avè passatu tutte e mo vacanze, aghju scrittu sta biblioteca è trasfirìu tutti i prughjetti. Siccomu anu una copertura quasi 100% cù e teste, questu significava chì a biblioteca era cumplettamente operativa.

PyDERASN, simile, hà una copertura di prova quasi 100%. Utilizà a prova generativa cù una grande biblioteca ipotesi. Hè statu ancu realizatu fuzzing py-afl- Mangiu nantu à 32 macchine nucleari. Malgradu u fattu chì ùn avemu praticamente micca codice Python2, PyDERASN mantene sempre a cumpatibilità cù questu è per quessa hà u solu solu. sei addiction. Inoltre, hè pruvatu contru ASN.1: 2008 suite di test di conformità.

U principiu di travaglià cun ellu hè simile à pyasn1 - travagliendu cù l'uggetti Python d'altu livellu. A descrizzione di schemi ASN.1 hè simile.

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)),
    )

In ogni casu, PyDERASN hà una certa apparenza di typing forte. In pyasn1, se un campu era di tipu CMSVersion (INTEGER), allora puderia esse assignatu int o INTEGER. PyDERASN richiede strettamente chì l'ughjettu assignatu sia esattamente CMSVersion. In più di scrive u codice Python3, avemu ancu aduprà annotazioni di scrittura, perchè e nostre funzioni ùn anu micca argumenti oscuri cum'è def func (seriale, cuntenutu), ma def func (seriale: CertificateSerialNumber, cuntenutu: EncapsulatedContentInfo), è PyDERASN aiuta à mantene tali codice.

À u listessu tempu, PyDERASN hà cuncessioni estremamente convenienti à questu assai typing. pyasn1 ùn hà micca permessu à u campu SubjectKeyIdentifier().subtype(implicitTag=Tag (...)) per assignà un ughjettu à u SubjectKeyIdentifier() (senza u TAG IMPLICIT necessariu) è era necessariu spessu copià è ricreate l'ogetti solu per via di i tags IMPLICIT/EXPLICIT cambiati. PyDERASN osserva strettamente solu u tippu di basa - sustituverà automaticamente tag da u schema ASN.1 esistente di a struttura. Questu simplifica assai u codice di l'applicazione.

Se un errore si trova durante a decodificazione, allora in pyasn1 ùn hè micca faciule per capiscenu esattamente induve hè accadutu. Per esempiu, in u certificatu turcu digià citatu sopra, riceveremu l'errore seguente: UTF8String (tbsCertificate:issuer:rdnSequence:3:0:value:DEFINED BY 2.5.4.10:utf8String) (à 138) limiti insatisfatti: 1 ⇐ 77 ⇐ 64 Quandu scrivite strutturi ASN .1 a ghjente pò fà sbagli, è questu rende più faciule per debug applicazioni o scuprite prublemi cù i ducumenti codificati di l'altru partitu.

A prima versione di PyDERASN ùn sustene micca a codificazione BER. Hè apparsu assai più tardi è ùn sustene micca u trattamentu UTCTime / GeneralizedTime cù i fusi orari. Questu vene in u futuru, perchè u prugettu hè scrittu principarmenti in u mo tempu liberu.

Inoltre, in a prima versione ùn ci era micca travagliu cù i campi DEFINED BY. Uni pochi mesi dopu questu l'occasione hè ghjunta è hà cuminciatu à esse usatu attivamente, riducendu significativamente u codice di l'applicazione - in una operazione di decodificazione era pussibule di ottene tutta a struttura disassemblata à a prufundità assai. Per fà questu, u schema specifica quale campi "definisce" ciò chì. Per esempiu, una descrizzione di u schema 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))),
    )

dice chì se u contentType cuntene un OID cù u valore id_signedData, allura u campu di cuntenutu (situatu in a listessa SEQUENCE) deve esse decoded secondu u schema SignedData. Perchè ci sò tanti parentesi ? Un campu pò "definisce" parechji campi à u stessu tempu, cum'è u casu in strutture EnvelopedData. I campi definiti sò identificati da a chjamata strada di decode - specifica u locu esatta di ogni elementu in tutte e strutture.

Ùn vulete micca sempre o ùn avete micca sempre l'uppurtunità di aghjunghje immediatamente questi definiti à u diagramma. Ci ponu esse casi specifichi di l'applicazione quandu l'OID è e strutture sò cunnisciuti solu in un prughjettu di terzu. PyDERASN furnisce a capacità di stabilisce queste definizioni ghjustu à u mumentu di decodificà a struttura:

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(),
    }),),
),)})

Quì dicemu chì in CMS SignedData per tutti i certificati attaccati, decode tutte e so estensioni (AuthorityKeyIdentifier, BasicConstraints, SubjectSignTool, etc.). Indichemu à traversu u percorsu di decode quale elementu deve esse "sustituitu" cù definisce, cum'è s'ellu era specificatu in u schema.

Infine, PyDERASN hà a capacità di scappà da linea di cummandu per decodificà i schedari ASN.1 è hà riccu bella stampa. Pudete decode un ASN.1 arbitrariu, o pudete specificà un schema chjaramente definitu è ​​vede qualcosa cum'è questu:

PyDERASN: cumu aghju scrittu una biblioteca ASN.1 cù slots è blobs

Infurmazioni affissate: offset di l'ughjettu, lunghezza di tag, lunghezza di lunghezza, lunghezza di cuntenutu, presenza di EOC (end-of-octets), attributu di codificazione BER, attributu di codifica di lunghezza indefinita, lunghezza è offset di tag EXPLICIT (se ci hè), prufundità di nidificazione di l'ughjettu in strutture, valore di tag IMPLICIT / EXPLICIT, nome di l'ughjettu secondu u schema, u so tipu ASN.1 di basa, numeru di sequenza in SEQUENCE / SET OF, CHOICE value (s'ellu ci hè), nome leggibile da l'omu INTEGER / ENUMERATED / BIT STRING sicondu u schema, valore di ogni tipu di basa , bandiera DEFAULT / OPTIONAL da u schema, un segnu chì l'ughjettu hè statu automaticamente decoded cum'è DEFINED BY è per via di quale OID hè accadutu, OID leggibile da l'omu.

U bellu sistema di stampa hè cuncepitu apposta in modu chì genera una sequenza d'oggetti PP chì sò visualizati cù arnesi separati. A screenshot mostra u renderer in un testu simplice di culore. Ci hè ancu rendering in u formatu JSON / HTML, perchè pò esse vistu cù evidenziazione in u navigatore ASN.1, cum'è in asn1js prughjettu.

Altre biblioteche

Questu ùn era micca u scopu, ma PyDERASN hè stata significativamente più veloce chè pyasn1. Per esempiu, decodificà i fugliali CRL di dimensioni megabyte pò piglià tantu tempu chì avete da pensà à i formati di almacenamiento di dati intermedi (rapidu) è cambià l'architettura di l'applicazione. pyasn1 decodifica CRL CACert.org nantu à u mo laptop dura più di 20 minuti, mentre chì PyDERASN dura solu 28 seconde! Ci hè un prughjettu asn1crypto, destinatu à u travagliu veloce cù strutture criptografiche: decodifica (completamente, micca pigramente) u stessu CRL in 29 seconde, ma cunsuma quasi duie volte di RAM quandu corre sottu Python3 (983 MiB versus 498), è in 3.5 volte sottu Python2 (1677). versus 488), mentri pyasn1 cunsuma quant'è 4.3 volte più (2093 versus 488).

Ùn avemu micca cunsideratu asn1crypto, chì aghju citatu, perchè u prughjettu era ancu in a so infanzia è ùn avemu micca intesu parlà. Avà ùn avemu micca fighjà ancu in a so direzzione, postu chì aghju scupertu subitu chì u stessu GeneralizedTime ùn piglia micca una forma arbitraria, è durante a serializazione elimina in silenziu una frazzioni di seconda. Questu hè accettatu per travaglià cù certificati X.509, ma in generale ùn hà micca travagliatu.

À u mumentu, PyDERASN hè u decodificatore Python / Go DER più strettu chì cunnoscu. In a biblioteca di codificazione / asn1 di u mo amatu Go micca un cuntrollu strettu OBJECT IDENTIFIER e stringhe UTCTime/GeneralizedTime. A volte, a strettezza pò mette in u modu (principalmente per via di a cumpatibilità inversa cù l'applicazioni più vechje chì nimu ùn risolverà), cusì PyDERASN pò passà. diverse paràmetri cuntrolli indebuli.

U codice di u prugettu prova à esse simplicità quant'è pussibule. Tutta a biblioteca hè un schedariu. U codice hè scrittu cù un enfasi in a facilità di capiscenu, senza ottimisazioni di rendiment innecessarii è codice DRY. Ùn hè micca, cum'è l'aghju digià dettu, sustene a decodificazione BER cumpleta di e stringhe UTCTime / GeneralizedTime, è ancu i tipi di dati REAL, RELATIVE OID, EXTERNAL, INSTANCE OF, EMBEDDED PDV, CHARACTER STRING. In tutti l'altri casi, personalmente ùn vecu micca u puntu di utilizà altre biblioteche in Python.

Cum'è tutti i mo prughjetti, cum'è PyGOST, GoGOST, NCCP, GoVPN, PyDERASN hè cumpletamente software gratuitu, distribuitu sottu i termini LGPLv3+, è hè dispunibule per scaricamentu gratuitu. Ci sò esempi di usu ccà è in Testi PyGOST.

Sergey Matveev, cypherpunk, membru Fundazione SPO, sviluppatore Python/Go, specialista capu FSUE "STC "Atlas".

Source: www.habr.com

Add a comment