Skip to content

asnkit

This is the documentation of the Python runtime and of the formats of the files. For the repository as a whole (the runtimes in Python and in Rust, the compiler, the plugins, the codecs, the commands and the known limitations) see the README. The Rust crates are documented by cargo doc. ASN.1 is compiled to .asni files by the compiler asnkitcc, which has no Python API.

Basic usage

asnkitrt.load_asni_file(filename, codec='ber', numeric_enums=False, nonce=None, accept_any_nonce=False, bound_to=None, accept_any_binding=False, verification_keys=None, accept_any_signature=False, decryption_keys=None, lenient=False)

Same as load(), but for the .asni file with given name.

>>> foo = asnkitrt.runtime.load_asni_file('foo.asni')

asnkitrt.load_asni(data, codec='ber', numeric_enums=False, nonce=None, accept_any_nonce=False, bound_to=None, accept_any_binding=False, verification_keys=None, accept_any_signature=False, decryption_keys=None, lenient=False)

Same as load(), but for given contents of an .asni file only.

>>> foo = asnkitrt.runtime.load_asni(data)

asnkitrt.runtime.Specification

Bases: object

This class is used to encode and decode ASN.1 types found in an ASN.1 specification.

Instances of this class are created by the functions load(), load_asni() and load_asni_file().

_decode_length = decode_length instance-attribute

_modules = modules instance-attribute

_types = {} instance-attribute

modules property

A dictionary of all modules in the specification. Unlike types, this attribute contains every type, even if the type name was found in two or more modules.

>>> question = foo.modules['Foo']['Question']
>>> question
Sequence(Question, [Integer(id), IA5String(question)])
>>> question.encode({'id': 1, 'question': 'Is 1+1=3?'})
b'0\x0e\x02\x01\x01\x16\x09Is 1+1=3?'

types property

A dictionary of all unique types in the specification. Types found in two or more modules are not part of this dictionary.

>>> question = foo.types['Question']
>>> question
Sequence(Question, [Integer(id), IA5String(question)])
>>> question.encode({'id': 1, 'question': 'Is 1+1=3?'})
b'0\x0e\x02\x01\x01\x16\x09Is 1+1=3?'

__init__(modules, decode_length, type_checkers, constraints_checkers)

decode(name, data, check_constraints=False)

Decode given bytes object data as given type name and return the decoded data as a dictionary.

If check_constraints is True all objects in data are checked against their ASN.1 type constraints. A ConstraintsError exception is raised if the constraints are not fulfilled. Set check_constraints to False to skip the constraints check and minimize the runtime overhead, but instead allow decoding of values not fulfilling the constraints.

>>> foo.decode('Question', b'0\x0e\x02\x01\x01\x16\x09Is 1+1=3?')
{'id': 1, 'question': 'Is 1+1=3?'}

decode_length(data)

Decode the length of given data data. Returns None if not enough data was given to decode the length.

This method only works for BER and DER codecs with definite length in the first data encoding. Other codecs and combinations lacks length information in the data.

>>> foo.decode_length(b'\x30\x0e\x02\x01\x01')
16

decode_with_length(name, data, check_constraints=False)

Same as decode, but also returns the byte length of the decoded data.

Use to get the length of indefinite-length BER encoded data.

>>> foo.decode_with_length('Question',
                            b'0\x0e\x02\x01\x01\x16\x09Is 1+1=3?')
({'id': 1, 'question': 'Is 1+1=3?'}, 16)

encode(name, data, check_types=True, check_constraints=False, **kwargs)

Encode given dictionary data as given type name and return the encoded data as a bytes object.

If check_types is True all objects in data are checked against the expected Python type for its ASN.1 type. Set check_types to False to minimize the runtime overhead, but instead get less informative error messages.

See Types (in the documentation) for a mapping table from ASN.1 types to Python types.

If check_constraints is True all objects in data are checked against their ASN.1 type constraints. A ConstraintsError exception is raised if the constraints are not fulfilled. Set check_constraints to False to skip the constraints check and minimize the runtime overhead, but instead get less informative error messages and allow encoding of values not fulfilling the constraints.

>>> foo.encode('Question', {'id': 1, 'question': 'Is 1+1=3?'})
b'0\x0e\x02\x01\x01\x16\x09Is 1+1=3?'

Types

ASN.1 types are mapped to Python types as shown in the table below.

ASN.1 type Python type Example
BOOLEAN bool True
INTEGER int (2) 87
REAL float 33.12
NULL -- None
BIT STRING tuple(bytes, int) (b'\x50', 4)
OCTET STRING bytes b'\x44\x1e\xff'
OBJECT IDENTIFIER str '1.33.2'
ENUMERATED str or int (1) 'one' or 1
SEQUENCE dict {'a': 52, 'b': 1}
SEQUENCE OF list [1, 3]
SET dict {'foo': 'bar'}
SET OF list [3, 0, 7]
CHOICE tuple(str, object) ('a', 5)
UTF8String str 'hello'
NumericString str '234359'
PrintableString str 'goo'
IA5String str 'name'
VisibleString str 'gle'
GeneralString str 'abc'
BMPString str 'ko'
GraphicString str 'a b'
TeletexString str 'ßø'
UniversalString str 'åäö'
UTCTime datetime.datetime datetime(2018, 6, 11, 11, 4, 59)
GeneralizedTime datetime.datetime datetime(2018, 1, 31, 5, 0, 47)
DATE datetime.date date(1985, 4, 12)
TIME-OF-DAY datetime.time time(15, 27, 46)
DATE-TIME datetime.datetime datetime(1985, 4, 12, 15, 27, 30)
DURATION str 'P1Y2M3DT4H5M6S'
ObjectDescriptor -- --
  1. Compile with numeric_enums=True for numeric enumeration values instead of strings.

  2. An INTEGER with named numbers can be given as the name of any of them when encoding. It is always an int when decoded.

