Library reference

Python ASN.1 DER codec with abstract structures

This library allows you to marshal and unmarshal various structures in ASN.1 DER format, like this:

>>> i = Integer(123)
>>> raw = i.encode()
>>> Integer().decode(raw) == i
True

There are primitive types, holding single values (pyderasn.BitString, pyderasn.Boolean, pyderasn.Enumerated, pyderasn.GeneralizedTime, pyderasn.Integer, pyderasn.Null, pyderasn.ObjectIdentifier, pyderasn.OctetString, pyderasn.UTCTime, various strings (pyderasn.BMPString, pyderasn.GeneralString, pyderasn.GraphicString, pyderasn.IA5String, pyderasn.ISO646String, pyderasn.NumericString, pyderasn.PrintableString, pyderasn.T61String, pyderasn.TeletexString, pyderasn.UniversalString, pyderasn.UTF8String, pyderasn.VideotexString, pyderasn.VisibleString)), constructed types, holding multiple primitive types (pyderasn.Sequence, pyderasn.SequenceOf, pyderasn.Set, pyderasn.SetOf), and special types like pyderasn.Any and pyderasn.Choice.

Common for most types

Tags

Most types in ASN.1 has specific tag for them. Obj.tag_default is the default tag used during coding process. You can override it with either IMPLICIT (using impl keyword argument), or EXPLICIT one (using expl keyword argument). Both arguments takes raw binary string, containing that tag. You can not set implicit and explicit tags simultaneously.

There are pyderasn.tag_ctxp() and pyderasn.tag_ctxc() functions, allowing you to easily create CONTEXT PRIMITIVE/CONSTRUCTED tags, by specifying only the required tag number. Pay attention that explicit tags always have constructed tag (tag_ctxc), but implicit tags for primitive types are primitive (tag_ctxp).

>>> Integer(impl=tag_ctxp(1))
[1] INTEGER
>>> Integer(expl=tag_ctxc(2))
[2] EXPLICIT INTEGER

Implicit tag is not explicitly shown.

Two object of the same type, but with different implicit/explicit tags are not equal.

You can get objects effective tag (either default or implicited) through tag property. You can decode it using pyderasn.tag_decode() function:

>>> tag_decode(tag_ctxc(123))
(128, 32, 123)
>>> klass, form, num = tag_decode(tag_ctxc(123))
>>> klass == TagClassContext
True
>>> form == TagFormConstructed
True

To determine if object has explicit tag, use expled boolean property and expl_tag property, returning explicit tag’s value.

Default/optional

Many objects in sequences could be OPTIONAL and could have DEFAULT value. You can specify that object’s property using corresponding keyword arguments.

>>> Integer(optional=True, default=123)
INTEGER 123 OPTIONAL DEFAULT

Those specifications do not play any role in primitive value encoding, but are taken into account when dealing with sequences holding them. For example TBSCertificate sequence holds defaulted, explicitly tagged version field:

class Version(Integer):
    schema = (
        ("v1", 0),
        ("v2", 1),
        ("v3", 2),
    )
