Examples

Schema definition

Let’s try to parse X.509 certificate. We have to define our structures based on ASN.1 schema descriptions.

ASN.1 specification

pyderasn’s code

Certificate  ::=  SEQUENCE  {
    tbsCertificate       TBSCertificate,
    signatureAlgorithm   AlgorithmIdentifier,
    signatureValue       BIT STRING  }
class Certificate(Sequence):
    schema = (
        ("tbsCertificate", TBSCertificate()),
        ("signatureAlgorithm", AlgorithmIdentifier()),
        ("signatureValue", BitString()),
    )
AlgorithmIdentifier  ::=  SEQUENCE  {
    algorithm    OBJECT IDENTIFIER,
    parameters   ANY DEFINED BY algorithm OPTIONAL  }
class AlgorithmIdentifier(Sequence):
    schema = (
        ("algorithm", ObjectIdentifier()),
        ("parameters", Any(optional=True)),
    )
TBSCertificate  ::=  SEQUENCE  {
    version         [0]  EXPLICIT Version DEFAULT v1,
    serialNumber         CertificateSerialNumber,
    signature            AlgorithmIdentifier,
    issuer               Name,
    validity             Validity,
    subject              Name,
    subjectPublicKeyInfo SubjectPublicKeyInfo,
    issuerUniqueID  [1]  IMPLICIT UniqueIdentifier OPTIONAL,
    subjectUniqueID [2]  IMPLICIT UniqueIdentifier OPTIONAL,
    extensions      [3]  EXPLICIT Extensions OPTIONAL  }
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)),
    )
Version  ::=  INTEGER  {  v1(0), v2(1), v3(2)  }
class Version(Integer):
    schema = (("v1", 0), ("v2", 1), ("v3", 2))
CertificateSerialNumber  ::=  INTEGER
class CertificateSerialNumber(Integer):
    pass
Validity ::= SEQUENCE {
    notBefore      Time,
    notAfter       Time }
Time ::= CHOICE {
    utcTime        UTCTime,
    generalTime    GeneralizedTime }
class Validity(Sequence):
    schema = (
        ("notBefore", Time()),
        ("notAfter", Time()),
    )
class Time(Choice):
    schema = (
        ("utcTime", UTCTime()),
        ("generalTime", GeneralizedTime()),
    )
SubjectPublicKeyInfo  ::=  SEQUENCE  {
    algorithm            AlgorithmIdentifier,
    subjectPublicKey     BIT STRING  }
class SubjectPublicKeyInfo(Sequence):
    schema = (
        ("algorithm", AlgorithmIdentifier()),
        ("subjectPublicKey", BitString()),
    )
UniqueIdentifier  ::=  BIT STRING
class UniqueIdentifier(BitString):
    pass
Name ::= CHOICE { rdnSequence  RDNSequence }

RDNSequence ::= SEQUENCE OF RelativeDistinguishedName

RelativeDistinguishedName ::= SET SIZE (1..MAX) OF AttributeTypeAndValue

AttributeTypeAndValue ::= SEQUENCE { type AttributeType, value AttributeValue }

AttributeType ::= OBJECT IDENTIFIER

AttributeValue ::= ANY -- DEFINED BY AttributeType
class Name(Choice):
    schema = (("rdnSequence", RDNSequence()),)
class RDNSequence(SequenceOf):
    schema = RelativeDistinguishedName()
class RelativeDistinguishedName(SetOf):
    schema = AttributeTypeAndValue()
    bounds = (1, float("+inf"))
class AttributeTypeAndValue(Sequence):
    schema = (
        ("type", AttributeType()),
        ("value", AttributeValue()),
    )
class AttributeType(ObjectIdentifier):
    pass
class AttributeValue(Any):
    pass
Extensions ::=  SEQUENCE SIZE (1..MAX) OF Extension