Advanced usage

asnkitrt.load(compiled, codec='ber', numeric_enums=False, nonce=None, accept_any_nonce=False, bound_to=None, accept_any_binding=False, verification_keys=None, accept_any_signature=False, decryption_keys=None, lenient=False)

Return a Specification object that can be used to encode and decode data structures of given compiled specification compiled with given codec codec. codec may be one of 'ber', 'der', 'gser', 'jer', oer, 'per', 'uper' and 'xer'.

compiled is a CompiledSpecification or the contents of an .asni file as bytes. Nothing else is loaded. A syntax tree, or an .asnst file, has to be compiled first.

Give numeric_enums as True for numeric enumeration values instead of strings.

Give lenient as True to have the 'der' codec decode what BER allows and DER does not, as it only does if it is told to. DER wants a BOOLEAN of 0x00 or 0xff, an INTEGER, an ENUMERATED and an OBJECT IDENTIFIER in as few bytes as possible, a length in as few bytes as possible and never indefinite, a BIT STRING with unused bits of zero and no trailing zero bits in a list of named bits, a SET OF in ascending order of its elements, members in the order they are declared in, nothing encoded that is a DEFAULT, a REAL in its canonical form, and a UTCTime and a GeneralizedTime in UTC with seconds and no trailing zeros. What the codec encodes is DER whatever lenient is, and no other codec looks at it. What is left after the data, as in decode, is never accepted.

nonce, accept_any_nonce, bound_to, accept_any_binding, verification_keys, accept_any_signature and decryption_keys tell which files to read, as for from_asni(). A file that is signed, encrypted, bound to something or has a nonce to check is not read unless told what to expect.

>>> with open('foo.asni', 'rb') as fin:
...     foo = asnkitrt.runtime.load(fin.read())

Reading and writing the files

asnkitrt.syntax.from_asni(data, nonce=None, accept_any_nonce=False, bound_to=None, accept_any_binding=False, verification_keys=None, accept_any_signature=False, decryption_keys=None)

Decode given contents of an .asni file, as returned by :func:to_asni(), to a compiled specification.

nonce is what the nonce of the file has to be: a number it has to be the same as, or a function that is given it and returns True if it will do, as lambda nonce: nonce > last. A file without a nonce to check is then not read. A file with a nonce to check is not read if nonce is not given. With accept_any_nonce as True a file is read whatever its nonce, and whether it has one to check or not.

A function given as nonce must not keep the nonce it is given, as the file may still be found to be bad. Keep that of :func:read_header() once the file has been read instead.

bound_to is what the file has to be bound to. A file that is not bound is then not read. A bound file is not read if bound_to is not given. With accept_any_binding as True a file is read whatever it is bound to, and whether it is bound or not.

verification_keys are the Ed25519 public keys of those trusted to sign files, as a dictionary of the identifier of each key, of 16 bytes, to the key, of 32 bytes. A file is then only read if signed with one of them. A signed file is not read if verification_keys is not given. With accept_any_signature as True a file is read without its signature being checked, and whether it is signed or not.

decryption_keys are the AES-256 keys to decrypt files with, as a dictionary of the identifier of each key, of 16 bytes, to the key, of 32 bytes. A file is then only read if encrypted with one of them. An encrypted file is not read if decryption_keys is not given.

Signed and encrypted files need the cryptography package.

compiled = asnkitrt.syntax.from_asni(data)

asnkitrt.syntax.from_asnst(data)

Decode given contents of an .asnst file, as returned by :func:to_asnst(), to a syntax tree.

tree = asnkitrt.syntax.from_asnst(data)

asnkitrt.syntax.to_asni(compiled, metadata=None, nonce=None, signing_key=None, encryption_key=None)

Encode given compiled specification as the contents of an .asni file, with given metadata.

Give nonce as a number of at most 24 bits for a file with a nonce to check.

Give signing_key for a signed file, as the identifier of the key, of 16 bytes, and the Ed25519 private key, of 32 bytes. Give encryption_key for an encrypted file, as the identifier of the key, of 16 bytes, and the AES-256 key, of 32 bytes. Both need the cryptography package.

with open('foo.asni', 'wb') as fout: ... fout.write(asnkitrt.syntax.to_asni(compiled))

asnkitrt.syntax.to_asnst(tree, metadata=None)

Encode given syntax tree as the contents of an .asnst file, with given metadata.

with open('foo.asnst', 'wb') as fout: ... fout.write(asnkitrt.syntax.to_asnst(tree))