#!/usr/bin/env python
# coding: utf-8
-# PyDERASN -- Python ASN.1 DER codec with abstract structures
-# Copyright (C) 2017-2018 Sergey Matveev <stargrave@stargrave.org>
+# PyDERASN -- Python ASN.1 DER/BER codec with abstract structures
+# Copyright (C) 2017-2019 Sergey Matveev <stargrave@stargrave.org>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as
# You should have received a copy of the GNU Lesser General Public
# License along with this program. If not, see
# <http://www.gnu.org/licenses/>.
-"""Python ASN.1 DER codec with abstract structures
+"""Python ASN.1 DER/BER codec with abstract structures
-This library allows you to marshal and unmarshal various structures in
-ASN.1 DER format, like this:
+This library allows you to marshal various structures in ASN.1 DER
+format, unmarshal them in BER/CER/DER ones.
>>> i = Integer(123)
>>> raw = i.encode()
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 :py:exc:`pyderasn.DecodeError` is raised.
+* ``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``)
+* ``fulloffset`` -- it equals to ``expl_offset`` if explicit tag is set,
+ ``offset`` otherwise
+* ``fulllen`` -- it equals to ``expl_len`` if explicit tag is set,
+ ``tlvlen`` otherwise
+
+When error occurs, :py:exc:`pyderasn.DecodeError` is raised.
.. _ctx:
Currently available context options:
+* :ref:`allow_default_values <allow_default_values_ctx>`
+* :ref:`allow_expl_oob <allow_expl_oob_ctx>`
+* :ref:`allow_unordered_set <allow_unordered_set_ctx>`
+* :ref:`bered <bered_ctx>`
* :ref:`defines_by_path <defines_by_path_ctx>`
-* :ref:`strict_default_existence <strict_default_existence_ctx>`
.. _pprinting:
``defines`` kwarg is a sequence. You can specify defined field
relatively or absolutely to current decode path. For example ``defines``
for AlgorithmIdentifier of X.509's
-``tbsCertificate.subjectPublicKeyInfo.algorithm.algorithm``::
+``tbsCertificate:subjectPublicKeyInfo:algorithm:algorithm``::
(
- (('parameters',), {
+ (("parameters",), {
id_ecPublicKey: ECParameters(),
id_GostR3410_2001: GostR34102001PublicKeyParameters(),
}),
- (('..', 'subjectPublicKey'), {
+ (("..", "subjectPublicKey"), {
id_rsaEncryption: RSAPublicKey(),
id_GostR3410_2001: OctetString(),
}),
* :py:class:`pyderasn.BitString` (that is multiple of 8 bits)
* :py:class:`pyderasn.OctetString`
* :py:class:`pyderasn.SequenceOf`/:py:class:`pyderasn.SetOf`
- ``Any``/``OctetString``-s
+ ``Any``/``BitString``/``OctetString``-s
When any of those fields is automatically decoded, then ``.defined``
attribute contains ``(OID, value)`` tuple. ``OID`` tells by which OID it
was defined, ``value`` contains corresponding decoded value. For example
-above, ``content_info["content"].defined == (id_signedData,
-signed_data)``.
+above, ``content_info["content"].defined == (id_signedData, signed_data)``.
.. _defines_by_path_ctx:
(
(
"content",
- decode_path_defby(id_signedData),
+ DecodePathDefBy(id_signedData),
"encapContentInfo",
"eContentType",
),
(
(
"content",
- decode_path_defby(id_signedData),
+ DecodePathDefBy(id_signedData),
"encapContentInfo",
"eContent",
- decode_path_defby(id_cct_PKIResponse),
+ DecodePathDefBy(id_cct_PKIResponse),
"controlSequence",
any,
"attrType",
),
))
-Pay attention for :py:func:`pyderasn.decode_path_defby` and ``any``.
+Pay attention for :py:class:`pyderasn.DecodePathDefBy` and ``any``.
First function is useful for path construction when some automatic
decoding is already done. ``any`` means literally any value it meet --
useful for SEQUENCE/SET OF-s.
+.. _bered_ctx:
+
+BER encoding
+------------
+
+By default PyDERASN accepts only DER encoded data. It always encodes to
+DER. But you can optionally enable BER decoding with setting ``bered``
+:ref:`context <ctx>` argument to True. Indefinite lengths and
+constructed primitive types should be parsed successfully.
+
+* If object is encoded in BER form (not the DER one), then ``ber_encoded``
+ attribute is set to True. Only ``BOOLEAN``, ``BIT STRING``, ``OCTET
+ STRING``, ``OBJECT IDENTIFIER``, ``SEQUENCE``, ``SET``, ``SET OF``
+ can contain it.
+* If object has an indefinite length encoding, then its ``lenindef``
+ attribute is set to True. Only ``BIT STRING``, ``OCTET STRING``,
+ ``SEQUENCE``, ``SET``, ``SEQUENCE OF``, ``SET OF``, ``ANY`` can
+ contain it.
+* If object has an indefinite length encoded explicit tag, then
+ ``expl_lenindef`` is set to True.
+* If object has either any of BER-related encoding (explicit tag
+ indefinite length, object's indefinite length, BER-encoding) or any
+ underlying component has that kind of encoding, then ``bered``
+ attribute is set to True. For example SignedData CMS can have
+ ``ContentInfo:content:signerInfos:*`` ``bered`` value set to True, but
+ ``ContentInfo:content:signerInfos:*:signedAttrs`` won't.
+
+EOC (end-of-contents) token's length is taken in advance in object's
+value length.
+
+.. _allow_expl_oob_ctx:
+
+Allow explicit tag out-of-bound
+-------------------------------
+
+Invalid BER encoding could contain ``EXPLICIT`` tag containing more than
+one value, more than one object. If you set ``allow_expl_oob`` context
+option to True, then no error will be raised and that invalid encoding
+will be silently further processed. But pay attention that offsets and
+lengths will be invalid in that case.
+
+.. warning::
+
+ This option should be used only for skipping some decode errors, just
+ to see the decoded structure somehow.
+
Primitive types
---------------
____________
.. autoclass:: pyderasn.CommonString
+NumericString
+_____________
+.. autoclass:: pyderasn.NumericString
+
UTCTime
_______
.. autoclass:: pyderasn.UTCTime
-------
.. autofunction:: pyderasn.abs_decode_path
+.. autofunction:: pyderasn.colonize_hex
.. autofunction:: pyderasn.hexenc
.. autofunction:: pyderasn.hexdec
.. autofunction:: pyderasn.tag_encode
.. autofunction:: pyderasn.tag_ctxp
.. autofunction:: pyderasn.tag_ctxc
.. autoclass:: pyderasn.Obj
+.. autoclass:: pyderasn.DecodeError
+ :members: __init__
+.. autoclass:: pyderasn.NotEnoughData
+.. autoclass:: pyderasn.LenIndefForm
+.. autoclass:: pyderasn.TagMismatch
+.. autoclass:: pyderasn.InvalidLength
+.. autoclass:: pyderasn.InvalidOID
+.. autoclass:: pyderasn.ObjUnknown
+.. autoclass:: pyderasn.ObjNotReady
+.. autoclass:: pyderasn.InvalidValueType
+.. autoclass:: pyderasn.BoundsError
"""
from codecs import getdecoder
from codecs import getencoder
from collections import namedtuple
from collections import OrderedDict
+from copy import copy
from datetime import datetime
from math import ceil
+from os import environ
+from string import ascii_letters
+from string import digits
from six import add_metaclass
from six import binary_type
from six import PY2
from six import string_types
from six import text_type
+from six import unichr as six_unichr
from six.moves import xrange as six_xrange
+try:
+ from termcolor import colored
+except ImportError: # pragma: no cover
+ def colored(what, *args):
+ return what
+
+
__all__ = (
"Any",
"BitString",
"Boolean",
"BoundsError",
"Choice",
- "decode_path_defby",
"DecodeError",
+ "DecodePathDefBy",
"Enumerated",
"GeneralizedTime",
"GeneralString",
"InvalidOID",
"InvalidValueType",
"ISO646String",
+ "LenIndefForm",
"NotEnoughData",
"Null",
"NumericString",
TagClassPrivate: "PRIVATE ",
TagClassUniversal: "UNIV ",
}
+EOC = b"\x00\x00"
+EOC_LEN = len(EOC)
+LENINDEF = b"\x80" # length indefinite mark
+LENINDEF_PP_CHAR = "I" if PY2 else "∞"
########################################################################
# Errors
########################################################################
-class DecodeError(Exception):
+class ASN1Error(ValueError):
+ pass
+
+
+class DecodeError(ASN1Error):
def __init__(self, msg="", klass=None, decode_path=(), offset=0):
"""
:param str msg: reason of decode failing
c for c in (
"" if self.klass is None else self.klass.__name__,
(
- ("(%s)" % ".".join(self.decode_path))
+ ("(%s)" % ":".join(str(dp) for dp in self.decode_path))
if len(self.decode_path) > 0 else ""
),
("(at %d)" % self.offset) if self.offset > 0 else "",
pass
+class LenIndefForm(DecodeError):
+ pass
+
+
class TagMismatch(DecodeError):
pass
pass
-class ObjUnknown(ValueError):
+class ObjUnknown(ASN1Error):
def __init__(self, name):
super(ObjUnknown, self).__init__()
self.name = name
return "%s(%s)" % (self.__class__.__name__, self)
-class ObjNotReady(ValueError):
+class ObjNotReady(ASN1Error):
def __init__(self, name):
super(ObjNotReady, self).__init__()
self.name = name
return "%s(%s)" % (self.__class__.__name__, self)
-class InvalidValueType(ValueError):
+class InvalidValueType(ASN1Error):
def __init__(self, expected_types):
super(InvalidValueType, self).__init__()
self.expected_types = expected_types
return "%s(%s)" % (self.__class__.__name__, self)
-class BoundsError(ValueError):
+class BoundsError(ASN1Error):
def __init__(self, bound_min, value, bound_max):
super(BoundsError, self).__init__()
self.bound_min = bound_min
def len_decode(data):
+ """Decode length
+
+ :returns: (decoded length, length's length, remaining data)
+ :raises LenIndefForm: if indefinite form encoding is met
+ """
if len(data) == 0:
raise NotEnoughData("no data at all")
first_octet = byte2int(data)
if octets_num + 1 > len(data):
raise NotEnoughData("encoded length is longer than data")
if octets_num == 0:
- raise DecodeError("long form instead of short one")
+ raise LenIndefForm()
if byte2int(data[1:]) == 0:
raise DecodeError("leading zeros")
l = 0
"offset",
"llen",
"vlen",
+ "expl_lenindef",
+ "lenindef",
+ "ber_encoded",
)
def __init__(
self.tag = getattr(self, "impl", self.tag_default) if impl is None else impl
self._expl = getattr(self, "expl", None) if expl is None else expl
if self.tag != self.tag_default and self._expl is not None:
- raise ValueError(
- "implicit and explicit tags can not be set simultaneously"
- )
+ raise ValueError("implicit and explicit tags can not be set simultaneously")
if default is not None:
optional = True
self.optional = optional
self.offset, self.llen, self.vlen = _decoded
self.default = None
+ self.expl_lenindef = False
+ self.lenindef = False
+ self.ber_encoded = False
@property
def ready(self): # pragma: no cover
if not self.ready:
raise ObjNotReady(self.__class__.__name__)
+ @property
+ def bered(self):
+ """Is either object or any elements inside is BER encoded?
+ """
+ return self.expl_lenindef or self.lenindef or self.ber_encoded
+
@property
def decoded(self):
"""Is object decoded?
def _encode(self): # pragma: no cover
raise NotImplementedError()
- def _decode(self, tlv, offset, decode_path, ctx): # pragma: no cover
+ def _decode(self, tlv, offset, decode_path, ctx, tag_only): # pragma: no cover
raise NotImplementedError()
def encode(self):
return raw
return b"".join((self._expl, len_encode(len(raw)), raw))
- def decode(self, data, offset=0, leavemm=False, decode_path=(), ctx=None):
+ def decode(
+ self,
+ data,
+ offset=0,
+ leavemm=False,
+ decode_path=(),
+ ctx=None,
+ tag_only=False,
+ _ctx_immutable=True,
+ ):
"""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
- :param ctx: optional :ref:`context <ctx>` governing decoding process.
+ :param ctx: optional :ref:`context <ctx>` governing decoding process
+ :param tag_only: decode only the tag, without length and contents
+ (used only in Choice and Set structures, trying to
+ determine if tag satisfies the scheme)
+ :param _ctx_immutable: do we need to copy ``ctx`` before using it
:returns: (Obj, remaining data)
"""
if ctx is None:
ctx = {}
+ elif _ctx_immutable:
+ ctx = copy(ctx)
tlv = memoryview(data)
if self._expl is None:
- obj, tail = self._decode(
+ result = self._decode(
tlv,
offset,
decode_path=decode_path,
ctx=ctx,
+ tag_only=tag_only,
)
+ if tag_only:
+ return
+ obj, tail = result
else:
try:
t, tlen, lv = tag_strip(tlv)
)
try:
l, llen, v = len_decode(lv)
+ except LenIndefForm as err:
+ if not ctx.get("bered", False):
+ raise err.__class__(
+ msg=err.msg,
+ klass=self.__class__,
+ decode_path=decode_path,
+ offset=offset,
+ )
+ llen, v = 1, lv[1:]
+ offset += tlen + llen
+ result = self._decode(
+ v,
+ offset=offset,
+ decode_path=decode_path,
+ ctx=ctx,
+ tag_only=tag_only,
+ )
+ if tag_only: # pragma: no cover
+ return
+ obj, tail = result
+ eoc_expected, tail = tail[:EOC_LEN], tail[EOC_LEN:]
+ if eoc_expected.tobytes() != EOC:
+ raise DecodeError(
+ "no EOC",
+ klass=self.__class__,
+ decode_path=decode_path,
+ offset=offset,
+ )
+ obj.vlen += EOC_LEN
+ obj.expl_lenindef = True
except DecodeError as err:
raise err.__class__(
msg=err.msg,
decode_path=decode_path,
offset=offset,
)
- if l > len(v):
- raise NotEnoughData(
- "encoded length is longer than data",
- klass=self.__class__,
+ else:
+ if l > len(v):
+ raise NotEnoughData(
+ "encoded length is longer than data",
+ klass=self.__class__,
+ decode_path=decode_path,
+ offset=offset,
+ )
+ result = self._decode(
+ v,
+ offset=offset + tlen + llen,
decode_path=decode_path,
- offset=offset,
+ ctx=ctx,
+ tag_only=tag_only,
)
- obj, tail = self._decode(
- v,
- offset=offset + tlen + llen,
- decode_path=decode_path,
- ctx=ctx,
- )
+ if tag_only: # pragma: no cover
+ return
+ obj, tail = result
+ if obj.tlvlen < l and not ctx.get("allow_expl_oob", False):
+ raise DecodeError(
+ "explicit tag out-of-bound, longer than data",
+ klass=self.__class__,
+ decode_path=decode_path,
+ offset=offset,
+ )
return obj, (tail if leavemm else tail.tobytes())
@property
@property
def expl_llen(self):
+ if self.expl_lenindef:
+ return 1
return len(len_encode(self.tlvlen))
@property
def expl_tlvlen(self):
return self.expl_tlen + self.expl_llen + self.expl_vlen
+ @property
+ def fulloffset(self):
+ return self.expl_offset if self.expled else self.offset
+
+ @property
+ def fulllen(self):
+ return self.expl_tlvlen if self.expled else self.tlvlen
-def decode_path_defby(defined_by):
+ def pps_lenindef(self, decode_path):
+ if self.lenindef and not (
+ getattr(self, "defined", None) is not None and
+ self.defined[1].lenindef
+ ):
+ yield _pp(
+ asn1_type_name="EOC",
+ obj_name="",
+ decode_path=decode_path,
+ offset=(
+ self.offset + self.tlvlen -
+ (EOC_LEN * 2 if self.expl_lenindef else EOC_LEN)
+ ),
+ tlen=1,
+ llen=1,
+ vlen=0,
+ ber_encoded=True,
+ bered=True,
+ )
+ if self.expl_lenindef:
+ yield _pp(
+ asn1_type_name="EOC",
+ obj_name="EXPLICIT",
+ decode_path=decode_path,
+ offset=self.expl_offset + self.expl_tlvlen - EOC_LEN,
+ tlen=1,
+ llen=1,
+ vlen=0,
+ ber_encoded=True,
+ bered=True,
+ )
+
+
+class DecodePathDefBy(object):
"""DEFINED BY representation inside decode path
"""
- return "DEFINED BY (%s)" % defined_by
+ __slots__ = ("defined_by",)
+
+ def __init__(self, defined_by):
+ self.defined_by = defined_by
+
+ def __ne__(self, their):
+ return not(self == their)
+
+ def __eq__(self, their):
+ if not isinstance(their, self.__class__):
+ return False
+ return self.defined_by == their.defined_by
+
+ def __str__(self):
+ return "DEFINED BY " + str(self.defined_by)
+
+ def __repr__(self):
+ return "<%s: %s>" % (self.__class__.__name__, self.defined_by)
########################################################################
########################################################################
PP = namedtuple("PP", (
+ "obj",
"asn1_type_name",
"obj_name",
"decode_path",
"expl_tlen",
"expl_llen",
"expl_vlen",
+ "expl_lenindef",
+ "lenindef",
+ "ber_encoded",
+ "bered",
))
def _pp(
+ obj=None,
asn1_type_name="unknown",
obj_name="unknown",
decode_path=(),
expl_tlen=None,
expl_llen=None,
expl_vlen=None,
+ expl_lenindef=False,
+ lenindef=False,
+ ber_encoded=False,
+ bered=False,
):
return PP(
+ obj,
asn1_type_name,
obj_name,
decode_path,
expl_tlen,
expl_llen,
expl_vlen,
+ expl_lenindef,
+ lenindef,
+ ber_encoded,
+ bered,
)
-def pp_console_row(pp, oids=None, with_offsets=False, with_blob=True):
+def _colourize(what, colour, with_colours, attrs=("bold",)):
+ return colored(what, colour, attrs=attrs) if with_colours else what
+
+
+def colonize_hex(hexed):
+ """Separate hexadecimal string with colons
+ """
+ return ":".join(hexed[i:i + 2] for i in six_xrange(0, len(hexed), 2))
+
+
+def pp_console_row(
+ pp,
+ oids=None,
+ with_offsets=False,
+ with_blob=True,
+ with_colours=False,
+ with_decode_path=False,
+ decode_path_len_decrease=0,
+):
cols = []
if with_offsets:
- cols.append("%5d%s [%d,%d,%4d]" % (
+ col = "%5d%s%s" % (
pp.offset,
(
" " if pp.expl_offset is None else
("-%d" % (pp.offset - pp.expl_offset))
),
+ LENINDEF_PP_CHAR if pp.expl_lenindef else " ",
+ )
+ col = _colourize(col, "red", with_colours, ())
+ col += _colourize("B", "red", with_colours) if pp.bered else " "
+ cols.append(col)
+ col = "[%d,%d,%4d]%s" % (
pp.tlen,
pp.llen,
pp.vlen,
- ))
- if len(pp.decode_path) > 0:
- cols.append(" ." * (len(pp.decode_path)))
- cols.append("%s:" % pp.decode_path[-1])
+ LENINDEF_PP_CHAR if pp.lenindef else " "
+ )
+ col = _colourize(col, "green", with_colours, ())
+ cols.append(col)
+ decode_path_len = len(pp.decode_path) - decode_path_len_decrease
+ if decode_path_len > 0:
+ cols.append(" ." * decode_path_len)
+ ent = pp.decode_path[-1]
+ if isinstance(ent, DecodePathDefBy):
+ cols.append(_colourize("DEFINED BY", "red", with_colours, ("reverse",)))
+ value = str(ent.defined_by)
+ if (
+ oids is not None and
+ ent.defined_by.asn1_type_name ==
+ ObjectIdentifier.asn1_type_name and
+ value in oids
+ ):
+ cols.append(_colourize("%s:" % oids[value], "green", with_colours))
+ else:
+ cols.append(_colourize("%s:" % value, "white", with_colours, ("reverse",)))
+ else:
+ cols.append(_colourize("%s:" % ent, "yellow", with_colours, ("reverse",)))
if pp.expl is not None:
klass, _, num = pp.expl
- cols.append("[%s%d] EXPLICIT" % (TagClassReprs[klass], num))
+ col = "[%s%d] EXPLICIT" % (TagClassReprs[klass], num)
+ cols.append(_colourize(col, "blue", with_colours))
if pp.impl is not None:
klass, _, num = pp.impl
- cols.append("[%s%d]" % (TagClassReprs[klass], num))
+ col = "[%s%d]" % (TagClassReprs[klass], num)
+ cols.append(_colourize(col, "blue", with_colours))
if pp.asn1_type_name.replace(" ", "") != pp.obj_name.upper():
- cols.append(pp.obj_name)
- cols.append(pp.asn1_type_name)
+ cols.append(_colourize(pp.obj_name, "magenta", with_colours))
+ if pp.ber_encoded:
+ cols.append(_colourize("BER", "red", with_colours))
+ cols.append(_colourize(pp.asn1_type_name, "cyan", with_colours))
if pp.value is not None:
value = pp.value
+ cols.append(_colourize(value, "white", with_colours, ("reverse",)))
if (
oids is not None and
pp.asn1_type_name == ObjectIdentifier.asn1_type_name and
value in oids
):
- value = "%s (%s)" % (oids[value], pp.value)
- cols.append(value)
+ cols.append(_colourize("(%s)" % oids[value], "green", with_colours))
+ if pp.asn1_type_name == Integer.asn1_type_name:
+ hex_repr = hex(int(pp.obj._value))[2:].upper()
+ if len(hex_repr) % 2 != 0:
+ hex_repr = "0" + hex_repr
+ cols.append(_colourize(
+ "(%s)" % colonize_hex(hex_repr),
+ "green",
+ with_colours,
+ ))
if with_blob:
if isinstance(pp.blob, binary_type):
cols.append(hexenc(pp.blob))
elif isinstance(pp.blob, tuple):
cols.append(", ".join(pp.blob))
if pp.optional:
- cols.append("OPTIONAL")
+ cols.append(_colourize("OPTIONAL", "red", with_colours))
if pp.default:
- cols.append("DEFAULT")
+ cols.append(_colourize("DEFAULT", "red", with_colours))
+ if with_decode_path:
+ cols.append(_colourize(
+ "[%s]" % ":".join(str(p) for p in pp.decode_path),
+ "grey",
+ with_colours,
+ ))
return " ".join(cols)
-def pp_console_blob(pp):
- cols = [" " * len("XXXXXYY [X,X,XXXX]")]
- if len(pp.decode_path) > 0:
- cols.append(" ." * (len(pp.decode_path) + 1))
+def pp_console_blob(pp, decode_path_len_decrease=0):
+ cols = [" " * len("XXXXXYYZZ [X,X,XXXX]Z")]
+ decode_path_len = len(pp.decode_path) - decode_path_len_decrease
+ if decode_path_len > 0:
+ cols.append(" ." * (decode_path_len + 1))
if isinstance(pp.blob, binary_type):
blob = hexenc(pp.blob).upper()
- for i in range(0, len(blob), 32):
+ for i in six_xrange(0, len(blob), 32):
chunk = blob[i:i + 32]
- yield " ".join(cols + [":".join(
- chunk[j:j + 2] for j in range(0, len(chunk), 2)
- )])
+ yield " ".join(cols + [colonize_hex(chunk)])
elif isinstance(pp.blob, tuple):
yield " ".join(cols + [", ".join(pp.blob)])
-def pprint(obj, oids=None, big_blobs=False):
+def pprint(
+ obj,
+ oids=None,
+ big_blobs=False,
+ with_colours=False,
+ with_decode_path=False,
+ decode_path_only=(),
+):
"""Pretty print object
:param Obj obj: object you want to pretty print
:param big_blobs: if large binary objects are met (like OctetString
values), do we need to print them too, on separate
lines
+ :param with_colours: colourize output, if ``termcolor`` library
+ is available
+ :param with_decode_path: print decode path
+ :param decode_path_only: print only that specified decode path
"""
def _pprint_pps(pps):
for pp in pps:
if hasattr(pp, "_fields"):
+ if (
+ decode_path_only != () and
+ tuple(
+ str(p) for p in pp.decode_path[:len(decode_path_only)]
+ ) != decode_path_only
+ ):
+ continue
if big_blobs:
yield pp_console_row(
pp,
oids=oids,
with_offsets=True,
with_blob=False,
+ with_colours=with_colours,
+ with_decode_path=with_decode_path,
+ decode_path_len_decrease=len(decode_path_only),
)
- for row in pp_console_blob(pp):
+ for row in pp_console_blob(
+ pp,
+ decode_path_len_decrease=len(decode_path_only),
+ ):
yield row
else:
- yield pp_console_row(pp, oids=oids, with_offsets=True)
+ yield pp_console_row(
+ pp,
+ oids=oids,
+ with_offsets=True,
+ with_blob=True,
+ with_colours=with_colours,
+ with_decode_path=with_decode_path,
+ decode_path_len_decrease=len(decode_path_only),
+ )
else:
for row in _pprint_pps(pp):
yield row
obj.offset = self.offset
obj.llen = self.llen
obj.vlen = self.vlen
+ obj.expl_lenindef = self.expl_lenindef
+ obj.lenindef = self.lenindef
+ obj.ber_encoded = self.ber_encoded
return obj
def __nonzero__(self):
(b"\xFF" if self._value else b"\x00"),
))
- def _decode(self, tlv, offset, decode_path, ctx):
+ def _decode(self, tlv, offset, decode_path, ctx, tag_only):
try:
t, _, lv = tag_strip(tlv)
except DecodeError as err:
decode_path=decode_path,
offset=offset,
)
+ if tag_only:
+ return
try:
l, _, v = len_decode(lv)
except DecodeError as err:
offset=offset,
)
first_octet = byte2int(v)
+ ber_encoded = False
if first_octet == 0:
value = False
elif first_octet == 0xFF:
value = True
+ elif ctx.get("bered", False):
+ value = True
+ ber_encoded = True
else:
raise DecodeError(
"unacceptable Boolean value",
optional=self.optional,
_decoded=(offset, 1, 1),
)
+ obj.ber_encoded = ber_encoded
return obj, v[1:]
def __repr__(self):
def pps(self, decode_path=()):
yield _pp(
+ obj=self,
asn1_type_name=self.asn1_type_name,
obj_name=self.__class__.__name__,
decode_path=decode_path,
expl_tlen=self.expl_tlen if self.expled else None,
expl_llen=self.expl_llen if self.expled else None,
expl_vlen=self.expl_vlen if self.expled else None,
+ expl_lenindef=self.expl_lenindef,
+ ber_encoded=self.ber_encoded,
+ bered=self.bered,
)
+ for pp in self.pps_lenindef(decode_path):
+ yield pp
class Integer(Obj):
obj.offset = self.offset
obj.llen = self.llen
obj.vlen = self.vlen
+ obj.expl_lenindef = self.expl_lenindef
+ obj.lenindef = self.lenindef
+ obj.ber_encoded = self.ber_encoded
return obj
def __int__(self):
break
return b"".join((self.tag, len_encode(len(octets)), octets))
- def _decode(self, tlv, offset, decode_path, ctx):
+ def _decode(self, tlv, offset, decode_path, ctx, tag_only):
try:
t, _, lv = tag_strip(tlv)
except DecodeError as err:
decode_path=decode_path,
offset=offset,
)
+ if tag_only:
+ return
try:
l, llen, v = len_decode(lv)
except DecodeError as err:
def pps(self, decode_path=()):
yield _pp(
+ obj=self,
asn1_type_name=self.asn1_type_name,
obj_name=self.__class__.__name__,
decode_path=decode_path,
expl_tlen=self.expl_tlen if self.expled else None,
expl_llen=self.expl_llen if self.expled else None,
expl_vlen=self.expl_vlen if self.expled else None,
+ expl_lenindef=self.expl_lenindef,
+ bered=self.bered,
)
+ for pp in self.pps_lenindef(decode_path):
+ yield pp
class BitString(Obj):
>>> b.bit_len
88
+ >>> BitString("'0A3B5F291CD'H")
+ BIT STRING 44 bits 0a3b5f291cd0
>>> b = BitString("'010110000000'B")
BIT STRING 12 bits 5800
>>> b.bit_len
class KeyUsage(BitString):
schema = (
- ('digitalSignature', 0),
- ('nonRepudiation', 1),
- ('keyEncipherment', 2),
+ ("digitalSignature", 0),
+ ("nonRepudiation", 1),
+ ("keyEncipherment", 2),
)
- >>> b = KeyUsage(('keyEncipherment', 'nonRepudiation'))
+ >>> b = KeyUsage(("keyEncipherment", "nonRepudiation"))
KeyUsage BIT STRING 3 bits nonRepudiation, keyEncipherment
>>> b.named
['nonRepudiation', 'keyEncipherment']
>>> b.specs
{'nonRepudiation': 1, 'digitalSignature': 0, 'keyEncipherment': 2}
+
+ .. note::
+
+ Pay attention that BIT STRING can be encoded both in primitive
+ and constructed forms. Decoder always checks constructed form tag
+ additionally to specified primitive one. If BER decoding is
+ :ref:`not enabled <bered_ctx>`, then decoder will fail, because
+ of DER restrictions.
"""
- __slots__ = ("specs", "defined")
+ __slots__ = ("tag_constructed", "specs", "defined")
tag_default = tag_encode(3)
asn1_type_name = "BIT STRING"
if value is None:
self._value = default
self.defined = None
+ tag_klass, _, tag_num = tag_decode(self.tag)
+ self.tag_constructed = tag_encode(
+ klass=tag_klass,
+ form=TagFormConstructed,
+ num=tag_num,
+ )
def _bits2octets(self, bits):
if len(self.specs) > 0:
if isinstance(value, (string_types, binary_type)):
if (
isinstance(value, string_types) and
- value.startswith("'") and
- value.endswith("'B")
+ value.startswith("'")
):
- value = value[1:-2]
- if not set(value) <= set(("0", "1")):
- raise ValueError("B's coding contains unacceptable chars")
- return self._bits2octets(value)
- elif isinstance(value, binary_type):
+ if value.endswith("'B"):
+ value = value[1:-2]
+ if not set(value) <= set(("0", "1")):
+ raise ValueError("B's coding contains unacceptable chars")
+ return self._bits2octets(value)
+ elif value.endswith("'H"):
+ value = value[1:-2]
+ return (
+ len(value) * 4,
+ hexdec(value + ("" if len(value) % 2 == 0 else "0")),
+ )
+ if isinstance(value, binary_type):
return (len(value) * 8, value)
else:
- raise InvalidValueType((
- self.__class__,
- string_types,
- binary_type,
- ))
+ raise InvalidValueType((self.__class__, string_types, binary_type))
if isinstance(value, tuple):
if (
len(value) == 2 and
obj.offset = self.offset
obj.llen = self.llen
obj.vlen = self.vlen
+ obj.expl_lenindef = self.expl_lenindef
+ obj.lenindef = self.lenindef
+ obj.ber_encoded = self.ber_encoded
return obj
def __iter__(self):
octets,
))
- def _decode(self, tlv, offset, decode_path, ctx):
- try:
- t, _, lv = tag_strip(tlv)
- except DecodeError as err:
- raise err.__class__(
- msg=err.msg,
- klass=self.__class__,
- decode_path=decode_path,
- offset=offset,
- )
- if t != self.tag:
- raise TagMismatch(
- klass=self.__class__,
- decode_path=decode_path,
- offset=offset,
- )
+ def _decode_chunk(self, lv, offset, decode_path, ctx):
try:
l, llen, v = len_decode(lv)
except DecodeError as err:
decode_path=decode_path,
offset=offset,
)
- if byte2int(v[-1:]) & ((1 << pad_size) - 1) != 0:
+ if byte2int(v[l - 1:l]) & ((1 << pad_size) - 1) != 0:
raise DecodeError(
"invalid pad",
klass=self.__class__,
)
return obj, tail
+ def _decode(self, tlv, offset, decode_path, ctx, tag_only):
+ try:
+ t, tlen, lv = tag_strip(tlv)
+ except DecodeError as err:
+ raise err.__class__(
+ msg=err.msg,
+ klass=self.__class__,
+ decode_path=decode_path,
+ offset=offset,
+ )
+ if t == self.tag:
+ if tag_only: # pragma: no cover
+ return
+ return self._decode_chunk(lv, offset, decode_path, ctx)
+ if t == self.tag_constructed:
+ if not ctx.get("bered", False):
+ raise DecodeError(
+ "unallowed BER constructed encoding",
+ klass=self.__class__,
+ decode_path=decode_path,
+ offset=offset,
+ )
+ if tag_only: # pragma: no cover
+ return
+ lenindef = False
+ try:
+ l, llen, v = len_decode(lv)
+ except LenIndefForm:
+ llen, l, v = 1, 0, lv[1:]
+ lenindef = True
+ except DecodeError as err:
+ raise err.__class__(
+ msg=err.msg,
+ klass=self.__class__,
+ decode_path=decode_path,
+ offset=offset,
+ )
+ if l > len(v):
+ raise NotEnoughData(
+ "encoded length is longer than data",
+ klass=self.__class__,
+ decode_path=decode_path,
+ offset=offset,
+ )
+ if not lenindef and l == 0:
+ raise NotEnoughData(
+ "zero length",
+ klass=self.__class__,
+ decode_path=decode_path,
+ offset=offset,
+ )
+ chunks = []
+ sub_offset = offset + tlen + llen
+ vlen = 0
+ while True:
+ if lenindef:
+ if v[:EOC_LEN].tobytes() == EOC:
+ break
+ else:
+ if vlen == l:
+ break
+ if vlen > l:
+ raise DecodeError(
+ "chunk out of bounds",
+ klass=self.__class__,
+ decode_path=decode_path + (str(len(chunks) - 1),),
+ offset=chunks[-1].offset,
+ )
+ sub_decode_path = decode_path + (str(len(chunks)),)
+ try:
+ chunk, v_tail = BitString().decode(
+ v,
+ offset=sub_offset,
+ decode_path=sub_decode_path,
+ leavemm=True,
+ ctx=ctx,
+ _ctx_immutable=False,
+ )
+ except TagMismatch:
+ raise DecodeError(
+ "expected BitString encoded chunk",
+ klass=self.__class__,
+ decode_path=sub_decode_path,
+ offset=sub_offset,
+ )
+ chunks.append(chunk)
+ sub_offset += chunk.tlvlen
+ vlen += chunk.tlvlen
+ v = v_tail
+ if len(chunks) == 0:
+ raise DecodeError(
+ "no chunks",
+ klass=self.__class__,
+ decode_path=decode_path,
+ offset=offset,
+ )
+ values = []
+ bit_len = 0
+ for chunk_i, chunk in enumerate(chunks[:-1]):
+ if chunk.bit_len % 8 != 0:
+ raise DecodeError(
+ "BitString chunk is not multiple of 8 bits",
+ klass=self.__class__,
+ decode_path=decode_path + (str(chunk_i),),
+ offset=chunk.offset,
+ )
+ values.append(bytes(chunk))
+ bit_len += chunk.bit_len
+ chunk_last = chunks[-1]
+ values.append(bytes(chunk_last))
+ bit_len += chunk_last.bit_len
+ obj = self.__class__(
+ value=(bit_len, b"".join(values)),
+ impl=self.tag,
+ expl=self._expl,
+ default=self.default,
+ optional=self.optional,
+ _specs=self.specs,
+ _decoded=(offset, llen, vlen + (EOC_LEN if lenindef else 0)),
+ )
+ obj.lenindef = lenindef
+ obj.ber_encoded = True
+ return obj, (v[EOC_LEN:] if lenindef else v)
+ raise TagMismatch(
+ klass=self.__class__,
+ decode_path=decode_path,
+ offset=offset,
+ )
+
def __repr__(self):
return pp_console_row(next(self.pps()))
if len(self.specs) > 0:
blob = tuple(self.named)
yield _pp(
+ obj=self,
asn1_type_name=self.asn1_type_name,
obj_name=self.__class__.__name__,
decode_path=decode_path,
expl_tlen=self.expl_tlen if self.expled else None,
expl_llen=self.expl_llen if self.expled else None,
expl_vlen=self.expl_vlen if self.expled else None,
+ expl_lenindef=self.expl_lenindef,
+ lenindef=self.lenindef,
+ ber_encoded=self.ber_encoded,
+ bered=self.bered,
)
defined_by, defined = self.defined or (None, None)
if defined_by is not None:
yield defined.pps(
- decode_path=decode_path + (decode_path_defby(defined_by),)
+ decode_path=decode_path + (DecodePathDefBy(defined_by),)
)
+ for pp in self.pps_lenindef(decode_path):
+ yield pp
class OctetString(Obj):
pyderasn.BoundsError: unsatisfied bounds: 4 <= 5 <= 4
>>> OctetString(b"hell", bounds=(4, 4))
OCTET STRING 4 bytes 68656c6c
+
+ .. note::
+
+ Pay attention that OCTET STRING can be encoded both in primitive
+ and constructed forms. Decoder always checks constructed form tag
+ additionally to specified primitive one. If BER decoding is
+ :ref:`not enabled <bered_ctx>`, then decoder will fail, because
+ of DER restrictions.
"""
- __slots__ = ("_bound_min", "_bound_max", "defined")
+ __slots__ = ("tag_constructed", "_bound_min", "_bound_max", "defined")
tag_default = tag_encode(4)
asn1_type_name = "OCTET STRING"
if self._value is None:
self._value = default
self.defined = None
+ tag_klass, _, tag_num = tag_decode(self.tag)
+ self.tag_constructed = tag_encode(
+ klass=tag_klass,
+ form=TagFormConstructed,
+ num=tag_num,
+ )
def _value_sanitize(self, value):
if issubclass(value.__class__, OctetString):
obj.offset = self.offset
obj.llen = self.llen
obj.vlen = self.vlen
+ obj.expl_lenindef = self.expl_lenindef
+ obj.lenindef = self.lenindef
+ obj.ber_encoded = self.ber_encoded
return obj
def __bytes__(self):
self._value,
))
- def _decode(self, tlv, offset, decode_path, ctx):
- try:
- t, _, lv = tag_strip(tlv)
- except DecodeError as err:
- raise err.__class__(
- msg=err.msg,
- klass=self.__class__,
- decode_path=decode_path,
- offset=offset,
- )
- if t != self.tag:
- raise TagMismatch(
- klass=self.__class__,
- decode_path=decode_path,
- offset=offset,
- )
+ def _decode_chunk(self, lv, offset, decode_path, ctx):
try:
l, llen, v = len_decode(lv)
except DecodeError as err:
optional=self.optional,
_decoded=(offset, llen, l),
)
+ except DecodeError as err:
+ raise DecodeError(
+ msg=err.msg,
+ klass=self.__class__,
+ decode_path=decode_path,
+ offset=offset,
+ )
except BoundsError as err:
raise DecodeError(
msg=str(err),
)
return obj, tail
+ def _decode(self, tlv, offset, decode_path, ctx, tag_only):
+ try:
+ t, tlen, lv = tag_strip(tlv)
+ except DecodeError as err:
+ raise err.__class__(
+ msg=err.msg,
+ klass=self.__class__,
+ decode_path=decode_path,
+ offset=offset,
+ )
+ if t == self.tag:
+ if tag_only:
+ return
+ return self._decode_chunk(lv, offset, decode_path, ctx)
+ if t == self.tag_constructed:
+ if not ctx.get("bered", False):
+ raise DecodeError(
+ "unallowed BER constructed encoding",
+ klass=self.__class__,
+ decode_path=decode_path,
+ offset=offset,
+ )
+ if tag_only:
+ return
+ lenindef = False
+ try:
+ l, llen, v = len_decode(lv)
+ except LenIndefForm:
+ llen, l, v = 1, 0, lv[1:]
+ lenindef = True
+ except DecodeError as err:
+ raise err.__class__(
+ msg=err.msg,
+ klass=self.__class__,
+ decode_path=decode_path,
+ offset=offset,
+ )
+ if l > len(v):
+ raise NotEnoughData(
+ "encoded length is longer than data",
+ klass=self.__class__,
+ decode_path=decode_path,
+ offset=offset,
+ )
+ chunks = []
+ sub_offset = offset + tlen + llen
+ vlen = 0
+ while True:
+ if lenindef:
+ if v[:EOC_LEN].tobytes() == EOC:
+ break
+ else:
+ if vlen == l:
+ break
+ if vlen > l:
+ raise DecodeError(
+ "chunk out of bounds",
+ klass=self.__class__,
+ decode_path=decode_path + (str(len(chunks) - 1),),
+ offset=chunks[-1].offset,
+ )
+ sub_decode_path = decode_path + (str(len(chunks)),)
+ try:
+ chunk, v_tail = OctetString().decode(
+ v,
+ offset=sub_offset,
+ decode_path=sub_decode_path,
+ leavemm=True,
+ ctx=ctx,
+ _ctx_immutable=False,
+ )
+ except TagMismatch:
+ raise DecodeError(
+ "expected OctetString encoded chunk",
+ klass=self.__class__,
+ decode_path=sub_decode_path,
+ offset=sub_offset,
+ )
+ chunks.append(chunk)
+ sub_offset += chunk.tlvlen
+ vlen += chunk.tlvlen
+ v = v_tail
+ try:
+ obj = self.__class__(
+ value=b"".join(bytes(chunk) for chunk in chunks),
+ bounds=(self._bound_min, self._bound_max),
+ impl=self.tag,
+ expl=self._expl,
+ default=self.default,
+ optional=self.optional,
+ _decoded=(offset, llen, vlen + (EOC_LEN if lenindef else 0)),
+ )
+ except DecodeError as err:
+ raise DecodeError(
+ msg=err.msg,
+ klass=self.__class__,
+ decode_path=decode_path,
+ offset=offset,
+ )
+ except BoundsError as err:
+ raise DecodeError(
+ msg=str(err),
+ klass=self.__class__,
+ decode_path=decode_path,
+ offset=offset,
+ )
+ obj.lenindef = lenindef
+ obj.ber_encoded = True
+ return obj, (v[EOC_LEN:] if lenindef else v)
+ raise TagMismatch(
+ klass=self.__class__,
+ decode_path=decode_path,
+ offset=offset,
+ )
+
def __repr__(self):
return pp_console_row(next(self.pps()))
def pps(self, decode_path=()):
yield _pp(
+ obj=self,
asn1_type_name=self.asn1_type_name,
obj_name=self.__class__.__name__,
decode_path=decode_path,
expl_tlen=self.expl_tlen if self.expled else None,
expl_llen=self.expl_llen if self.expled else None,
expl_vlen=self.expl_vlen if self.expled else None,
+ expl_lenindef=self.expl_lenindef,
+ lenindef=self.lenindef,
+ ber_encoded=self.ber_encoded,
+ bered=self.bered,
)
defined_by, defined = self.defined or (None, None)
if defined_by is not None:
yield defined.pps(
- decode_path=decode_path + (decode_path_defby(defined_by),)
+ decode_path=decode_path + (DecodePathDefBy(defined_by),)
)
+ for pp in self.pps_lenindef(decode_path):
+ yield pp
class Null(Obj):
obj.offset = self.offset
obj.llen = self.llen
obj.vlen = self.vlen
+ obj.expl_lenindef = self.expl_lenindef
+ obj.lenindef = self.lenindef
+ obj.ber_encoded = self.ber_encoded
return obj
def __eq__(self, their):
def _encode(self):
return self.tag + len_encode(0)
- def _decode(self, tlv, offset, decode_path, ctx):
+ def _decode(self, tlv, offset, decode_path, ctx, tag_only):
try:
t, _, lv = tag_strip(tlv)
except DecodeError as err:
decode_path=decode_path,
offset=offset,
)
+ if tag_only: # pragma: no cover
+ return
try:
l, _, v = len_decode(lv)
except DecodeError as err:
def pps(self, decode_path=()):
yield _pp(
+ obj=self,
asn1_type_name=self.asn1_type_name,
obj_name=self.__class__.__name__,
decode_path=decode_path,
expl_tlen=self.expl_tlen if self.expled else None,
expl_llen=self.expl_llen if self.expled else None,
expl_vlen=self.expl_vlen if self.expled else None,
+ expl_lenindef=self.expl_lenindef,
+ bered=self.bered,
)
+ for pp in self.pps_lenindef(decode_path):
+ yield pp
class ObjectIdentifier(Obj):
obj.offset = self.offset
obj.llen = self.llen
obj.vlen = self.vlen
+ obj.expl_lenindef = self.expl_lenindef
+ obj.lenindef = self.lenindef
+ obj.ber_encoded = self.ber_encoded
return obj
def __iter__(self):
v = b"".join(octets)
return b"".join((self.tag, len_encode(len(v)), v))
- def _decode(self, tlv, offset, decode_path, ctx):
+ def _decode(self, tlv, offset, decode_path, ctx, tag_only):
try:
t, _, lv = tag_strip(tlv)
except DecodeError as err:
decode_path=decode_path,
offset=offset,
)
+ if tag_only: # pragma: no cover
+ return
try:
l, llen, v = len_decode(lv)
except DecodeError as err:
)
v, tail = v[:l], v[l:]
arcs = []
+ ber_encoded = False
while len(v) > 0:
i = 0
arc = 0
while True:
octet = indexbytes(v, i)
+ if i == 0 and octet == 0x80:
+ if ctx.get("bered", False):
+ ber_encoded = True
+ else:
+ raise DecodeError("non normalized arc encoding")
arc = (arc << 7) | (octet & 0x7F)
if octet & 0x80 == 0:
arcs.append(arc)
optional=self.optional,
_decoded=(offset, llen, l),
)
+ if ber_encoded:
+ obj.ber_encoded = True
return obj, tail
def __repr__(self):
def pps(self, decode_path=()):
yield _pp(
+ obj=self,
asn1_type_name=self.asn1_type_name,
obj_name=self.__class__.__name__,
decode_path=decode_path,
expl_tlen=self.expl_tlen if self.expled else None,
expl_llen=self.expl_llen if self.expled else None,
expl_vlen=self.expl_vlen if self.expled else None,
+ expl_lenindef=self.expl_lenindef,
+ ber_encoded=self.ber_encoded,
+ bered=self.bered,
)
+ for pp in self.pps_lenindef(decode_path):
+ yield pp
class Enumerated(Integer):
obj.offset = self.offset
obj.llen = self.llen
obj.vlen = self.vlen
+ obj.expl_lenindef = self.expl_lenindef
+ obj.lenindef = self.lenindef
+ obj.ber_encoded = self.ber_encoded
return obj
def __call__(
>>> PrintableString("привет мир")
Traceback (most recent call last):
- UnicodeEncodeError: 'ascii' codec can't encode characters in position 0-5: ordinal not in range(128)
+ pyderasn.DecodeError: 'ascii' codec can't encode characters in position 0-5: ordinal not in range(128)
>>> BMPString("ада", bounds=(2, 2))
Traceback (most recent call last):
value_raw = value
else:
raise InvalidValueType((self.__class__, text_type, binary_type))
- value_raw = (
- value_decoded.encode(self.encoding)
- if value_raw is None else value_raw
- )
- value_decoded = (
- value_raw.decode(self.encoding)
- if value_decoded is None else value_decoded
- )
+ try:
+ value_raw = (
+ value_decoded.encode(self.encoding)
+ if value_raw is None else value_raw
+ )
+ value_decoded = (
+ value_raw.decode(self.encoding)
+ if value_decoded is None else value_decoded
+ )
+ except (UnicodeEncodeError, UnicodeDecodeError) as err:
+ raise DecodeError(str(err))
if not self._bound_min <= len(value_decoded) <= self._bound_max:
raise BoundsError(
self._bound_min,
if self.ready:
value = hexenc(bytes(self)) if no_unicode else self.__unicode__()
yield _pp(
+ obj=self,
asn1_type_name=self.asn1_type_name,
obj_name=self.__class__.__name__,
decode_path=decode_path,
tlen=self.tlen,
llen=self.llen,
vlen=self.vlen,
+ expl_offset=self.expl_offset if self.expled else None,
+ expl_tlen=self.expl_tlen if self.expled else None,
+ expl_llen=self.expl_llen if self.expled else None,
+ expl_vlen=self.expl_vlen if self.expled else None,
+ expl_lenindef=self.expl_lenindef,
+ ber_encoded=self.ber_encoded,
+ bered=self.bered,
)
+ for pp in self.pps_lenindef(decode_path):
+ yield pp
class UTF8String(CommonString):
asn1_type_name = "UTF8String"
-class NumericString(CommonString):
+class AllowableCharsMixin(object):
+ @property
+ def allowable_chars(self):
+ if PY2:
+ return self._allowable_chars
+ return set(six_unichr(c) for c in self._allowable_chars)
+
+
+class NumericString(AllowableCharsMixin, CommonString):
+ """Numeric string
+
+ Its value is properly sanitized: only ASCII digits with spaces can
+ be stored.
+
+ >>> NumericString().allowable_chars
+ set(['3', '4', '7', '5', '1', '0', '8', '9', ' ', '6', '2'])
+ """
__slots__ = ()
tag_default = tag_encode(18)
encoding = "ascii"
asn1_type_name = "NumericString"
+ _allowable_chars = set(digits.encode("ascii") + b" ")
+
+ def _value_sanitize(self, value):
+ value = super(NumericString, self)._value_sanitize(value)
+ if not set(value) <= self._allowable_chars:
+ raise DecodeError("non-numeric value")
+ return value
-class PrintableString(CommonString):
+class PrintableString(AllowableCharsMixin, CommonString):
+ """Printable string
+
+ Its value is properly sanitized: see X.680 41.4 table 10.
+
+ >>> PrintableString().allowable_chars
+ >>> set([' ', "'", ..., 'z'])
+ """
__slots__ = ()
tag_default = tag_encode(19)
encoding = "ascii"
asn1_type_name = "PrintableString"
+ _allowable_chars = set(
+ (ascii_letters + digits + " '()+,-./:=?").encode("ascii")
+ )
+
+ def _value_sanitize(self, value):
+ value = super(PrintableString, self)._value_sanitize(value)
+ if not set(value) <= self._allowable_chars:
+ raise DecodeError("non-printable value")
+ return value
class TeletexString(CommonString):
if isinstance(value, datetime):
return value.strftime(self.fmt).encode("ascii")
if isinstance(value, binary_type):
- value_decoded = value.decode("ascii")
+ try:
+ value_decoded = value.decode("ascii")
+ except (UnicodeEncodeError, UnicodeDecodeError) as err:
+ raise DecodeError("invalid UTCTime encoding")
if len(value_decoded) == LEN_YYMMDDHHMMSSZ:
try:
datetime.strptime(value_decoded, self.fmt)
- except ValueError:
+ except (TypeError, ValueError):
raise DecodeError("invalid UTCTime format")
return value
else:
def pps(self, decode_path=()):
yield _pp(
+ obj=self,
asn1_type_name=self.asn1_type_name,
obj_name=self.__class__.__name__,
decode_path=decode_path,
tlen=self.tlen,
llen=self.llen,
vlen=self.vlen,
+ expl_offset=self.expl_offset if self.expled else None,
+ expl_tlen=self.expl_tlen if self.expled else None,
+ expl_llen=self.expl_llen if self.expled else None,
+ expl_vlen=self.expl_vlen if self.expled else None,
+ expl_lenindef=self.expl_lenindef,
+ ber_encoded=self.ber_encoded,
+ bered=self.bered,
)
+ for pp in self.pps_lenindef(decode_path):
+ yield pp
class GeneralizedTime(UTCTime):
self.fmt_ms if value.microsecond > 0 else self.fmt
).encode("ascii")
if isinstance(value, binary_type):
- value_decoded = value.decode("ascii")
+ try:
+ value_decoded = value.decode("ascii")
+ except (UnicodeEncodeError, UnicodeDecodeError) as err:
+ raise DecodeError("invalid GeneralizedTime encoding")
if len(value_decoded) == LEN_YYYYMMDDHHMMSSZ:
try:
datetime.strptime(value_decoded, self.fmt)
- except ValueError:
+ except (TypeError, ValueError):
raise DecodeError(
"invalid GeneralizedTime (without ms) format",
)
elif len(value_decoded) >= LEN_YYYYMMDDHHMMSSDMZ:
try:
datetime.strptime(value_decoded, self.fmt_ms)
- except ValueError:
+ except (TypeError, ValueError):
raise DecodeError(
"invalid GeneralizedTime (with ms) format",
)
class GeneralName(Choice):
schema = (
- ('rfc822Name', IA5String(impl=tag_ctxp(1))),
- ('dNSName', IA5String(impl=tag_ctxp(2))),
+ ("rfc822Name", IA5String(impl=tag_ctxp(1))),
+ ("dNSName", IA5String(impl=tag_ctxp(2))),
)
>>> gn = GeneralName()
def ready(self):
return self._value is not None and self._value[1].ready
+ @property
+ def bered(self):
+ return self.expl_lenindef or (
+ (self._value is not None) and
+ self._value[1].bered
+ )
+
def copy(self):
obj = self.__class__(schema=self.specs)
obj._expl = self._expl
obj.offset = self.offset
obj.llen = self.llen
obj.vlen = self.vlen
+ obj.expl_lenindef = self.expl_lenindef
+ obj.lenindef = self.lenindef
+ obj.ber_encoded = self.ber_encoded
value = self._value
if value is not None:
obj._value = (value[0], value[1].copy())
self._assert_ready()
return self._value[1].encode()
- def _decode(self, tlv, offset, decode_path, ctx):
+ def _decode(self, tlv, offset, decode_path, ctx, tag_only):
for choice, spec in self.specs.items():
+ sub_decode_path = decode_path + (choice,)
try:
- value, tail = spec.decode(
+ spec.decode(
tlv,
offset=offset,
leavemm=True,
- decode_path=decode_path + (choice,),
+ decode_path=sub_decode_path,
ctx=ctx,
+ tag_only=True,
+ _ctx_immutable=False,
)
except TagMismatch:
continue
- obj = self.__class__(
- schema=self.specs,
- expl=self._expl,
- default=self.default,
- optional=self.optional,
- _decoded=(offset, 0, value.tlvlen),
+ break
+ else:
+ raise TagMismatch(
+ klass=self.__class__,
+ decode_path=decode_path,
+ offset=offset,
)
- obj._value = (choice, value)
- return obj, tail
- raise TagMismatch(
- klass=self.__class__,
- decode_path=decode_path,
+ if tag_only: # pragma: no cover
+ return
+ value, tail = spec.decode(
+ tlv,
offset=offset,
+ leavemm=True,
+ decode_path=sub_decode_path,
+ ctx=ctx,
+ _ctx_immutable=False,
+ )
+ obj = self.__class__(
+ schema=self.specs,
+ expl=self._expl,
+ default=self.default,
+ optional=self.optional,
+ _decoded=(offset, 0, value.fulllen),
)
+ obj._value = (choice, value)
+ return obj, tail
def __repr__(self):
value = pp_console_row(next(self.pps()))
def pps(self, decode_path=()):
yield _pp(
+ obj=self,
asn1_type_name=self.asn1_type_name,
obj_name=self.__class__.__name__,
decode_path=decode_path,
tlen=self.tlen,
llen=self.llen,
vlen=self.vlen,
+ expl_lenindef=self.expl_lenindef,
+ bered=self.bered,
)
if self.ready:
yield self.value.pps(decode_path=decode_path + (self.choice,))
+ for pp in self.pps_lenindef(decode_path):
+ yield pp
class PrimitiveTypes(Choice):
def ready(self):
return self._value is not None
+ @property
+ def bered(self):
+ if self.expl_lenindef or self.lenindef:
+ return True
+ if self.defined is None:
+ return False
+ return self.defined[1].bered
+
def copy(self):
obj = self.__class__()
obj._value = self._value
obj.offset = self.offset
obj.llen = self.llen
obj.vlen = self.vlen
+ obj.expl_lenindef = self.expl_lenindef
+ obj.lenindef = self.lenindef
+ obj.ber_encoded = self.ber_encoded
return obj
def __eq__(self, their):
self._assert_ready()
return self._value
- def _decode(self, tlv, offset, decode_path, ctx):
+ def _decode(self, tlv, offset, decode_path, ctx, tag_only):
try:
t, tlen, lv = tag_strip(tlv)
+ except DecodeError as err:
+ raise err.__class__(
+ msg=err.msg,
+ klass=self.__class__,
+ decode_path=decode_path,
+ offset=offset,
+ )
+ try:
l, llen, v = len_decode(lv)
+ except LenIndefForm as err:
+ if not ctx.get("bered", False):
+ raise err.__class__(
+ msg=err.msg,
+ klass=self.__class__,
+ decode_path=decode_path,
+ offset=offset,
+ )
+ llen, vlen, v = 1, 0, lv[1:]
+ sub_offset = offset + tlen + llen
+ chunk_i = 0
+ while v[:EOC_LEN].tobytes() != EOC:
+ chunk, v = Any().decode(
+ v,
+ offset=sub_offset,
+ decode_path=decode_path + (str(chunk_i),),
+ leavemm=True,
+ ctx=ctx,
+ _ctx_immutable=False,
+ )
+ vlen += chunk.tlvlen
+ sub_offset += chunk.tlvlen
+ chunk_i += 1
+ tlvlen = tlen + llen + vlen + EOC_LEN
+ obj = self.__class__(
+ value=tlv[:tlvlen].tobytes(),
+ expl=self._expl,
+ optional=self.optional,
+ _decoded=(offset, 0, tlvlen),
+ )
+ obj.lenindef = True
+ obj.tag = t
+ return obj, v[EOC_LEN:]
except DecodeError as err:
raise err.__class__(
msg=err.msg,
def pps(self, decode_path=()):
yield _pp(
+ obj=self,
asn1_type_name=self.asn1_type_name,
obj_name=self.__class__.__name__,
decode_path=decode_path,
expl_tlen=self.expl_tlen if self.expled else None,
expl_llen=self.expl_llen if self.expled else None,
expl_vlen=self.expl_vlen if self.expled else None,
+ expl_lenindef=self.expl_lenindef,
+ lenindef=self.lenindef,
+ bered=self.bered,
)
defined_by, defined = self.defined or (None, None)
if defined_by is not None:
yield defined.pps(
- decode_path=decode_path + (decode_path_defby(defined_by),)
+ decode_path=decode_path + (DecodePathDefBy(defined_by),)
)
+ for pp in self.pps_lenindef(decode_path):
+ yield pp
########################################################################
def abs_decode_path(decode_path, rel_path):
"""Create an absolute decode path from current and relative ones
- :param decode_path: current decode path, starting point.
- Tuple of strings
+ :param decode_path: current decode path, starting point. Tuple of strings
:param rel_path: relative path to ``decode_path``. Tuple of strings.
If first tuple's element is "/", then treat it as
an absolute path, ignoring ``decode_path`` as
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:
+ You can determine if sequence is ready to be encoded:
>>> ext.ready
False
Assign ``None`` to remove value from sequence.
- You can know if value exists/set in the sequence and take its value:
+ You can set values in Sequence during its initialization:
+
+ >>> AlgorithmIdentifier((
+ ("algorithm", ObjectIdentifier("1.2.3")),
+ ("parameters", Any(Null()))
+ ))
+ AlgorithmIdentifier SEQUENCE[algorithm: OBJECT IDENTIFIER 1.2.3; parameters: ANY 0500 OPTIONAL]
+
+ You can determine if value exists/set in the sequence and take its value:
>>> "extnID" in ext, "extnValue" in ext, "critical" in ext
(True, True, False)
All defaulted values are always optional.
- .. _strict_default_existence_ctx:
-
- .. warning::
+ .. _allow_default_values_ctx:
- When decoded DER contains defaulted value inside, then
- technically this is not valid DER encoding. But we allow and pass
- it **by default**. Of course reencoding of that kind of DER will
- result in different binary representation (validly without
- defaulted value inside). You can enable strict defaulted values
- existence validation by setting ``"strict_default_existence":
- True`` :ref:`context <ctx>` option -- decoding process will raise
- an exception if defaulted value is met.
+ DER prohibits default value encoding and will raise an error if
+ default value is unexpectedly met during decode.
+ If :ref:`bered <bered_ctx>` context option is set, then no error
+ will be raised, but ``bered`` attribute set. You can disable strict
+ defaulted values existence validation by setting
+ ``"allow_default_values": True`` :ref:`context <ctx>` option.
Two sequences are equal if they have equal specification (schema),
implicit/explicit tagging and the same values.
)
self._value = {}
if value is not None:
- self._value = self._value_sanitize(value)
+ if issubclass(value.__class__, Sequence):
+ self._value = value._value
+ elif hasattr(value, "__iter__"):
+ for seq_key, seq_value in value:
+ self[seq_key] = seq_value
+ else:
+ raise InvalidValueType((Sequence,))
if default is not None:
- default_value = self._value_sanitize(default)
+ if not issubclass(default.__class__, Sequence):
+ raise InvalidValueType((Sequence,))
+ default_value = default._value
default_obj = self.__class__(impl=self.tag, expl=self._expl)
default_obj.specs = self.specs
default_obj._value = default_value
if value is None:
self._value = default_obj.copy()._value
- def _value_sanitize(self, value):
- if not issubclass(value.__class__, Sequence):
- raise InvalidValueType((Sequence,))
- return value._value
-
@property
def ready(self):
for name, spec in self.specs.items():
return False
return True
+ @property
+ def bered(self):
+ if self.expl_lenindef or self.lenindef or self.ber_encoded:
+ return True
+ return any(value.bered for value in self._value.values())
+
def copy(self):
obj = self.__class__(schema=self.specs)
obj.tag = self.tag
obj.offset = self.offset
obj.llen = self.llen
obj.vlen = self.vlen
+ obj.expl_lenindef = self.expl_lenindef
+ obj.lenindef = self.lenindef
+ obj.ber_encoded = self.ber_encoded
obj._value = {k: v.copy() for k, v in self._value.items()}
return obj
v = b"".join(self._encoded_values())
return b"".join((self.tag, len_encode(len(v)), v))
- def _decode(self, tlv, offset, decode_path, ctx):
+ def _decode(self, tlv, offset, decode_path, ctx, tag_only):
try:
t, tlen, lv = tag_strip(tlv)
except DecodeError as err:
decode_path=decode_path,
offset=offset,
)
+ if tag_only: # pragma: no cover
+ return
+ lenindef = False
+ ctx_bered = ctx.get("bered", False)
try:
l, llen, v = len_decode(lv)
+ except LenIndefForm as err:
+ if not ctx_bered:
+ raise err.__class__(
+ msg=err.msg,
+ klass=self.__class__,
+ decode_path=decode_path,
+ offset=offset,
+ )
+ l, llen, v = 0, 1, lv[1:]
+ lenindef = True
except DecodeError as err:
raise err.__class__(
msg=err.msg,
decode_path=decode_path,
offset=offset,
)
- v, tail = v[:l], v[l:]
+ if not lenindef:
+ v, tail = v[:l], v[l:]
+ vlen = 0
sub_offset = offset + tlen + llen
values = {}
+ ber_encoded = False
+ ctx_allow_default_values = ctx.get("allow_default_values", False)
for name, spec in self.specs.items():
- if len(v) == 0 and spec.optional:
+ if spec.optional and (
+ (lenindef and v[:EOC_LEN].tobytes() == EOC) or
+ len(v) == 0
+ ):
continue
sub_decode_path = decode_path + (name,)
try:
leavemm=True,
decode_path=sub_decode_path,
ctx=ctx,
+ _ctx_immutable=False,
)
except TagMismatch:
if spec.optional:
continue
raise
- defined = get_def_by_path(ctx.get("defines", ()), sub_decode_path)
+ defined = get_def_by_path(ctx.get("_defines", ()), sub_decode_path)
if defined is not None:
defined_by, defined_spec = defined
if issubclass(value.__class__, SequenceOf):
for i, _value in enumerate(value):
sub_sub_decode_path = sub_decode_path + (
str(i),
- decode_path_defby(defined_by),
+ DecodePathDefBy(defined_by),
)
defined_value, defined_tail = defined_spec.decode(
memoryview(bytes(_value)),
- sub_offset + value.tlen + value.llen,
+ sub_offset + (
+ (value.tlen + value.llen + value.expl_tlen + value.expl_llen)
+ if value.expled else (value.tlen + value.llen)
+ ),
leavemm=True,
decode_path=sub_sub_decode_path,
ctx=ctx,
+ _ctx_immutable=False,
)
if len(defined_tail) > 0:
raise DecodeError(
else:
defined_value, defined_tail = defined_spec.decode(
memoryview(bytes(value)),
- sub_offset + value.tlen + value.llen,
+ sub_offset + (
+ (value.tlen + value.llen + value.expl_tlen + value.expl_llen)
+ if value.expled else (value.tlen + value.llen)
+ ),
leavemm=True,
- decode_path=sub_decode_path + (decode_path_defby(defined_by),),
+ decode_path=sub_decode_path + (DecodePathDefBy(defined_by),),
ctx=ctx,
+ _ctx_immutable=False,
)
if len(defined_tail) > 0:
raise DecodeError(
"remaining data",
klass=self.__class__,
- decode_path=sub_decode_path + (decode_path_defby(defined_by),),
+ decode_path=sub_decode_path + (DecodePathDefBy(defined_by),),
offset=offset,
)
value.defined = (defined_by, defined_value)
- sub_offset += (value.expl_tlvlen if value.expled else value.tlvlen)
+ value_len = value.fulllen
+ vlen += value_len
+ sub_offset += value_len
v = v_tail
if spec.default is not None and value == spec.default:
- if ctx.get("strict_default_existence", False):
+ if ctx_bered or ctx_allow_default_values:
+ ber_encoded = True
+ else:
raise DecodeError(
"DEFAULT value met",
klass=self.__class__,
decode_path=sub_decode_path,
offset=sub_offset,
)
- else:
- continue
values[name] = value
spec_defines = getattr(spec, "defines", ())
for rel_path, schema in spec_defines:
defined = schema.get(value, None)
if defined is not None:
- ctx.setdefault("defines", []).append((
+ ctx.setdefault("_defines", []).append((
abs_decode_path(sub_decode_path[:-1], rel_path),
(value, defined),
))
- if len(v) > 0:
+ if lenindef:
+ if v[:EOC_LEN].tobytes() != EOC:
+ raise DecodeError(
+ "no EOC",
+ klass=self.__class__,
+ decode_path=decode_path,
+ offset=offset,
+ )
+ tail = v[EOC_LEN:]
+ vlen += EOC_LEN
+ elif len(v) > 0:
raise DecodeError(
"remaining data",
klass=self.__class__,
expl=self._expl,
default=self.default,
optional=self.optional,
- _decoded=(offset, llen, l),
+ _decoded=(offset, llen, vlen),
)
obj._value = values
+ obj.lenindef = lenindef
+ obj.ber_encoded = ber_encoded
return obj, tail
def __repr__(self):
_value = self._value.get(name)
if _value is None:
continue
- cols.append(repr(_value))
- return "%s[%s]" % (value, ", ".join(cols))
+ cols.append("%s: %s" % (name, repr(_value)))
+ return "%s[%s]" % (value, "; ".join(cols))
def pps(self, decode_path=()):
yield _pp(
+ obj=self,
asn1_type_name=self.asn1_type_name,
obj_name=self.__class__.__name__,
decode_path=decode_path,
expl_tlen=self.expl_tlen if self.expled else None,
expl_llen=self.expl_llen if self.expled else None,
expl_vlen=self.expl_vlen if self.expled else None,
+ expl_lenindef=self.expl_lenindef,
+ lenindef=self.lenindef,
+ ber_encoded=self.ber_encoded,
+ bered=self.bered,
)
for name in self.specs:
value = self._value.get(name)
if value is None:
continue
yield value.pps(decode_path=decode_path + (name,))
+ for pp in self.pps_lenindef(decode_path):
+ yield pp
class Set(Sequence):
"""``SET`` structure type
Its usage is identical to :py:class:`pyderasn.Sequence`.
+
+ .. _allow_unordered_set_ctx:
+
+ DER prohibits unordered values encoding and will raise an error
+ during decode. If If :ref:`bered <bered_ctx>` context option is set,
+ then no error will occure. Also you can disable strict values
+ ordering check by setting ``"allow_unordered_set": True``
+ :ref:`context <ctx>` option.
"""
__slots__ = ()
tag_default = tag_encode(form=TagFormConstructed, num=17)
v = b"".join(raws)
return b"".join((self.tag, len_encode(len(v)), v))
- def _decode(self, tlv, offset, decode_path, ctx):
+ def _decode(self, tlv, offset, decode_path, ctx, tag_only):
try:
t, tlen, lv = tag_strip(tlv)
except DecodeError as err:
decode_path=decode_path,
offset=offset,
)
+ if tag_only:
+ return
+ lenindef = False
+ ctx_bered = ctx.get("bered", False)
try:
l, llen, v = len_decode(lv)
+ except LenIndefForm as err:
+ if not ctx_bered:
+ raise err.__class__(
+ msg=err.msg,
+ klass=self.__class__,
+ decode_path=decode_path,
+ offset=offset,
+ )
+ l, llen, v = 0, 1, lv[1:]
+ lenindef = True
except DecodeError as err:
raise err.__class__(
msg=err.msg,
klass=self.__class__,
offset=offset,
)
- v, tail = v[:l], v[l:]
+ if not lenindef:
+ v, tail = v[:l], v[l:]
+ vlen = 0
sub_offset = offset + tlen + llen
values = {}
+ ber_encoded = False
+ ctx_allow_default_values = ctx.get("allow_default_values", False)
+ ctx_allow_unordered_set = ctx.get("allow_unordered_set", False)
+ value_prev = memoryview(v[:0])
specs_items = self.specs.items
while len(v) > 0:
+ if lenindef and v[:EOC_LEN].tobytes() == EOC:
+ break
for name, spec in specs_items():
+ sub_decode_path = decode_path + (name,)
try:
- value, v_tail = spec.decode(
+ spec.decode(
v,
sub_offset,
leavemm=True,
- decode_path=decode_path + (name,),
+ decode_path=sub_decode_path,
ctx=ctx,
+ tag_only=True,
+ _ctx_immutable=False,
)
except TagMismatch:
continue
- sub_offset += (
- value.expl_tlvlen if value.expled else value.tlvlen
- )
- v = v_tail
- if spec.default is None or value != spec.default: # pragma: no cover
- # SeqMixing.test_encoded_default_accepted covers that place
- values[name] = value
break
else:
raise TagMismatch(
decode_path=decode_path,
offset=offset,
)
+ value, v_tail = spec.decode(
+ v,
+ sub_offset,
+ leavemm=True,
+ decode_path=sub_decode_path,
+ ctx=ctx,
+ _ctx_immutable=False,
+ )
+ value_len = value.fulllen
+ if value_prev.tobytes() > v[:value_len].tobytes():
+ if ctx_bered or ctx_allow_unordered_set:
+ ber_encoded = True
+ else:
+ raise DecodeError(
+ "unordered " + self.asn1_type_name,
+ klass=self.__class__,
+ decode_path=sub_decode_path,
+ offset=sub_offset,
+ )
+ if spec.default is None or value != spec.default:
+ pass
+ elif ctx_bered or ctx_allow_default_values:
+ ber_encoded = True
+ else:
+ raise DecodeError(
+ "DEFAULT value met",
+ klass=self.__class__,
+ decode_path=sub_decode_path,
+ offset=sub_offset,
+ )
+ values[name] = value
+ value_prev = v[:value_len]
+ sub_offset += value_len
+ vlen += value_len
+ v = v_tail
obj = self.__class__(
schema=self.specs,
impl=self.tag,
expl=self._expl,
default=self.default,
optional=self.optional,
- _decoded=(offset, llen, l),
+ _decoded=(offset, llen, vlen + (EOC_LEN if lenindef else 0)),
)
+ if lenindef:
+ if v[:EOC_LEN].tobytes() != EOC:
+ raise DecodeError(
+ "no EOC",
+ klass=self.__class__,
+ decode_path=decode_path,
+ offset=offset,
+ )
+ tail = v[EOC_LEN:]
+ obj.lenindef = True
obj._value = values
+ if not obj.ready:
+ raise DecodeError(
+ "not all values are ready",
+ klass=self.__class__,
+ decode_path=decode_path,
+ offset=offset,
+ )
+ obj.ber_encoded = ber_encoded
return obj, tail
def ready(self):
return all(v.ready for v in self._value)
+ @property
+ def bered(self):
+ if self.expl_lenindef or self.lenindef or self.ber_encoded:
+ return True
+ return any(v.bered for v in self._value)
+
def copy(self):
obj = self.__class__(schema=self.spec)
obj._bound_min = self._bound_min
obj.offset = self.offset
obj.llen = self.llen
obj.vlen = self.vlen
+ obj.expl_lenindef = self.expl_lenindef
+ obj.lenindef = self.lenindef
+ obj.ber_encoded = self.ber_encoded
obj._value = [v.copy() for v in self._value]
return obj
v = b"".join(self._encoded_values())
return b"".join((self.tag, len_encode(len(v)), v))
- def _decode(self, tlv, offset, decode_path, ctx):
+ def _decode(self, tlv, offset, decode_path, ctx, tag_only, ordering_check=False):
try:
t, tlen, lv = tag_strip(tlv)
except DecodeError as err:
decode_path=decode_path,
offset=offset,
)
+ if tag_only:
+ return
+ lenindef = False
+ ctx_bered = ctx.get("bered", False)
try:
l, llen, v = len_decode(lv)
+ except LenIndefForm as err:
+ if not ctx_bered:
+ raise err.__class__(
+ msg=err.msg,
+ klass=self.__class__,
+ decode_path=decode_path,
+ offset=offset,
+ )
+ l, llen, v = 0, 1, lv[1:]
+ lenindef = True
except DecodeError as err:
raise err.__class__(
msg=err.msg,
decode_path=decode_path,
offset=offset,
)
- v, tail = v[:l], v[l:]
+ if not lenindef:
+ v, tail = v[:l], v[l:]
+ vlen = 0
sub_offset = offset + tlen + llen
_value = []
+ ctx_allow_unordered_set = ctx.get("allow_unordered_set", False)
+ value_prev = memoryview(v[:0])
+ ber_encoded = False
spec = self.spec
while len(v) > 0:
+ if lenindef and v[:EOC_LEN].tobytes() == EOC:
+ break
+ sub_decode_path = decode_path + (str(len(_value)),)
value, v_tail = spec.decode(
v,
sub_offset,
leavemm=True,
- decode_path=decode_path + (str(len(_value)),),
+ decode_path=sub_decode_path,
ctx=ctx,
+ _ctx_immutable=False,
)
- sub_offset += (value.expl_tlvlen if value.expled else value.tlvlen)
- v = v_tail
+ value_len = value.fulllen
+ if ordering_check:
+ if value_prev.tobytes() > v[:value_len].tobytes():
+ if ctx_bered or ctx_allow_unordered_set:
+ ber_encoded = True
+ else:
+ raise DecodeError(
+ "unordered " + self.asn1_type_name,
+ klass=self.__class__,
+ decode_path=sub_decode_path,
+ offset=sub_offset,
+ )
+ value_prev = v[:value_len]
_value.append(value)
- obj = self.__class__(
- value=_value,
- schema=spec,
- bounds=(self._bound_min, self._bound_max),
- impl=self.tag,
- expl=self._expl,
- default=self.default,
- optional=self.optional,
- _decoded=(offset, llen, l),
- )
+ sub_offset += value_len
+ vlen += value_len
+ v = v_tail
+ try:
+ obj = self.__class__(
+ value=_value,
+ schema=spec,
+ bounds=(self._bound_min, self._bound_max),
+ impl=self.tag,
+ expl=self._expl,
+ default=self.default,
+ optional=self.optional,
+ _decoded=(offset, llen, vlen + (EOC_LEN if lenindef else 0)),
+ )
+ except BoundsError as err:
+ raise DecodeError(
+ msg=str(err),
+ klass=self.__class__,
+ decode_path=decode_path,
+ offset=offset,
+ )
+ if lenindef:
+ if v[:EOC_LEN].tobytes() != EOC:
+ raise DecodeError(
+ "no EOC",
+ klass=self.__class__,
+ decode_path=decode_path,
+ offset=offset,
+ )
+ obj.lenindef = True
+ tail = v[EOC_LEN:]
+ obj.ber_encoded = ber_encoded
return obj, tail
def __repr__(self):
def pps(self, decode_path=()):
yield _pp(
+ obj=self,
asn1_type_name=self.asn1_type_name,
obj_name=self.__class__.__name__,
decode_path=decode_path,
expl_tlen=self.expl_tlen if self.expled else None,
expl_llen=self.expl_llen if self.expled else None,
expl_vlen=self.expl_vlen if self.expled else None,
+ expl_lenindef=self.expl_lenindef,
+ lenindef=self.lenindef,
+ ber_encoded=self.ber_encoded,
+ bered=self.bered,
)
for i, value in enumerate(self._value):
yield value.pps(decode_path=decode_path + (str(i),))
+ for pp in self.pps_lenindef(decode_path):
+ yield pp
class SetOf(SequenceOf):
v = b"".join(raws)
return b"".join((self.tag, len_encode(len(v)), v))
+ def _decode(self, tlv, offset, decode_path, ctx, tag_only):
+ return super(SetOf, self)._decode(
+ tlv,
+ offset,
+ decode_path,
+ ctx,
+ tag_only,
+ ordering_check=True,
+ )
+
def obj_by_path(pypath): # pragma: no cover
"""Import object specified as string Python path
choice = PrimitiveTypes()
choice.specs["SequenceOf"] = SequenceOf(schema=choice)
choice.specs["SetOf"] = SetOf(schema=choice)
- for i in range(31):
+ for i in six_xrange(31):
choice.specs["SequenceOf%d" % i] = SequenceOf(
schema=choice,
expl=tag_ctxc(i),
__slots__ = ()
schema = choice
- def pprint_any(obj, oids=None):
+ def pprint_any(
+ obj,
+ oids=None,
+ with_colours=False,
+ with_decode_path=False,
+ decode_path_only=(),
+ ):
def _pprint_pps(pps):
for pp in pps:
if hasattr(pp, "_fields"):
+ if (
+ decode_path_only != () and
+ pp.decode_path[:len(decode_path_only)] != decode_path_only
+ ):
+ continue
if pp.asn1_type_name == Choice.asn1_type_name:
continue
pp_kwargs = pp._asdict()
oids=oids,
with_offsets=True,
with_blob=False,
+ with_colours=with_colours,
+ with_decode_path=with_decode_path,
+ decode_path_len_decrease=len(decode_path_only),
)
- for row in pp_console_blob(pp):
+ for row in pp_console_blob(
+ pp,
+ decode_path_len_decrease=len(decode_path_only),
+ ):
yield row
else:
for row in _pprint_pps(pp):
def main(): # pragma: no cover
import argparse
- parser = argparse.ArgumentParser(description="PyDERASN ASN.1 DER decoder")
+ parser = argparse.ArgumentParser(description="PyDERASN ASN.1 BER/DER decoder")
parser.add_argument(
"--skip",
type=int,
"--defines-by-path",
help="Python path to decoder's defines_by_path",
)
+ parser.add_argument(
+ "--nobered",
+ action="store_true",
+ help="Disallow BER encoding",
+ )
+ parser.add_argument(
+ "--print-decode-path",
+ action="store_true",
+ help="Print decode paths",
+ )
+ parser.add_argument(
+ "--decode-path-only",
+ help="Print only specified decode path",
+ )
+ parser.add_argument(
+ "--allow-expl-oob",
+ action="store_true",
+ help="Allow explicit tag out-of-bound",
+ )
parser.add_argument(
"DERFile",
type=argparse.FileType("rb"),
pprinter = partial(pprint, big_blobs=True)
else:
schema, pprinter = generic_decoder()
- obj, tail = schema().decode(
- der,
- ctx=(
- None if args.defines_by_path is None else
- {"defines_by_path": obj_by_path(args.defines_by_path)}
+ ctx = {
+ "bered": not args.nobered,
+ "allow_expl_oob": args.allow_expl_oob,
+ }
+ if args.defines_by_path is not None:
+ ctx["defines_by_path"] = obj_by_path(args.defines_by_path)
+ obj, tail = schema().decode(der, ctx=ctx)
+ print(pprinter(
+ obj,
+ oids=oids,
+ with_colours=True if environ.get("NO_COLOR") is None else False,
+ with_decode_path=args.print_decode_path,
+ decode_path_only=(
+ () if args.decode_path_only is None else
+ tuple(args.decode_path_only.split(":"))
),
- )
- print(pprinter(obj, oids=oids))
+ ))
if tail != b"":
print("\nTrailing data: %s" % hexenc(tail))