class TBSCertificate(Sequence):
    schema = (
        ("version", Version(expl=tag_ctxc(0), default="v1")),
    [...]

When default argument is used and value is not specified, then it equals to default one.

Size constraints

Some objects give ability to set value size constraints. This is either possible integer value, or allowed length of various strings and sequences. Constraints are set in the following way:

class X(...):
    bounds = (MIN, MAX)

And values satisfaction is checked as: MIN <= X <= MAX.

For simplicity you can also set bounds the following way:

bounded_x = X(bounds=(MIN, MAX))

If bounds are not satisfied, then pyderasn.BoundsError is raised.

Common methods

All objects have ready boolean property, that tells if it is ready to be encoded. If that kind of action is performed on unready object, then pyderasn.ObjNotReady exception will be raised.

All objects have copy() method, returning its copy, that can be safely mutated.

Decoding

Decoding is performed using decode() method. offset optional argument could be used to set initial object’s offset in the binary data, for convenience. It returns decoded object and remaining unmarshalled data (tail). Internally all work is done on memoryview(data), and you can leave returning tail as a memoryview, by specifying leavemm=True argument.

When object is decoded, decoded property is true and you can safely use following properties:

  • offset – position from initial offset where object’s tag is started
  • tlen – length of object’s tag
  • llen – length of object’s length value
  • vlen – length of object’s value
  • tlvlen – length of the whole object

Pay attention that those values do not include anything related to explicit tag. If you want to know information about it, then use: expled (to know if explicit tag is set), expl_offset (it is lesser than offset), expl_tlen, expl_llen, expl_vlen (that actually equals to ordinary tlvlen).

When error occurs, then pyderasn.DecodeError is raised.

Pretty printing

All objects have pps() method, that is a generator of pyderasn.PP namedtuple, holding various raw information about the object. If pps is called on sequences, then all underlying PP will be yielded.

You can use pyderasn.pp_console_row() function, converting those PP to human readable string. Actually exactly it is used for all object repr. But it is easy to write custom formatters.

>>> from pyderasn import pprint
>>> encoded = Integer(-12345).encode()
>>> obj, tail = Integer().decode(encoded)
>>> print(pprint(obj))
    0   [1,1,   2] INTEGER -12345

Primitive types

Boolean

class pyderasn.Boolean(value=None, impl=None, expl=None, default=None, optional=False, _decoded=(0, 0, 0))

BOOLEAN boolean type

>>> b = Boolean(True)
BOOLEAN True
>>> b == Boolean(True)
True
>>> bool(b)
True
__init__(value=None, impl=None, expl=None, default=None, optional=False, _decoded=(0, 0, 0))
Parameters:
  • value – set the value. Either boolean type, or pyderasn.Boolean object
  • impl (bytes) – override default tag with IMPLICIT one
  • expl (bytes) – override default tag with EXPLICIT one
  • default – set default value. Type same as in value
  • optional (bool) – is object OPTIONAL in sequence

Integer

class pyderasn.Integer(value=None, bounds=None, impl=None, expl=None, default=None, optional=False, _specs=None, _decoded=(0, 0, 0))

INTEGER integer type

>>> b = Integer(-123)
INTEGER -123
>>> b == Integer(-123)
True
>>> int(b)
-123
>>> Integer(2, bounds=(1, 3))
INTEGER 2
>>> Integer(5, bounds=(1, 3))
Traceback (most recent call last):
pyderasn.BoundsError: unsatisfied bounds: 1 <= 5 <= 3
class Version(Integer):
    schema = (
        ("v1", 0),
        ("v2", 1),
        ("v3", 2),
    )
>>> v = Version("v1")
Version INTEGER v1
>>> int(v)
0
>>> v.named
'v1'
>>> v.specs
{'v3': 2, 'v1': 0, 'v2': 1}
__init__(value=None, bounds=None, impl=None, expl=None, default=None, optional=False, _specs=None, _decoded=(0, 0, 0))
Parameters:
  • value – set the value. Either integer type, named value (if schema is specified in the class), or pyderasn.Integer object
  • bounds – set (MIN, MAX) value constraint. (-inf, +inf) by default
  • impl (bytes) – override default tag with IMPLICIT one
  • expl (bytes) – override default tag with EXPLICIT one
  • default – set default value. Type same as in value
  • optional (bool) – is object OPTIONAL in sequence

BitString

class pyderasn.BitString(value=None, impl=None, expl=None, default=None, optional=False, _specs=None, _decoded=(0, 0, 0))

BIT STRING bit string type

>>> BitString(b"hello world")
BIT STRING 88 bits 68656c6c6f20776f726c64
>>> bytes(b)
b'hello world'
>>> b == b"hello world"
True
>>> b.bit_len
88
>>> b = BitString("'010110000000'B")
BIT STRING 12 bits 5800
>>> b.bit_len
12
>>> b[0], b[1], b[2], b[3]
(False, True, False, True)
>>> b[1000]
False
>>> [v for v in b]
[False, True, False, True, True, False, False, False, False, False, False, False]
class KeyUsage(BitString):
    schema = (
        ('digitalSignature', 0),
        ('nonRepudiation', 1),
        ('keyEncipherment', 2),
    )
>>> b = KeyUsage(('keyEncipherment', 'nonRepudiation'))
KeyUsage BIT STRING 3 bits nonRepudiation, keyEncipherment
>>> b.named
['nonRepudiation', 'keyEncipherment']
>>> b.specs
{'nonRepudiation': 1, 'digitalSignature': 0, 'keyEncipherment': 2}
__init__(value=None, impl=None, expl=None, default=None, optional=False, _specs=None, _decoded=(0, 0, 0))
Parameters:
  • value – set the value. Either binary type, tuple of named values (if schema is specified in the class), string in 'XXX...'B form, or pyderasn.BitString object
  • impl (bytes) – override default tag with IMPLICIT one
  • expl (bytes) – override default tag with EXPLICIT one
  • default – set default value. Type same as in value
  • optional (bool) – is object OPTIONAL in sequence

OctetString

class pyderasn.OctetString(value=None, bounds=None, impl=None, expl=None, default=None, optional=False, _decoded=(0, 0, 0))

OCTET STRING binary string type

>>> s = OctetString(b"hello world")
OCTET STRING 11 bytes 68656c6c6f20776f726c64
>>> s == OctetString(b"hello world")
True
>>> bytes(s)
b'hello world'
>>> OctetString(b"hello", bounds=(4, 4))
Traceback (most recent call last):
pyderasn.BoundsError: unsatisfied bounds: 4 <= 5 <= 4
>>> OctetString(b"hell", bounds=(4, 4))
OCTET STRING 4 bytes 68656c6c
__init__(value=None, bounds=None, impl=None, expl=None, default=None, optional=False, _decoded=(0, 0, 0))
Parameters:
  • value – set the value. Either binary type, or pyderasn.OctetString object
  • bounds – set (MIN, MAX) value size constraint. (-inf, +inf) by default
  • impl (bytes) – override default tag with IMPLICIT one
  • expl (bytes) – override default tag with EXPLICIT one
  • default – set default value. Type same as in value
  • optional (bool) – is object OPTIONAL in sequence

Null

class pyderasn.Null(value=None, impl=None, expl=None, optional=False, _decoded=(0, 0, 0))

NULL null object

>>> n = Null()
NULL
>>> n.ready
True
__init__(value=None, impl=None, expl=None, optional=False, _decoded=(0, 0, 0))
Parameters:
  • impl (bytes) – override default tag with IMPLICIT one
  • expl (bytes) – override default tag with EXPLICIT one
  • optional (bool) – is object OPTIONAL in sequence

ObjectIdentifier

class pyderasn.ObjectIdentifier(value=None, impl=None, expl=None, default=None, optional=False, _decoded=(0, 0, 0))

OBJECT IDENTIFIER OID type

>>> oid = ObjectIdentifier((1, 2, 3))
OBJECT IDENTIFIER 1.2.3
>>> oid == ObjectIdentifier("1.2.3")
True
>>> tuple(oid)
(1, 2, 3)
>>> str(oid)
'1.2.3'
>>> oid + (4, 5) + ObjectIdentifier("1.7")
OBJECT IDENTIFIER 1.2.3.4.5.1.7
>>> str(ObjectIdentifier((3, 1)))
Traceback (most recent call last):
pyderasn.InvalidOID: unacceptable first arc value
__init__(value=None, impl=None, expl=None, default=None, optional=False, _decoded=(0, 0, 0))
Parameters:
  • value – set the value. Either tuples of integers, string of “.”-concatenated integers, or pyderasn.ObjectIdentifier object
  • impl (bytes) – override default tag with IMPLICIT one
  • expl (bytes) – override default tag with EXPLICIT one
  • default – set default value. Type same as in value
  • optional (bool) – is object OPTIONAL in sequence

Enumerated

class pyderasn.Enumerated(value=None, impl=None, expl=None, default=None, optional=False, _specs=None, _decoded=(0, 0, 0), bounds=None)

ENUMERATED integer type

This type is identical to pyderasn.Integer, but requires schema to be specified and does not accept values missing from it.

CommonString

class pyderasn.CommonString(value=None, bounds=None, impl=None, expl=None, default=None, optional=False, _decoded=(0, 0, 0))

Common class for all strings

Everything resembles pyderasn.OctetString, except ability to deal with unicode text strings.

>>> hexenc("привет мир".encode("utf-8"))
'd0bfd180d0b8d0b2d0b5d18220d0bcd0b8d180'
>>> UTF8String("привет мир") == UTF8String(hexdec("d0...80"))
True
>>> s = UTF8String("привет мир")
UTF8String UTF8String привет мир
>>> str(s)
'привет мир'
>>> hexenc(bytes(s))
'd0bfd180d0b8d0b2d0b5d18220d0bcd0b8d180'
>>> PrintableString("привет мир")
Traceback (most recent call last):
UnicodeEncodeError: 'ascii' codec can't encode characters in position 0-5: ordinal not in range(128)
>>> BMPString("ада", bounds=(2, 2))
Traceback (most recent call last):
pyderasn.BoundsError: unsatisfied bounds: 2 <= 3 <= 2
>>> s = BMPString("ад", bounds=(2, 2))
>>> s.encoding
'utf-16-be'
>>> hexenc(bytes(s))
'04300434'
Class Text Encoding
pyderasn.UTF8String utf-8
pyderasn.NumericString ascii
pyderasn.PrintableString ascii
pyderasn.TeletexString ascii
pyderasn.T61String ascii
pyderasn.VideotexString iso-8859-1
pyderasn.IA5String ascii
pyderasn.GraphicString iso-8859-1
pyderasn.VisibleString ascii
pyderasn.ISO646String ascii
pyderasn.GeneralString iso-8859-1
pyderasn.UniversalString utf-32-be
pyderasn.BMPString utf-16-be

UTCTime

class pyderasn.UTCTime(value=None, impl=None, expl=None, default=None, optional=False, _decoded=(0, 0, 0), bounds=None)

UTCTime datetime type

>>> t = UTCTime(datetime(2017, 9, 30, 22, 7, 50, 123))
UTCTime UTCTime 2017-09-30T22:07:50
>>> str(t)
'170930220750Z'
>>> bytes(t)
b'170930220750Z'
>>> t.todatetime()
datetime.datetime(2017, 9, 30, 22, 7, 50)
>>> UTCTime(datetime(2057, 9, 30, 22, 7, 50)).todatetime()
datetime.datetime(1957, 9, 30, 22, 7, 50)
__init__(value=None, impl=None, expl=None, default=None, optional=False, _decoded=(0, 0, 0), bounds=None)
Parameters:
  • value – set the value. Either datetime type, or pyderasn.UTCTime object
  • impl (bytes) – override default tag with IMPLICIT one
  • expl (bytes) – override default tag with EXPLICIT one
  • default – set default value. Type same as in value
  • optional (bool) – is object OPTIONAL in sequence
todatetime()

Convert to datetime

Returns:datetime

Pay attention that UTCTime can not hold full year, so all years having < 50 years are treated as 20xx, 19xx otherwise, according to X.509 recomendation.

GeneralizedTime

class pyderasn.GeneralizedTime(value=None, impl=None, expl=None, default=None, optional=False, _decoded=(0, 0, 0), bounds=None)

GeneralizedTime datetime type

This type is similar to pyderasn.UTCTime.

>>> t = GeneralizedTime(datetime(2017, 9, 30, 22, 7, 50, 123))
GeneralizedTime GeneralizedTime 2017-09-30T22:07:50.000123
>>> str(t)
'20170930220750.000123Z'
>>> t = GeneralizedTime(datetime(2057, 9, 30, 22, 7, 50))
GeneralizedTime GeneralizedTime 2057-09-30T22:07:50

Special types

Choice

class pyderasn.Choice(value=None, schema=None, impl=None, expl=None, default=None, optional=False, _decoded=(0, 0, 0))

CHOICE special type

class GeneralName(Choice):
    schema = (
        ('rfc822Name', IA5String(impl=tag_ctxp(1))),
        ('dNSName', IA5String(impl=tag_ctxp(2))),
    )
>>> gn = GeneralName()
GeneralName CHOICE
>>> gn["rfc822Name"] = IA5String("foo@bar.baz")
GeneralName CHOICE rfc822Name[[1] IA5String IA5 foo@bar.baz]
>>> gn["dNSName"] = IA5String("bar.baz")
GeneralName CHOICE dNSName[[2] IA5String IA5 bar.baz]
>>> gn["rfc822Name"]
None
>>> gn["dNSName"]
[2] IA5String IA5 bar.baz
>>> gn.choice
'dNSName'
>>> gn.value == gn["dNSName"]
True
>>> gn.specs
OrderedDict([('rfc822Name', [1] IA5String IA5), ('dNSName', [2] IA5String IA5)])
>>> GeneralName(("rfc822Name", IA5String("foo@bar.baz")))
GeneralName CHOICE rfc822Name[[1] IA5String IA5 foo@bar.baz]
__init__(value=None, schema=None, impl=None, expl=None, default=None, optional=False, _decoded=(0, 0, 0))
Parameters:
  • value – set the value. Either (choice, value) tuple, or pyderasn.Choice object
  • impl (bytes) – can not be set, do not use it
  • expl (bytes) – override default tag with EXPLICIT one
  • default – set default value. Type same as in value
  • optional (bool) – is object OPTIONAL in sequence

PrimitiveTypes

class pyderasn.PrimitiveTypes(value=None, schema=None, impl=None, expl=None, default=None, optional=False, _decoded=(0, 0, 0))

Predefined CHOICE for all generic primitive types

It could be useful for general decoding of some unspecified values:

>>> PrimitiveTypes().decode(hexdec("0403666f6f"))[0].value
OCTET STRING 3 bytes 666f6f
>>> PrimitiveTypes().decode(hexdec("0203123456"))[0].value
INTEGER 1193046

Any

class pyderasn.Any(value=None, expl=None, optional=False, _decoded=(0, 0, 0))

ANY special type

>>> Any(Integer(-123))
ANY 020185
>>> a = Any(OctetString(b"hello world").encode())
ANY 040b68656c6c6f20776f726c64
>>> hexenc(bytes(a))
b'0x040x0bhello world'
__init__(value=None, expl=None, optional=False, _decoded=(0, 0, 0))
Parameters:
  • value – set the value. Either any kind of pyderasn’s ready object, or bytes. Pay attention that no validation is performed is raw binary value is valid TLV
  • expl (bytes) – override default tag with EXPLICIT one
  • optional (bool) – is object OPTIONAL in sequence

Constructed types

Sequence

class pyderasn.Sequence(value=None, schema=None, impl=None, expl=None, default=None, optional=False, _decoded=(0, 0, 0))

SEQUENCE structure type

You have to make specification of sequence:

class Extension(Sequence):
    __slots__ = ()
    schema = (
        ("extnID", ObjectIdentifier()),
        ("critical", Boolean(default=False)),
        ("extnValue", OctetString()),
    )

Then, you can work with it as with dictionary.

>>> ext = Extension()
>>> Extension().specs
OrderedDict([
    ('extnID', OBJECT IDENTIFIER),
    ('critical', BOOLEAN False OPTIONAL DEFAULT),
    ('extnValue', OCTET STRING),
])
>>> ext["extnID"] = "1.2.3"
Traceback (most recent call last):
pyderasn.InvalidValueType: invalid value type, expected: <class 'pyderasn.ObjectIdentifier'>
>>> ext["extnID"] = ObjectIdentifier("1.2.3")

You can know if sequence is ready to be encoded:

>>> ext.ready
False
>>> ext.encode()
Traceback (most recent call last):
pyderasn.ObjNotReady: object is not ready: extnValue
>>> ext["extnValue"] = OctetString(b"foobar")
>>> ext.ready
True

Value you want to assign, must have the same type as in corresponding specification, but it can have different tags, optional/default attributes – they will be taken from specification automatically:

class TBSCertificate(Sequence):
    schema = (
        ("version", Version(expl=tag_ctxc(0), default="v1")),
    [...]
>>> tbs = TBSCertificate()
>>> tbs["version"] = Version("v2") # no need to explicitly add ``expl``

You can know if value exists/set in the sequence and take its value:

>>> "extnID" in ext, "extnValue" in ext, "critical" in ext
(True, True, False)
>>> ext["extnID"]
OBJECT IDENTIFIER 1.2.3

But pay attention that if value has default, then it won’t be (not in) in the sequence (because DEFAULT must not be encoded in DER), but you can read its value:

>>> "critical" in ext, ext["critical"]
(False, BOOLEAN False)
>>> ext["critical"] = Boolean(True)
>>> "critical" in ext, ext["critical"]
(True, BOOLEAN True)

All defaulted values are always optional.

Warning

When decoded DER contains defaulted value inside, then technically this is not valid DER encoding. But we allow and pass it. Of course reencoding of that kind of DER will result in different binary representation (validly without defaulted value inside).

Two sequences are equal if they have equal specification (schema), implicit/explicit tagging and the same values.

Set

class pyderasn.Set(value=None, schema=None, impl=None, expl=None, default=None, optional=False, _decoded=(0, 0, 0))

SET structure type

Its usage is identical to pyderasn.Sequence.

SequenceOf

class pyderasn.SequenceOf(value=None, schema=None, bounds=None, impl=None, expl=None, default=None, optional=False, _decoded=(0, 0, 0))

SEQUENCE OF sequence type

For that kind of type you must specify the object it will carry on (bounds are for example here, not required):

class Ints(SequenceOf):
    schema = Integer()
    bounds = (0, 2)
>>> ints = Ints()
>>> ints.append(Integer(123))
>>> ints.append(Integer(234))
>>> ints
Ints SEQUENCE OF[INTEGER 123, INTEGER 234]
>>> [int(i) for i in ints]
[123, 234]
>>> ints.append(Integer(345))
Traceback (most recent call last):
pyderasn.BoundsError: unsatisfied bounds: 0 <= 3 <= 2
>>> ints[1]
INTEGER 234
>>> ints[1] = Integer(345)
>>> ints
Ints SEQUENCE OF[INTEGER 123, INTEGER 345]

Also you can initialize sequence with preinitialized values:

>>> ints = Ints([Integer(123), Integer(234)])

SetOf

class pyderasn.SetOf(value=None, schema=None, bounds=None, impl=None, expl=None, default=None, optional=False, _decoded=(0, 0, 0))

SET OF sequence type

Its usage is identical to pyderasn.SequenceOf.

Various

pyderasn.hexenc(data)

Hexadecimal string to binary data convert

pyderasn.hexdec(data)

Binary data to hexadecimal string convert

pyderasn.tag_encode(num, klass=0, form=0)

Encode tag to binary form

Parameters:
  • num (int) – tag’s number
  • klass (int) – tag’s class (pyderasn.TagClassUniversal, pyderasn.TagClassContext, pyderasn.TagClassApplication, pyderasn.TagClassPrivate)
  • form (int) – tag’s form (pyderasn.TagFormPrimitive, pyderasn.TagFormConstructed)
pyderasn.tag_decode(tag)

Decode tag from binary form

Warning

No validation is performed, assuming that it has already passed.

It returns tuple with three integers, as pyderasn.tag_encode() accepts.

pyderasn.tag_ctxp(num)

Create CONTEXT PRIMITIVE tag

pyderasn.tag_ctxc(num)

Create CONTEXT CONSTRUCTED tag

class pyderasn.Obj(impl=None, expl=None, default=None, optional=False, _decoded=(0, 0, 0))

Common ASN.1 object class

All ASN.1 types are inherited from it. It has metaclass that automatically adds __slots__ to all inherited classes.