PyDERASN: kaip aš parašiau ASN.1 biblioteką su lizdais ir dėmėmis

ASN.1 tai yra kalbos standartas (ISO, ITU-T, GOST), aprašantis struktūrizuotą informaciją, taip pat šios informacijos kodavimo taisyklės. Man, kaip programuotojui, tai tik dar vienas duomenų serializavimo ir pateikimo formatas kartu su JSON, XML, XDR ir kt. Tai itin dažna mūsų kasdienybėje ir su juo susiduria daugelis žmonių: korinio ryšio, telefono, VoIP komunikacijose (UMTS, LTE, WiMAX, SS7, H.323), tinklo protokoluose (LDAP, SNMP, Kerberos), visame kame, susiję su kriptografija (X.509, CMS, PKCS standartai), banko kortelėse ir biometriniuose pasuose ir daug daugiau.

Šiame straipsnyje kalbama apie PyDERASN: Python ASN.1 biblioteka aktyviai naudojama su kriptografija susijusiuose projektuose Atlasas.

PyDERASN: kaip aš parašiau ASN.1 biblioteką su lizdais ir dėmėmis
Apskritai ASN.1 neverta rekomenduoti kriptografinėms užduotims: ASN.1 ir jo kodekai yra sudėtingi. Tai reiškia, kad kodas nebus paprastas, ir tai visada yra papildomas atakos vektorius. Tik pažiūrėk į sąrašą ASN.1 bibliotekų pažeidžiamumas. Bruce'as Schneieris savo Kriptografijos inžinerija taip pat pataria nenaudoti šio standarto dėl jo sudėtingumo: „Geriausiai žinoma TLV koduotė yra ASN.1, tačiau ji yra neįtikėtinai sudėtinga ir mes jos vengiame“. Bet, deja, šiandien turime viešojo rakto infrastruktūra kurioje jie aktyviai naudojami X.509 sertifikatai, CRL, OCSP, TSP, CMP protokolai, CMC, žinutės TVS, ir daug standartų PKCS. Todėl, jei darote ką nors, kas susiję su kriptografija, turite mokėti dirbti su ASN.1.

ASN.1 gali būti užkoduotas įvairiais būdais / kodekais:

  • BIR (Pagrindinės kodavimo taisyklės)
  • CER (kanoninės kodavimo taisyklės)
  • DER (Išskirtos kodavimo taisyklės)
  • GSER (Bendrosios eilučių kodavimo taisyklės)
  • JER (JSON kodavimo taisyklės)
  • LWER (lengvo svorio kodavimo taisyklės)
  • OER (okteto kodavimo taisyklės)
  • PER (Supakuotos kodavimo taisyklės)
  • SER (specifinės signalizacijos kodavimo taisyklės)
  • MOKINIAI (XML kodavimo taisyklės)

ir nemažai kitų. Tačiau kriptografinėse užduotyse praktiškai naudojami du: BER ir DER. Net pasirašytuose XML dokumentuose (XMLDSig, XAdES) vis tiek bus Base64 koduotų ASN.1 DER objektų, kaip ir JSON orientuotame protokole acme iš Let's Encrypt. Visus šiuos kodekus ir BER/CER/DER kodavimo principus galite geriau suprasti straipsniuose ir knygose: ASN.1 paprastais žodžiais, ASN.1 – Ryšys tarp heterogeninių sistemų, Olivier Dubuisson, ASN.1 užbaigė prof. John Larmouth.

BER yra dvejetainis į baitus orientuotas (pavyzdžiui, PER, populiarus korinio ryšio srityje – orientuotas į bitus) TLV formatas. Kiekvienas elementas yra užkoduotas kaip: tag (Tag), nurodant koduojamo elemento tipą (sveikasis skaičius, eilutė, data ir kt.), ilgis (Length) turinys ir pats turinys (Vdydis). BER pasirinktinai leidžia nenurodyti ilgio reikšmės, nustatant specialią neapibrėžto ilgio reikšmę ir baigiant oktetų pabaigos pranešimą oktetų pabaigos ženklu. Be ilgio kodavimo, BER turi daug įvairių duomenų tipų kodavimo būdų, tokių kaip:

  • SVEIKI SKAIČIUS, OBJEKTO IDENTIFIKATAS, BITŲ EIGA ir elemento ilgis gali būti nenormalizuoti (neužkoduoti minimalia forma);
  • BOOLEAN tinka bet kokiam turiniui, kuris nėra nulis;
  • BIT STRING gali turėti „papildomų“ nulio bitų;
  • BIT STRING, OCTET STRING ir visi jų išvestiniai eilučių tipai, įskaitant datą/laiką, gali būti suskirstyti į kintamo ilgio dalis, kurių ilgis (de)kodavimo metu iš anksto nežinomas;
  • UTCTime/GeneralizedTime gali turėti skirtingus būdus nurodyti laiko juostos poslinkį ir „papildomas“ nulines sekundžių dalis;
  • DEFAULT SEQUENCE reikšmės gali būti užkoduotos arba ne;
  • Paskutinių BIT STRING bitų įvardintos reikšmės pasirinktinai gali būti nekoduotos;
  • SEQUENCE (OF)/SET (OF) gali turėti bet kokią elementų tvarką.