Extension  ::=  SEQUENCE  {
    extnID      OBJECT IDENTIFIER,
    critical    BOOLEAN DEFAULT FALSE,
    extnValue   OCTET STRING
    }
class Extensions(SequenceOf):
    schema = Extension()
    bounds = (1, float("+inf"))
class Extension(Sequence):
    schema = (
        ("extnID", ObjectIdentifier()),
        ("critical", Boolean(default=False)),
        ("extnValue", OctetString()),
    )

We are ready to decode PayPal’s certificate from Go encoding/asn1 test suite (assuming that it’s DER encoded representation is already in raw variable):

>>> crt = Certificate().decod(raw)
>>> crt
Certificate SEQUENCE[tbsCertificate: TBSCertificate SEQUENCE[
    version: [0] EXPLICIT Version INTEGER v3 OPTIONAL;
    serialNumber: CertificateSerialNumber INTEGER 61595;
    signature: AlgorithmIdentifier SEQUENCE[OBJECT IDENTIFIER 1.2.840.113549.1.1.5...

As command line utility

Descriptive errors

If you have bad DER/BER, then errors will show you where error occurred:

$ python -m pyderasn --schema tests.test_crts:Certificate path/to/bad/file
Traceback (most recent call last):
[...]
pyderasn.DecodeError: UTCTime (tbsCertificate:validity:notAfter:utcTime) (at 328) invalid UTCTime format
$ python -m pyderasn path/to/bad/file
[...]
pyderasn.DecodeError: UTCTime (0:SequenceOf:4:SequenceOf:1:UTCTime) (at 328) invalid UTCTime format

You can see, so called, decode path inside the structures: tbsCertificate -> validity -> notAfter -> utcTime and that object at byte 328 is invalid.

X.509 certificate creation

Let’s create some simple self-signed X.509 certificate from the ground:

tbs = TBSCertificate()
tbs["serialNumber"] = CertificateSerialNumber(10143011886257155224)

sign_algo_id = AlgorithmIdentifier((
    ("algorithm", ObjectIdentifier("1.2.840.113549.1.1.5")),
    ("parameters", Any(Null())),
))
tbs["signature"] = sign_algo_id

rdnSeq = RDNSequence()
for oid, klass, text in (
        ("2.5.4.6", PrintableString, "XX"),
        ("2.5.4.8", PrintableString, "Some-State"),
        ("2.5.4.7", PrintableString, "City"),
        ("2.5.4.10", PrintableString, "Internet Widgits Pty Ltd"),
        ("2.5.4.3", PrintableString, "false.example.com"),
        ("1.2.840.113549.1.9.1", IA5String, "false@example.com"),
):
    rdnSeq.append(
        RelativeDistinguishedName((
            AttributeTypeAndValue((
                ("type", AttributeType(oid)),
                ("value", AttributeValue(klass(text))),
            )),
        ))
    )
issuer = Name(("rdnSequence", rdnSeq))
tbs["issuer"] = issuer
tbs["subject"] = issuer

validity = Validity((
    ("notBefore", Time(
        ("utcTime", UTCTime(datetime(2009, 10, 8, 0, 25, 53))),
    )),
    ("notAfter", Time(
        ("utcTime", UTCTime(datetime(2010, 10, 8, 0, 25, 53))),
    )),
))
tbs["validity"] = validity

spki = SubjectPublicKeyInfo()
spki_algo_id = copy(sign_algo_id)
spki_algo_id["algorithm"] = ObjectIdentifier("1.2.840.113549.1.1.1")
spki["algorithm"] = spki_algo_id
spki["subjectPublicKey"] = BitString(hexdec("".join((
    "3048024100cdb7639c3278f006aa277f6eaf42902b592d8cbcbe38a1c92ba4695",
    "a331b1deadeadd8e9a5c27e8c4c2fd0a8889657722a4f2af7589cf2c77045dc8f",
    "deec357d0203010001",
))))
tbs["subjectPublicKeyInfo"] = spki

crt = Certificate()
crt["tbsCertificate"] = tbs
crt["signatureAlgorithm"] = sign_algo_id
crt["signatureValue"] = BitString(hexdec("".join((
    "a67b06ec5ece92772ca413cba3ca12568fdc6c7b4511cd40a7f659980402df2b",
    "998bb9a4a8cbeb34c0f0a78cf8d91ede14a5ed76bf116fe360aafa8821490435",
))))
crt.encode()

And we will get the same certificate used in Go’s library tests.

DEFINED BY fields

Here is only very simple example how you can define Any/OctetString fields automatic decoding:

class AttributeTypeAndValue(Sequence):
    schema = (
        ((("type",), AttributeType(defines=((("value",), {
            id_at_countryName: PrintableString(),
            id_at_stateOrProvinceName: PrintableString(),
            id_at_localityName: PrintableString(),
            id_at_organizationName: PrintableString(),
            id_at_commonName: PrintableString(),
        }),)))),
        ("value", AttributeValue()),
    )

And when you will try to decode X.509 certificate with it, your pretty printer will show:

34   [0,0, 149]  . . issuer: Name CHOICE rdnSequence
34   [1,2, 146]  . . . rdnSequence: RDNSequence SEQUENCE OF
37   [1,1,  11]  . . . . 0: RelativeDistinguishedName SET OF
39   [1,1,   9]  . . . . . 0: AttributeTypeAndValue SEQUENCE
41   [1,1,   3]  . . . . . . type: AttributeType OBJECT IDENTIFIER id-at-countryName (2.5.4.6)
46   [0,0,   4]  . . . . . . value: [UNIV 19] AttributeValue ANY
                 . . . . . . . 13:02:58:58
46   [1,1,   2]  . . . . . . . DEFINED BY (2.5.4.6): PrintableString PrintableString XX
50   [1,1,  19]  . . . . 1: RelativeDistinguishedName SET OF
52   [1,1,  17]  . . . . . 0: AttributeTypeAndValue SEQUENCE
54   [1,1,   3]  . . . . . . type: AttributeType OBJECT IDENTIFIER id-at-stateOrProvinceName (2.5.4.8)
59   [0,0,  12]  . . . . . . value: [UNIV 19] AttributeValue ANY
                 . . . . . . . 13:0A:53:6F:6D:65:2D:53:74:61:74:65
59   [1,1,  10]  . . . . . . . DEFINED BY (2.5.4.8): PrintableString PrintableString Some-State
71   [1,1,  13]  . . . . 2: RelativeDistinguishedName SET OF
73   [1,1,  11]  . . . . . 0: AttributeTypeAndValue SEQUENCE
75   [1,1,   3]  . . . . . . type: AttributeType OBJECT IDENTIFIER id-at-localityName (2.5.4.7)
80   [0,0,   6]  . . . . . . value: [UNIV 19] AttributeValue ANY
                 . . . . . . . 13:04:43:69:74:79
80   [1,1,   4]  . . . . . . . DEFINED BY (2.5.4.7): PrintableString PrintableString City
86   [1,1,  33]  . . . . 3: RelativeDistinguishedName SET OF
88   [1,1,  31]  . . . . . 0: AttributeTypeAndValue SEQUENCE
90   [1,1,   3]  . . . . . . type: AttributeType OBJECT IDENTIFIER id-at-organizationName (2.5.4.10)
95   [0,0,  26]  . . . . . . value: [UNIV 19] AttributeValue ANY
                 . . . . . . . 13:18:49:6E:74:65:72:6E:65:74:20:57:69:64:67:69
                 . . . . . . . 74:73:20:50:74:79:20:4C:74:64
95   [1,1,  24]  . . . . . . . DEFINED BY (2.5.4.10): PrintableString PrintableString Internet Widgits Pty Ltd

See also

DEFINED BY

Streaming and dealing with huge structures