Detta Àr en standard (ISO, ITU-T, GOST) för ett sprÄk som beskriver strukturerad information samt regler för kodning av denna information. För mig som programmerare Àr det bara Ànnu ett format för serialisering och presentation av data, pÄ samma nivÄ som JSON, XML, XDR och andra. Det Àr mycket vanligt i vÄrt dagliga liv och mÄnga stöter pÄ det i: mobil, telefon- och VoIP-kommunikation (UMTS, LTE, WiMAX, SS7, H.323), i nÀtverksprotokoll (LDAP, SNMP, Kerberos), i allt som rör kryptografi (X.509, CMS, PKCS-standarder), pÄ bankkort och biometriska pass, och pÄ mÄnga andra stÀllen.
I den hÀr artikeln diskteras : Python ASN.1-bibliotek som aktivt anvÀnds i projekt relaterade till kryptografi i .

I allmÀnhet Àr det inte vÀrt att rekommendera ASN.1 för kryptografiska uppgifter: ASN.1 och dess kodare Àr komplexa. Det innebÀr att koden inte kommer att vara enkel, och det Àr alltid en extra vektor för attacker. Det rÀcker att titta över sÄrbarheter i ASN.1-bibliotek. Bruce Schneier i sin rekommenderar inte heller att anvÀnda denna standard pÄ grund av dess komplexitet: "The best-known TLV encoding is ASN.1, but it is incredibly complex and we shy away from it." Men tyvÀrr har vi idag dÀr , CRL, OCSP, TSP, CMP-protokoll, , meddelanden , och en mÀngd standarder . DÀrför mÄste man kunna arbeta med ASN.1 om man sysslar med nÄgot relaterat till kryptografi.
ASN.1 kan kodas pÄ mÄnga sÀtt/kodare:
- (Basic Encoding Rules)
- (Canonical Encoding Rules)
- (Distinguished Encoding Rules)
- (Generic String Encoding Rules)
- (JSON Encoding Rules)
- LWER (Light Weight Encoding Rules)
- (Octet Encoding Rules)
- (Packed Encoding Rules)
- SER (Signalling specific Encoding Rules)
- (XML Encoding Rules)
och flera andra. Men i kryptografiska uppgifter anvĂ€nds i praktiken tvĂ„: BER och DER. Ăven i signerade XML-dokument (, ) kommer det fortfarande att finnas Base64-kodade ASN.1 DER-objekt, precis som i det JSON-orienterade protokollet frĂ„n Letâs Encrypt. Det Ă€r bĂ€ttre att förstĂ„ alla dessa kodare och kodningsprinciper BER/CER/DER i artiklar och böcker: , , .
BER Ă€r ett binĂ€rt byte-orienterat (till exempel PER, populĂ€rt inom mobilkommunikation â bit-orienterat) TLV-format. Varje element kodas som: en tagg (Tag), som identifierar typen av det kodade elementet (heltal, strĂ€ng, datum, osv.), lĂ€ngden (Length) av innehĂ„llet och sjĂ€lva innehĂ„llet (VValue). BER valfritt tillĂ„ter att lĂ€ngdvĂ€rde inte anges, genom att anvĂ€nda ett speciellt odefinierat lĂ€ngdvĂ€rde och avsluta med End-Of-Octets-mĂ€rkningen. Förutom lĂ€ngdkodning finns det mycket variation i sĂ€ttet att koda datatyper, som exempelvis:
- INTEGER, OBJECT IDENTIFIER, BIT STRING och elementets lÀngd kan vara onormerade (inte kodade i minimala formen);
- BOOLEAN Àr sann för varje icke-noll innehÄll;
- BIT STRING kan innehÄlla "överskotts" nollor;
- BIT STRING, OCTET STRING och alla deras avledda strÀngtyper, inklusive datum/tid, kan delas upp i bitar (chunk) av variabel lÀngd, vars lÀngd inte Àr kÀnd i förvÀg under (de)kodning;
- UTCTime/GeneralizedTime kan ha olika metoder för att ange tidszonsförskjutning och "överskotts" nollsekunder;
- DEFAULT-vÀrden för SEQUENCE kan kodas, men behöver inte alltid göras;
- Namngivna vÀrden för de sista bitarna i BIT STRING kan valfritt lÀmnas okodade;
- SEQUENCE (OF)/SET (OF) kan ha valfri ordning pÄ elementen.
PĂ„ grund av allt detta Ă€r det inte alltid möjligt att koda data sĂ„ att de Ă€r identiska med originalformen. DĂ€rför skapades en delmĂ€ngd av regler: DER â som strikt reglerar en enda tillĂ„ten sĂ€tt att koda, vilket Ă€r kritiskt för kryptografiska uppgifter, dĂ€r, till exempel, en Ă€ndring av en bit gör signaturen eller kontrollsumman ogiltig. DER har en vĂ€sentlig nackdel: lĂ€ngden pĂ„ alla element mĂ„ste vara kĂ€nd i förvĂ€g under kodning, vilket hindrar strömmande serialisering av data. CER-kodningen saknar denna nackdel och garanterar pĂ„ liknande sĂ€tt en entydig representation av data. TyvĂ€rr (eller lyckligtvis sĂ„ att vi inte har Ă€nnu mer komplexa dekodare?) har den inte blivit populĂ€r. DĂ€rför stöter vi i praktiken pĂ„ en "blandad" anvĂ€ndning av BER och DER-kodade data. Eftersom bĂ„de CER och DER Ă€r delmĂ€ngder av BER kan vilken BER-dekodare som helst hantera dem.
Problem med pyasn1
PÄ jobbet skriver vi mÄnga program i Python relaterade till kryptografi. För flera Är sedan fanns det nÀstan inga fria bibliotek att vÀlja pÄ: antingen var det mycket lÄgnivÄbibliotek som bara tillÄter att koda/dekoda, till exempel ett heltal och strukturell header, eller sÄ var det biblioteket . Vi bodde pÄ den i flera Är och var initialt mycket nöjda, eftersom den gör det möjligt att arbeta med ASN.1-strukturer som med högre ordningens objekt: till exempel kan det avkodade objektet X.509-certifikatets fÀlt nÄs genom ordboksgrÀnssnittet: cert[«tbsCertificate»][«serialNumber»] visar serienumret för detta certifikat. PÄ liknande sÀtt kan komplexa objekt "sammansÀttas" genom att arbeta med dem som listor och ordböcker, för att sedan bara kalla funktionen pyasn1.codec.der.encoder.encode och fÄ den serialiserade representationen av dokumentet.
Men nackdelar, problem och begrĂ€nsningar uppdagades. I pyasn1 fanns det och tyvĂ€rr finns det fortfarande fel: vid tidpunkten för artikelskrivningen var en av de grundlĂ€ggande typerna â GeneralizedTime, avkodas och kodas.
I vÄra projekt, för att spara utrymme, lagrar vi ofta bara sökvÀgen till filen, förskjutningen och lÀngden i byte av objektet som vi vill referera till. Till exempel kommer en godtyckligt signerad fil troligtvis att finnas i CMS SignedData ASN.1-strukturen:
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
HĂR ĂR INNEHĂ
LLET AV DEN SIGNERADE FILEN PĂ
751 BYTE
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
och vi kan hĂ€mta den ursprungliga signerade filen med en förskjutning pĂ„ 65 byte, med en lĂ€ngd pĂ„ 751 byte. pyasn1 lagrar inte denna information i sina dekodade objekt. En sĂ„ kallad TLVSeeker har skrivits â ett litet bibliotek som gör det möjligt att dekodera taggar och lĂ€ngder av objekt, dĂ€r vi kommenderade i grĂ€nssnittet "gĂ„ till nĂ€sta tagg", "gĂ„ in i taggen" (gĂ„r in i SEQUENCE-objektet), "gĂ„ till nĂ€sta tagg", "rapportera din offset och lĂ€ngd av objektet, dĂ€r vi befinner oss". Detta var "manuell" navigering genom ASN.1 DER-seriella data. Men sĂ„ kunde man inte arbeta med BER-seriella data, eftersom till exempel byte-strĂ€ngen OCTET STRING kan ha kodats i flera chunkar.
En annan nackdel med pyasn1 för vĂ„ra Ă€ndamĂ„l Ă€r oförmĂ„gan att förstĂ„ om ett givet fĂ€lt fanns i SEQUENCE eller inte, baserat pĂ„ de dekodade objekten. Till exempel, om strukturen innehĂ„ller fĂ€ltet Field SEQUENCE OF Smth OPTIONAL, kunde det helt saknas i de inkommande data (OPTIONAL), eller sĂ„ kunde det finnas men ha en lĂ€ngd pĂ„ noll (en tom lista). I allmĂ€nhet kunde detta inte avgöras. Och det var nödvĂ€ndigt för en strikt kontroll av giltigheten av de inkommande data. TĂ€nk dig att nĂ„gon certifieringsmyndighet utfĂ€rdade ett certifikat med data som inte var "helt" giltiga enligt ASN.1-schema! Till exempel utfĂ€rdade certifieringsmyndigheten "TĂRKTRUST Elektronik Sertifika Hizmet SaÄlayıcısı" ett rotcertifikat som överskred godtagbara grĂ€nser för lĂ€ngden pĂ„ komponenten subject â det gĂ„r inte att korrekt dekoda det enligt schemat. DER-kodaren krĂ€ver att ett fĂ€lt vars vĂ€rde Ă€r DEFAULT-vĂ€rdet, inte kodas vid överföringen â i verkligheten dyker sĂ„dana dokument upp, och den första versionen av PyDERASN tillĂ€t medvetet ett sĂ„dant ogiltigt (enligt DER) beteende för att sĂ€kerstĂ€lla bakĂ„tkompatibilitet.
En annan begrÀnsning Àr omöjligheten att enkelt veta vilket format (BER/DER) en viss objekt i strukturen har kodats i. Till exempel sÀger CMS-standarden att meddelandet kodas i BER, men fÀltet signedAttrs, som den kryptografiska signaturen bildas över, mÄste vara i DER. Om vi dekodar med DER, kommer vi att fÄ problem med sjÀlva CMS-behandlingen, och om vi dekodar med BER, vet vi inte i vilket format signedAttrs var. Slutligen mÄste vi anvÀnda TLVSeeker (som inte har nÄgon motsvarighet i pyasn1) för att hitta platsen för varje signedAttrs-fÀlt och dekoda det separat frÄn den serialiserade representationen med DER.
Möjligheten till automatisk behandling av DEFINED BY-fÀlt, som förekommer mycket ofta, var vÀldigt önskvÀrd för oss. Efter dekodning av ASN.1-strukturen kan vi ha mÄnga ANY-fÀlt kvar som mÄste behandlas vidare enligt det schema som vÀljs baserat pÄ OBJECT IDENTIFIER specificerad i fÀltet. I Python-koden betyder detta att vi mÄste skriva if-satser och dÀrefter anropa dekodern för ANY-fÀltet.
Introduktionen av PyDERASN
I Atlas skickar vi regelbundet patchar till de anvÀnda fria programmen nÀr vi hittar problem eller gör förbÀttringar. Vi har flera gÄnger skickat in bidrag till pyasn1, men koden i pyasn1 Àr inte den enklaste att förstÄ och det har ibland förekommit inkompatibla API-förÀndringar som har stÀllt till det för oss. Dessutom Àr vi vana vid att skriva tester med generativ testning, vilket saknades i pyasn1.
En dag bestÀmde jag mig för att det var dags att sluta acceptera detta och börja skriva ett eget bibliotek med __slot__-ar, offset-ar och lÀtt tolkade blob-ar! Att skapa en ASN.1-kodare skulle inte vara tillrÀckligt - vi behövde översÀtta alla vÄra ömsesidigt beroende projekt till det, vilket innebÀr hundratusentals rader kod dÀr det finns mycket arbete med ASN.1-strukturer. SÄ en av de krav som stÀlldes pÄ det var: enkelheten att översÀtta den nuvarande pyasn1-koden. Under hela min semester skrev jag detta bibliotek och konverterade alla projekt till det. Eftersom de har nÀstan 100% testtÀckning innebar detta ocksÄ att biblioteket var helt funktionellt.
PÄ samma sÀtt har PyDERASN nÀstan 100% testtÀckning. Generativ testning anvÀnds med det fantastiska biblioteket . Fuzzing genomfördes ocksÄ och - pÄ 32-kÀrniga maskiner. Trots att vi praktiskt taget inte har nÄgon Python2-kod kvar, upprÀtthÄller PyDERASN fortfarande kompatibilitet med den, och dÀrför har den en enda beroende. Dessutom har den testats mot .
Arbetsprincipen med den Ă€r liknande pyasn1 â arbete med högnivĂ„objekt i Python. Beskrivningen av ASN.1-scheman Ă€r liknande.
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)),
)
Men PyDERASN har en form av strikt typning. I pyasn1, om ett fÀlt hade typen CMSVersion(INTEGER), kunde man tilldela det en int eller INTEGER. PyDERASN krÀver strÀngt att det tilldelade objektet Àr just CMSVersion. Dessutom, eftersom vi skriver Python3-kod, anvÀnder vi ocksÄ , sÄ i vÄra funktioner kommer det inte att finnas oklara argument som typ def func(serial, contents), utan def func(serial: CertificateSerialNumber, contents: EncapsulatedContentInfo), och PyDERASN hjÀlper till att upprÀtthÄlla sÄdan kod.
Samtidigt har PyDERASN mycket bekvĂ€ma fördelar med denna typning. pyasn1 tillĂ€t inte att man i SubjectKeyIdentifier().subtype(implicitTag=Tag(âŠ)) tilldela ett SubjectKeyIdentifier()-objekt (utan rĂ€tt IMPLICIT TAG), vilket ledde till att man ofta var tvungen att kopiera och Ă„terskapa objekt bara pĂ„ grund av Ă€ndrade IMPLICIT/EXPLICIT-taggar. PyDERASN övervakar strikt bara den grundlĂ€ggande typen â taggarna sĂ€tts automatiskt in frĂ„n den redan befintliga ASN.1-strukturens schema. Detta förenklar kodningen av applikationer avsevĂ€rt.
Om det uppstĂ„r ett fel under dekodning Ă€r det inte lĂ€tt att förstĂ„ exakt var i pyasn1 det intrĂ€ffade. Till exempel, i det redan nĂ€mnda turkiska certifikatet fĂ„r vi ett sĂ„dant fel: UTF8String (tbsCertificate:issuer:rdnSequence:3:0:value:DEFINED BY 2.5.4.10:utf8String) (vid 138) otillfredsstĂ€llda grĂ€nser: 1 â 77 â 64 NĂ€r man skriver ASN.1-strukturer kan folk göra misstag, och detta hjĂ€lper att lĂ€ttare debugga applikationer eller upptĂ€cka problem med kodade dokument frĂ„n den andra sidan.
I den första versionen av PyDERASN fanns det ingen stöd för BER-kodning. Det kom mycket senare och fortfarande stöds inte bearbetning av UTCTime/GeneralizedTime med tidszoner. Detta kommer att komma i framtiden, eftersom projektet huvudsakligen skrivs pÄ fritiden.
I den första versionen fanns det inte nÄgot stöd för DEFINED BY-fÀlten. Efter nÄgra mÄnader dök denna och började anvÀndas aktivt, vilket avsevÀrt reducerade kodmÀngden i applikationer - med en enda avkodningsoperation kunde man fÄ hela strukturen nedbruten till dess innersta delar. För detta definieras i schemat vilka fÀlt som "definierar" vad. Till exempel, beskrivningen av CMS-schemat:
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))),
)
indikerar att om contentType innehÄller OID med vÀrdet id_signedData, sÄ behöver fÀltet content (som finns i samma SEQUENCE) avkodas enligt schemat SignedData. Varför sÄ mÄnga parenteser? Ett fÀlt kan "definiera" flera fÀlt samtidigt, som i strukturerna i EnvelopedData. De definierade fÀlten identifieras med sÄ kallad decode path - den anger den exakta positionen för varje element i alla strukturer.
Det Àr inte alltid önskvÀrt, eller möjligt, att omedelbart lÀgga till dessa defines i schemat. Det kan finnas applikationsspecifika fall dÀr OID:er och strukturer endast Àr kÀnda i ett externt projekt. PyDERASN erbjuder möjligheten att ange dessa defines direkt vid avkodning av strukturen:
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(),
}),),
),)})
HÀr sÀger vi att i CMS SignedData för alla bifogade certifikat, ska avkoda alla deras tillÀgg (AuthorityKeyIdentifier, BasicConstraints, SubjectSignTool, etc.). Vi anger genom decode path vilket element som behöver "ersÀttas" med defines, som om det hade definierats i schemat.
Slutligen har PyDERASN möjlighet att fungera frÄn för att avkoda ASN.1-filer och har ett rikt . Man kan avkoda godtycklig ASN.1, eller sÄ kan man ange ett tydligt angivet schema och se nÄgot sÄdant hÀr:

Visad information: objektets offset, taggens lÀngd, lÀngden pÄ lÀngden, innehÄllslÀngd, förekomst av EOC (end-of-octets), indikationen för BER-kodning, indikationen för oÀndlig lÀngd kodning, lÀngd och offset för EXPLICIT-taggen (om det finns), djupet pÄ objektets inbÀddning i strukturer, IMPLICIT/EXPLICIT vÀrde för taggen, namnet pÄ objektet enligt schemat, dess grundlÀggande ASN.1 typ, sekvensnummer inom SEQUENCE/SET OF, vÀrdet för CHOICE (om det finns), mÀnniskolÀsbart namn för INTEGER/ENUMERATED/BIT STRING enligt schemat, vÀrdet av vilken grundlÀggande typ som helst, DEFAULT/OPTIONAL flagga frÄn schemat, indikationen att objektet automatiskt har dekodats som DEFINED BY och genom vilken OID det intrÀffade, mÀnniskolÀsbar OID.
Systemet för pretty printing Àr speciellt utformat sÄ att det genererar en sekvens av PP-objekt som visualiseras med separat verktyg. PÄ skÀrmdumpen visas en renderer i enkel fÀrgtext. Det finns Àven renderers i JSON/HTML-format sÄ att detta kan ses med syntaxmarkering i webblÀsaren för ASN.1 som i projektet.
Andra bibliotek
Det var inte syftet, men PyDERASN blev avsevÀrt Àn pyasn1. Till exempel kan dekodning av CRL-filer i megabyte storlek ta sÄ lÄng tid att man mÄste tÀnka pÄ snabba mellanlagringsformat och Àndra applikationsarkitekturen. pyasn1 dekodade CRL pÄ min bÀrbara dator pÄ över 20 minuter, medan PyDERASN bara gör det pÄ 28 sekunder! Det finns ett projekt , som Àr inriktat pÄ snabb hantering av kryptografiska strukturer: det dekoderar (fullstÀndigt, inte lat) samma CRL pÄ 29 sekunder, men konsumerar nÀstan dubbelt sÄ mycket RAM nÀr det körs under Python3 (983 MiB mot 498), och 3,5 gÄnger sÄ mycket under Python2 (1677 mot 488), medan pyasn1 konsumerar hela 4,3 gÄnger mer (2093 mot 488).
asn1crypto, som jag nÀmnde, övervÀgde vi inte eftersom projektet fortfarande var i sin linda, och vi hade inte hört talas om det. Just nu skulle vi heller inte titta Ät det hÄllet, eftersom jag genast upptÀckte att den samma GeneralizedTime inte accepterar godtyckligt format, och vid serialisering tar den tyst bort sekunders delar. Detta Àr acceptabelt för arbete med X.509-certifikat, men i allmÀnhet lÀmpar det sig inte.
För nÀrvarande Àr PyDERASN den striktaste av de fria Python/Go DER-dekodar som jag kÀnner till. I encoding/asn1-biblioteket av min Àlskade Go OBJEKTIDENTIFIKATOR och UTCTime/GeneralizedTime strÀngar. Ibland kan striktheten stÀlla till med problem (frÀmst pÄ grund av bakÄtkompatibilitet med gamla applikationer som ingen kommer att ÄtgÀrda), sÄ vid dekodning i PyDERASN kan man skicka som mildrar kontroller.
Projektkoden strÀvar efter att vara sÄ enkel som möjligt. Hela biblioteket Àr en fil. Koden Àr skriven med fokus pÄ lÀttförstÄelighet, utan överflödig prestandaoptimering och DRY-kod. Den stödjer inte, som nÀmnt, fullstÀndig BER-dekodning av UTCTime/GeneralizedTime strÀngar, samt REAL, RELATIVE OID, EXTERNAL, INSTANCE OF, EMBEDDED PDV, CHARACTER STRING datatyper. I alla andra fall ser jag personligen ingen mening med att anvÀnda andra bibliotek i Python.
Som alla mina projekt, typ , , , , PyDERASN Àr helt , som distribueras under villkoren , och tillgÀnglig för gratis nedladdning. Exempel pÄ anvÀndning finns i och i .
, , medlem av , Python/Go-utvecklare, huvudspecialist .
KĂ€lla: habr.com