Dėl visų aukščiau išvardytų dalykų ne visada įmanoma koduoti duomenis taip, kad jie būtų identiški pradinei formai. Todėl buvo išrastas taisyklių poaibis: DER – griežtai reglamentuojantis tik vieną galiojantį kodavimo metodą, kuris yra labai svarbus kriptografinėms užduotims, kai, pavyzdžiui, pakeitus vieną bitą parašas arba kontrolinė suma taps negaliojančia. DER turi reikšmingą trūkumą: visų elementų ilgiai turi būti žinomi iš anksto kodavimo metu, o tai neleidžia duomenų srautu serializuoti. CER kodekas neturi šio trūkumo, taip pat garantuoja nedviprasmišką duomenų atvaizdavimą. Deja (o gal laimė, kad neturime dar sudėtingesnių dekoderių?), jis neišpopuliarėjo. Todėl praktikoje susiduriame su „mišriu“ BER ir DER koduotų duomenų naudojimu. Kadangi ir CER, ir DER yra BER poaibis, bet kuris BER dekoderis gali juos apdoroti.

Problemos su pyasn1

Darbe rašome daug Python programų, susijusių su kriptografija. O prieš keletą metų nemokamų bibliotekų pasirinkimo praktiškai nebuvo: arba tai labai žemo lygio bibliotekos, leidžiančios tiesiog užkoduoti/dekoduoti, pavyzdžiui, sveikąjį skaičių ir struktūros antraštę, arba ši biblioteka. pyasn1. Mes gyvenome kelerius metus ir iš pradžių buvome labai patenkinti, nes jis leidžia dirbti su ASN.1 struktūromis kaip su aukšto lygio objektais: pavyzdžiui, iššifruotas X.509 sertifikato objektas leidžia pasiekti jo laukus per žodyno sąsaja: cert["tbsCertificate"] ["serialNumber"] parodys šio sertifikato serijos numerį. Panašiai galite „surinkti“ sudėtingus objektus dirbdami su jais kaip sąrašus, žodynus, tada tiesiog iškvieskite funkciją pyasn1.codec.der.encoder.encode ir gausite nuoseklų dokumento vaizdą.

Tačiau išryškėjo trūkumai, problemos ir apribojimai. Buvo ir, deja, vis dar yra pyasn1 klaidų: rašymo metu vienas iš pagrindinių pyasn1 tipų yra GeneralizedTime, neteisingai iššifruoti ir užkoduoti.

Savo projektuose, norėdami sutaupyti vietos, dažnai saugome tik failo kelią, poslinkį ir ilgį baitais objekto, kurį norime nurodyti. Pavyzdžiui, atsitiktinai pasirašytas failas greičiausiai bus CMS SignedData ASN.1 struktūroje:

  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

ir mes galime gauti originalų pasirašytą failą, kurio poslinkis yra 65 baitai, 751 baitų ilgio. pyasn1 nesaugo šios informacijos savo dekoduotuose objektuose. Buvo parašyta vadinamoji TLVSeeker - maža biblioteka, leidžianti iššifruoti žymes ir objektų ilgius, kurių sąsajoje nurodėme „eiti į kitą žymą“, „eiti į žymą“ (eiti į SEQUENCE objektą), „eikite prie kitos žymos“, „pasakykite savo poslinkį ir objekto, kuriame esame, ilgį“. Tai buvo „rankinis“ žingsnis per ASN.1 DER serijinius duomenis. Tačiau tokiu būdu buvo neįmanoma dirbti su BER serializuotais duomenimis, nes, pavyzdžiui, OCTET STRING baitų eilutė gali būti užkoduota kelių dalių forma.

