_____
.. autoclass:: pyderasn.SetOf
:members: __init__
+
+Various
+-------
+
+.. autofunction:: pyderasn.hexenc
+.. autofunction:: pyderasn.hexdec
+.. autofunction:: pyderasn.tag_encode
+.. autofunction:: pyderasn.tag_decode
+.. autofunction:: pyderasn.tag_ctxp
+.. autofunction:: pyderasn.tag_ctxc
+.. autoclass:: pyderasn.Obj
"""
from codecs import getdecoder
from datetime import datetime
from math import ceil
+from six import add_metaclass
from six import binary_type
from six import byte2int
from six import indexbytes
class DecodeError(Exception):
def __init__(self, msg="", klass=None, decode_path=(), offset=0):
+ """
+ :param str msg: reason of decode failing
+ :param klass: optional exact DecodeError inherited class (like
+ :py:exc:`NotEnoughData`, :py:exc:`TagMismatch`,
+ :py:exc:`InvalidLength`)
+ :param decode_path: tuple of strings. It contains human
+ readable names of the fields through which
+ decoding process has passed
+ :param int offset: binary offset where failure happened
+ """
super(DecodeError, self).__init__()
self.msg = msg
self.klass = klass
def hexdec(data):
+ """Binary data to hexadecimal string convert
+ """
return _hexdecoder(data)[0]
def hexenc(data):
+ """Hexadecimal string to binary data convert
+ """
return _hexencoder(data)[0].decode("ascii")
def tag_encode(num, klass=TagClassUniversal, form=TagFormPrimitive):
+ """Encode tag to binary form
+
+ :param int num: tag's number
+ :param int klass: tag's class (:py:data:`pyderasn.TagClassUniversal`,
+ :py:data:`pyderasn.TagClassContext`,
+ :py:data:`pyderasn.TagClassApplication`,
+ :py:data:`pyderasn.TagClassPrivate`)
+ :param int form: tag's form (:py:data:`pyderasn.TagFormPrimitive`,
+ :py:data:`pyderasn.TagFormConstructed`)
+ """
if num < 31:
# [XX|X|.....]
return int2byte(klass | form | num)
def tag_decode(tag):
- """
- assume that data is validated
+ """Decode tag from binary form
+
+ .. warning::
+
+ No validation is performed, assuming that it has already passed.
+
+ It returns tuple with three integers, as
+ :py:func:`pyderasn.tag_encode` accepts.
"""
first_octet = byte2int(tag)
klass = first_octet & 0xC0
def tag_ctxp(num):
+ """Create CONTEXT PRIMITIVE tag
+ """
return tag_encode(num=num, klass=TagClassContext, form=TagFormPrimitive)
def tag_ctxc(num):
+ """Create CONTEXT CONSTRUCTED tag
+ """
return tag_encode(num=num, klass=TagClassContext, form=TagFormConstructed)
# Base class
########################################################################
+class AutoAddSlots(type):
+ def __new__(cls, name, bases, _dict):
+ _dict["__slots__"] = _dict.get("__slots__", ())
+ return type.__new__(cls, name, bases, _dict)
+
+
+@add_metaclass(AutoAddSlots)
class Obj(object):
"""Common ASN.1 object class
+
+ All ASN.1 types are inherited from it. It has metaclass that
+ automatically adds ``__slots__`` to all inherited classes.
"""
__slots__ = (
"tag",
@property
def ready(self): # pragma: no cover
+ """Is object ready to be encoded?
+ """
raise NotImplementedError()
def _assert_ready(self):
@property
def decoded(self):
- return self.llen > 0
+ """Is object decoded?
+ """
+ return (self.llen + self.vlen) > 0
def copy(self): # pragma: no cover
+ """Make a copy of object, safe to be mutated
+ """
raise NotImplementedError()
@property
return b"".join((self._expl, len_encode(len(raw)), raw))
def decode(self, data, offset=0, leavemm=False, decode_path=()):
+ """Decode the data
+
+ :param data: either binary or memoryview
+ :param int offset: initial data's offset
+ :param bool leavemm: do we need to leave memoryview of remaining
+ data as is, or convert it to bytes otherwise
+ :returns: (Obj, remaining data)
+ """
tlv = memoryview(data)
if self._expl is None:
obj, tail = self._decode(
def pprint(obj, oids=None, big_blobs=False):
+ """Pretty print object
+
+ :param Obj obj: object you want to pretty print
+ :param oids: ``OID <-> humand readable string`` dictionary. When OID
+ from it is met, then its humand readable form is printed
+ :param big_blobs: if large binary objects are met (like OctetString
+ values), do we need to print them too, on separate
+ lines
+ """
def _pprint_pps(pps):
for pp in pps:
if hasattr(pp, "_fields"):
True
>>> bool(b)
True
- >>> Boolean(optional=True)
- BOOLEAN OPTIONAL
- >>> Boolean(impl=tag_ctxp(1), default=False)
- [1] BOOLEAN False OPTIONAL DEFAULT
"""
__slots__ = ()
tag_default = tag_encode(1)
True
>>> int(b)
-123
- >>> Integer(optional=True)
- INTEGER OPTIONAL
- >>> Integer(impl=tag_ctxp(1), default=123)
- [1] INTEGER 123 OPTIONAL DEFAULT
>>> Integer(2, bounds=(1, 3))
INTEGER 2
########################################################################
class Sequence(Obj):
+ """``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.
+ """
__slots__ = ("specs",)
tag_default = tag_encode(form=TagFormConstructed, num=16)
asn1_type_name = "SEQUENCE"
class Set(Sequence):
+ """``SET`` structure type
+
+ Its usage is identical to :py:class:`pyderasn.Sequence`.
+ """
__slots__ = ()
tag_default = tag_encode(form=TagFormConstructed, num=17)
asn1_type_name = "SET"
class SequenceOf(Obj):
+ """``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)])
+ """
__slots__ = ("spec", "_bound_min", "_bound_max")
tag_default = tag_encode(form=TagFormConstructed, num=16)
asn1_type_name = "SEQUENCE OF"
class SetOf(SequenceOf):
+ """``SET OF`` sequence type
+
+ Its usage is identical to :py:class:`pyderasn.SequenceOf`.
+ """
__slots__ = ()
tag_default = tag_encode(form=TagFormConstructed, num=17)
asn1_type_name = "SET OF"
parser.add_argument(
"DERFile",
type=argparse.FileType("rb"),
- help="Python path to schema definition to use",
+ help="Path to DER file you want to decode",
)
args = parser.parse_args()
der = memoryview(args.DERFile.read())