Kitas mūsų pyasn1 užduočių trūkumas yra nesugebėjimas iš dekoduotų objektų suprasti, ar tam tikras laukas yra SEQUENCE, ar ne. Pvz., jei struktūroje yra laukas „SEMTH OPTIONAL“ SEKKA, jo gali visiškai nebūti gaunamuose duomenyse (PASIRENKAMA) arba jis gali būti, bet jo ilgis yra nulinis (tuščias sąrašas). Apskritai to nustatyti nepavyko. O tai būtina norint griežtai patikrinti gautų duomenų pagrįstumą. Įsivaizduokite, kad kokia nors sertifikavimo institucija išduotų sertifikatą su duomenimis, kurie „ne visiškai“ galioja ASN.1 schemų požiūriu! Pavyzdžiui, sertifikavimo institucija „TÜRKTRUST Elektronik Sertifika Hizmet Sağlayıcısı“ viršijo leistinas ribas savo pagrindiniame sertifikate. RFC 5280 dalyko komponento ilgio ribos – jo negalima sąžiningai iššifruoti pagal schemą. DER kodekas reikalauja, kad laukas, kurio reikšmė lygi DEFAULT, nebūtų užkoduotas perdavimo metu – tokių dokumentų pasitaiko gyvenime, o pirmoji PyDERASN versija netgi sąmoningai leido tokį netinkamą (DER požiūriu) elgesį. atgalinis suderinamumas.

Kitas apribojimas yra nesugebėjimas lengvai sužinoti, kokia forma (BER/DER) struktūroje buvo užkoduotas konkretus objektas. Pavyzdžiui, TVS standartas sako, kad pranešimas yra užkoduotas BER, tačiau laukas signedAttrs, per kurį generuojamas kriptografinis parašas, turi būti DER. Jei dekoduosime naudodami DER, nepavyks apdoroti pačios TVS, o jei dekoduosime su BER, nesužinosime, kokios formos buvo pasirašytas Attrs. Dėl to TLVSeeker (kuris neturi analogo pyasn1) turės ieškoti kiekvieno signedAttrs lauko vietos ir atskirai, išėmus jį iš serializuoto atvaizdo, dekoduoti su DER.

Galimybė automatiškai apdoroti DEFINED BY laukus, kurie pasitaiko labai dažnai, mums buvo labai pageidautina. Iškodavus ASN.1 struktūrą, mums gali likti daug BET KOKIO laukų, kuriuos reikia toliau apdoroti pagal schemą, parinktą pagal struktūros lauke nurodytą OBJEKTO IDENTIFIKAVĄ. Python kode tai reiškia parašyti if ir tada iškviesti bet kurio lauko dekoderį.

PyDERASN atsiradimas

„Atlas“ reguliariai siunčia pataisas į viršų, kai nustatome kokių nors problemų arba patobuliname naudojamas nemokamas programas. Keletą kartų pateikėme pyasn1 patobulinimus, tačiau pyasn1 kodas nėra lengviausiai suprantamas ir kartais buvo nesuderinami API pakeitimai, kurie mus nugalėjo. Be to, mes įpratę rašyti testus naudodami generatyvųjį testavimą, o to nebuvo pyasn1.

Vieną gražią dieną nusprendžiau, kad man to užtenka, ir atėjo laikas pabandyti sukurti savo biblioteką su __slot__s, poslinkiais ir gražiai rodomomis dėmėmis! Vien sukurti ASN.1 kodeką nepakaktų – į jį reikia perkelti visus savo priklausomus projektus, o tai yra šimtai tūkstančių kodo eilučių, kuriose pilna darbo su ASN.1 struktūromis. Tai yra, vienas iš jam keliamų reikalavimų: lengvas dabartinio pyasn1 kodo vertimas. Praleidęs visas atostogas parašiau šią biblioteką ir į ją perkėliau visus projektus. Kadangi jie beveik 100% aprėpia testus, tai reiškė, kad biblioteka veikė visiškai.

„PyDERASN“ taip pat turi beveik 100% testavimo aprėptį. Naudoja generatyvųjį testavimą su puikia biblioteka hipotezė. Jis taip pat buvo atliktas neryškus py-afl– Valgau ant 32 branduolinių mašinų. Nepaisant to, kad praktiškai neturime Python2 kodo, PyDERASN vis dar palaiko suderinamumą su juo ir dėl to turi vienintelį šeši priklausomybė. Be to, jis yra išbandytas ASN.1:2008 atitikties testų rinkinys.

Darbo su juo principas panašus į pyasn1 – darbą su aukšto lygio Python objektais. ASN.1 schemų aprašymas panašus.

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

Tačiau PyDERASN turi tam tikrą stipraus spausdinimo įvaizdį. Pyasn1, jei laukas buvo CMSVersion(INTEGER) tipo, jam gali būti priskirtas int arba INTEGER. PyDERASN griežtai reikalauja, kad priskirtas objektas būtų tiksliai CMSVersion. Be Python3 kodo rašymo, mes taip pat naudojame rašyti komentarus, todėl mūsų funkcijos neturės neaiškių argumentų, tokių kaip def func(serial, contents), o def func(serial: CertificateSerialNumber, contents: EncapsulatedContentInfo), o PyDERASN padeda išlaikyti tokį kodą.

Tuo pačiu metu PyDERASN turi itin patogių nuolaidų šiam spausdinimui. pyasn1 neleido lauke SubjectKeyIdentifier().subtype(implicitTag=Tag(...)) priskirti objekto SubjectKeyIdentifier() (be būtinos IMPLICIT TAG) ir dažnai reikėjo kopijuoti ir atkurti objektus vien dėl pakeistos IMPLICIT/EXPLICIT žymos. PyDERASN griežtai laikosi tik bazinio tipo – automatiškai pakeis žymes iš jau esamos struktūros ASN.1 schemos. Tai labai supaprastina programos kodą.

Jei dekoduojant įvyksta klaida, tada pyasn1 nėra lengva suprasti, kur tiksliai ji įvyko. Pavyzdžiui, jau minėtame turkiškame sertifikate gausime šią klaidą: UTF8String (tbsCertificate:issuer:rdnSequence:3:0:value:DEFINED BY 2.5.4.10:utf8String) (138) nepatenkintos ribos: 1 ⇐ 77 ⇐ 64 Rašydami ASN .1 struktūras žmonės gali padaryti klaidų, todėl lengviau derinti programas arba išsiaiškinti problemas, susijusias su kitos šalies koduotais dokumentais.

Pirmoji PyDERASN versija nepalaikė BER kodavimo. Jis pasirodė daug vėliau ir vis dar nepalaiko UTCTime/GeneralizedTime apdorojimo su laiko juostomis. Tai ateis ateityje, nes projektas daugiausia rašomas mano laisvalaikiu.

Be to, pirmoje versijoje nebuvo darbo su DEFINED BY laukais. Po kelių mėnesių tai atsirado galimybė ir pradėtas aktyviai naudoti, gerokai sumažinant programos kodą – per vieną dekodavimo operaciją buvo galima gauti visą konstrukciją išardytą iki pat gylio. Norėdami tai padaryti, schemoje nurodoma, kurie laukai ką „apibrėžia“. Pavyzdžiui, TVS schemos aprašymas:

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

sako, kad jei contentType yra OID su reikšme id_signedData, tai turinio laukas (esantis toje pačioje SEQUENCE) turi būti iškoduotas pagal SignedData schemą. Kodėl tiek daug skliaustų? Laukas vienu metu gali „apibūdinti“ kelis laukus, kaip yra EnvelopedData struktūrose. Apibrėžti laukai identifikuojami pagal vadinamąjį dekodavimo kelią – jis nurodo tikslią bet kurio elemento vietą visose struktūrose.

Jūs ne visada norite arba ne visada turite galimybę iš karto įtraukti šiuos apibrėžimus prie diagramos. Gali būti konkrečių programų atvejų, kai OID ir struktūros žinomi tik trečiosios šalies projekte. PyDERASN suteikia galimybę nustatyti šiuos apibrėžimus tiesiai dekoduojant struktūrą:

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

Čia sakome, kad CMS SignedData visiems pridedamiems sertifikatams iššifruokite visus jų plėtinius (AuthorityKeyIdentifier, BasicConstraints, SubjectSignTool ir kt.). Dekodavimo keliu nurodome, kurį elementą reikia „pakeisti“ apibrėžimais, tarsi jis būtų nurodytas schemoje.

Galiausiai PyDERASN turi galimybę paleisti iš komandinė eilutė ASN.1 failams dekoduoti ir turi daug grazus spausdinimas. Galite iššifruoti savavališką ASN.1 arba galite nurodyti aiškiai apibrėžtą schemą ir pamatyti kažką panašaus į tai:

PyDERASN: kaip aš parašiau ASN.1 biblioteką su lizdais ir dėmėmis

Rodoma informacija: objekto poslinkis, žymos ilgis, ilgio ilgis, turinio ilgis, EOC buvimas (oktetų pabaiga), BER kodavimo atributas, neapibrėžto ilgio kodavimo atributas, EXPLICIT žymos ilgis ir poslinkis (jei yra), įdėjimo gylis objektas struktūrose, IMPLICIT/EXPLICIT žymos reikšmė, objekto pavadinimas pagal schemą, jo bazinis ASN.1 tipas, eilės numeris SEQUENCE/SET OF viduje, CHOICE reikšmė (jei yra), žmogaus skaitomas pavadinimas INTEGER/NUMERATED/BIT STRING pagal schemą, bet kurio bazinio tipo reikšmė , DEFAULT/OPTIONAL vėliavėlė iš schemos, ženklas, kad objektas buvo automatiškai dekoduotas kaip DEFINED BY ir dėl kurio OID tai atsitiko, žmogui skaitomas OID.

Graži spausdinimo sistema yra specialiai sukurta taip, kad generuotų PP objektų seką, kuri vizualizuojama naudojant atskiras priemones. Ekrano kopijoje perteikėjas rodomas paprastu spalvotu tekstu. Taip pat yra JSON / HTML formato atvaizduotojų, todėl ASN.1 naršyklėje jį būtų galima pamatyti paryškinus, kaip asn1js projektą.

Kitos bibliotekos

Tai nebuvo tikslas, tačiau PyDERASN pasirodė reikšmingai greičiau nei pyasn1. Pavyzdžiui, megabaitų dydžio CRL failų dekodavimas gali užtrukti tiek ilgai, kad teks galvoti apie tarpinius duomenų saugojimo formatus (greitai) ir keisti programos architektūrą. pyasn1 iššifruoja CRL CACert.org mano nešiojamajame kompiuteryje užtrunka daugiau nei 20 minučių, o PyDERASN užtrunka tik 28 sekundes! Yra projektas asn1crypto, skirtas greitam darbui su kriptografinėmis struktūromis: iššifruoja (visiškai, o ne tingiai) tą patį CRL per 29 sekundes, tačiau sunaudoja beveik dvigubai daugiau RAM, kai veikia pagal Python3 (983 MiB, palyginti su 498), ir per 3.5 karto pagal Python2 (1677). palyginti su 488), o pyasn1 suvartoja net 4.3 karto daugiau (2093, palyginti su 488).

Asn1crypto, apie kurį minėjau, nesvarstėme, nes projektas vis dar buvo pradinėje stadijoje ir apie jį nebuvome girdėję. Dabar irgi nežiūrėtume jo kryptimi, nes iškart sužinojau, kad tas pats GeneralizedTime neįgauna savavališkos formos, o serializacijos metu tyliai pašalina sekundės dalį. Tai priimtina dirbant su X.509 sertifikatais, tačiau apskritai tai neveiks.

Šiuo metu PyDERASN yra griežčiausias nemokamas Python/Go DER dekoderis, kokį aš žinau. Mano mylimo Go kodavimo/asn1 bibliotekoje ne griežtas patikrinimas OBJEKTO IDENTIFIER ir UTCTime/GeneralizedTime eilutės. Kartais griežtumas gali trukdyti (visų pirma dėl atgalinio suderinamumo su senesnėmis programomis, kurių niekas nepataisys), todėl PyDERASN gali praeiti įvairūs nustatymai čekių susilpnėjimas.

Projekto kodas stengiasi būti kuo paprastesnis. Visa biblioteka yra vienas failas. Kodas parašytas pabrėžiant, kad būtų lengviau suprasti, be nereikalingo našumo optimizavimo ir DRY kodo. Kaip jau sakiau, jis nepalaiko visiško UTCTime/GeneralizedTime eilučių BER dekodavimo, taip pat REAL, RELATIVE OID, EXTERNAL, INSTANCE OF, EMBEDDED PDV, CHARACTER STRING duomenų tipų. Visais kitais atvejais aš asmeniškai nematau prasmės naudoti kitas Python bibliotekas.

Kaip ir visi mano projektai, patinka PyGOST, GoGOST, NCCP, GoVPN, PyDERASN yra visiškai nemokama programinė įranga, platinamas pagal sąlygas LGPLv3+, ir jį galima atsisiųsti nemokamai. Yra naudojimo pavyzdžių čia ir PyGOST testai.

Sergejus Matvejevas, cypherpunk, narys SPO fondas, Python/Go kūrėjas, vyriausiasis specialistas FSUE "STC "Atlas".

Šaltinis: www.habr.com

Добавить комментарий