#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as
* :ref:`allow_default_values <allow_default_values_ctx>`
* :ref:`allow_expl_oob <allow_expl_oob_ctx>`
* :ref:`allow_default_values <allow_default_values_ctx>`
* :ref:`allow_expl_oob <allow_expl_oob_ctx>`
:ref:`context <ctx>` argument to True. Indefinite lengths and
constructed primitive types should be parsed successfully.
:ref:`context <ctx>` argument to True. Indefinite lengths and
constructed primitive types should be parsed successfully.
- STRING``, ``SEQUENCE``, ``SET`` can contain it.
+ 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 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.
.. autofunction:: pyderasn.hexenc
.. autofunction:: pyderasn.hexdec
.. autofunction:: pyderasn.tag_encode
.. autofunction:: pyderasn.hexenc
.. autofunction:: pyderasn.hexdec
.. autofunction:: pyderasn.tag_encode
from codecs import getencoder
from collections import namedtuple
from collections import OrderedDict
from codecs import getencoder
from collections import namedtuple
from collections import OrderedDict
def __init__(self, msg="", klass=None, decode_path=(), offset=0):
"""
:param str msg: reason of decode failing
def __init__(self, msg="", klass=None, decode_path=(), offset=0):
"""
:param str msg: reason of decode failing
def __init__(self, name):
super(ObjUnknown, self).__init__()
self.name = name
def __init__(self, name):
super(ObjUnknown, self).__init__()
self.name = name
def __init__(self, name):
super(ObjNotReady, self).__init__()
self.name = name
def __init__(self, name):
super(ObjNotReady, self).__init__()
self.name = name
def __init__(self, expected_types):
super(InvalidValueType, self).__init__()
self.expected_types = expected_types
def __init__(self, expected_types):
super(InvalidValueType, self).__init__()
self.expected_types = expected_types
def __init__(self, bound_min, value, bound_max):
super(BoundsError, self).__init__()
self.bound_min = bound_min
def __init__(self, bound_min, value, bound_max):
super(BoundsError, self).__init__()
self.bound_min = bound_min
: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 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 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 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)
return self.expl_tlvlen if self.expled else self.tlvlen
def pps_lenindef(self, decode_path):
return self.expl_tlvlen if self.expled else self.tlvlen
def pps_lenindef(self, decode_path):
- cols.append(_colourize(col, "red", with_colours, ()))
+ col = _colourize(col, "red", with_colours, ())
+ col += _colourize("B", "red", with_colours) if pp.bered else " "
+ cols.append(col)
cols.append(_colourize(col, "blue", with_colours))
if pp.asn1_type_name.replace(" ", "") != pp.obj_name.upper():
cols.append(_colourize(pp.obj_name, "magenta", with_colours))
cols.append(_colourize(col, "blue", with_colours))
if pp.asn1_type_name.replace(" ", "") != pp.obj_name.upper():
cols.append(_colourize(pp.obj_name, "magenta", with_colours))
cols.append(_colourize("BER", "red", with_colours))
cols.append(_colourize(pp.asn1_type_name, "cyan", with_colours))
if pp.value is not None:
cols.append(_colourize("BER", "red", with_colours))
cols.append(_colourize(pp.asn1_type_name, "cyan", with_colours))
if pp.value is not None:
+ 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))
if with_blob:
if isinstance(pp.blob, binary_type):
cols.append(hexenc(pp.blob))
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()
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()
elif isinstance(pp.blob, tuple):
yield " ".join(cols + [", ".join(pp.blob)])
elif isinstance(pp.blob, tuple):
yield " ".join(cols + [", ".join(pp.blob)])
if first_octet == 0:
value = False
elif first_octet == 0xFF:
value = True
elif ctx.get("bered", False):
value = True
if first_octet == 0:
value = False
elif first_octet == 0xFF:
value = True
elif ctx.get("bered", False):
value = True
asn1_type_name=self.asn1_type_name,
obj_name=self.__class__.__name__,
decode_path=decode_path,
asn1_type_name=self.asn1_type_name,
obj_name=self.__class__.__name__,
decode_path=decode_path,
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,
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,
- if issubclass(value.__class__, Integer):
- value = value._value
- elif isinstance(value, integer_types):
+ if isinstance(value, integer_types):
elif isinstance(value, str):
value = self.specs.get(value)
if value is None:
elif isinstance(value, str):
value = self.specs.get(value)
if value is None:
asn1_type_name=self.asn1_type_name,
obj_name=self.__class__.__name__,
decode_path=decode_path,
asn1_type_name=self.asn1_type_name,
obj_name=self.__class__.__name__,
decode_path=decode_path,
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,
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,
return bit_len, bytes(octets)
def _value_sanitize(self, value):
return bit_len, bytes(octets)
def _value_sanitize(self, value):
if isinstance(value, (string_types, binary_type)):
if (
isinstance(value, string_types) and
if isinstance(value, (string_types, binary_type)):
if (
isinstance(value, string_types) and
raise ValueError("B's coding contains unacceptable chars")
return self._bits2octets(value)
elif value.endswith("'H"):
raise ValueError("B's coding contains unacceptable chars")
return self._bits2octets(value)
elif value.endswith("'H"):
return self._bits2octets("".join(
("1" if bit in bits else "0")
for bit in six_xrange(max(bits) + 1)
))
return self._bits2octets("".join(
("1" if bit in bits else "0")
for bit in six_xrange(max(bits) + 1)
))
return
return self._decode_chunk(lv, offset, decode_path, ctx)
if t == self.tag_constructed:
return
return self._decode_chunk(lv, offset, decode_path, ctx)
if t == self.tag_constructed:
_decoded=(offset, llen, vlen + (EOC_LEN if lenindef else 0)),
)
obj.lenindef = lenindef
_decoded=(offset, llen, vlen + (EOC_LEN if lenindef else 0)),
)
obj.lenindef = lenindef
asn1_type_name=self.asn1_type_name,
obj_name=self.__class__.__name__,
decode_path=decode_path,
asn1_type_name=self.asn1_type_name,
obj_name=self.__class__.__name__,
decode_path=decode_path,
expl_vlen=self.expl_vlen if self.expled else None,
expl_lenindef=self.expl_lenindef,
lenindef=self.lenindef,
expl_vlen=self.expl_vlen if self.expled else None,
expl_lenindef=self.expl_lenindef,
lenindef=self.lenindef,
- if issubclass(value.__class__, OctetString):
- value = value._value
- elif isinstance(value, binary_type):
+ if isinstance(value, binary_type):
else:
raise InvalidValueType((self.__class__, bytes))
if not self._bound_min <= len(value) <= self._bound_max:
else:
raise InvalidValueType((self.__class__, bytes))
if not self._bound_min <= len(value) <= self._bound_max:
asn1_type_name=self.asn1_type_name,
obj_name=self.__class__.__name__,
decode_path=decode_path,
asn1_type_name=self.asn1_type_name,
obj_name=self.__class__.__name__,
decode_path=decode_path,
expl_vlen=self.expl_vlen if self.expled else None,
expl_lenindef=self.expl_lenindef,
lenindef=self.lenindef,
expl_vlen=self.expl_vlen if self.expled else None,
expl_lenindef=self.expl_lenindef,
lenindef=self.lenindef,
asn1_type_name=self.asn1_type_name,
obj_name=self.__class__.__name__,
decode_path=decode_path,
asn1_type_name=self.asn1_type_name,
obj_name=self.__class__.__name__,
decode_path=decode_path,
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,
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,
optional=self.optional,
_decoded=(offset, llen, l),
)
optional=self.optional,
_decoded=(offset, llen, l),
)
asn1_type_name=self.asn1_type_name,
obj_name=self.__class__.__name__,
decode_path=decode_path,
asn1_type_name=self.asn1_type_name,
obj_name=self.__class__.__name__,
decode_path=decode_path,
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,
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,
if isinstance(value, self.__class__):
value = value._value
elif isinstance(value, integer_types):
if isinstance(value, self.__class__):
value = value._value
elif isinstance(value, integer_types):
asn1_type_name=self.asn1_type_name,
obj_name=self.__class__.__name__,
decode_path=decode_path,
asn1_type_name=self.asn1_type_name,
obj_name=self.__class__.__name__,
decode_path=decode_path,
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,
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,
-class NumericString(CommonString):
+class AllowableCharsMixin(object):
+ @property
+ def allowable_chars(self):
+ if PY2:
+ return self._allowable_chars
+ return frozenset(six_unichr(c) for c in self._allowable_chars)
+
+
+class NumericString(AllowableCharsMixin, CommonString):
- Its value is properly sanitized: only ASCII digits can be stored.
+ 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'])
def _value_sanitize(self, value):
value = super(NumericString, self)._value_sanitize(value)
def _value_sanitize(self, value):
value = super(NumericString, self)._value_sanitize(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'])
+ """
+ _allowable_chars = frozenset(
+ (ascii_letters + digits + " '()+,-./:=?").encode("ascii")
+ )
+
+ def _value_sanitize(self, value):
+ value = super(PrintableString, self)._value_sanitize(value)
+ if not frozenset(value) <= self._allowable_chars:
+ raise DecodeError("non-printable value")
+ return value
if isinstance(value, binary_type):
try:
value_decoded = value.decode("ascii")
if isinstance(value, binary_type):
try:
value_decoded = value.decode("ascii")
asn1_type_name=self.asn1_type_name,
obj_name=self.__class__.__name__,
decode_path=decode_path,
asn1_type_name=self.asn1_type_name,
obj_name=self.__class__.__name__,
decode_path=decode_path,
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,
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,
fmt_ms = "%Y%m%d%H%M%S.%fZ"
def _value_sanitize(self, value):
fmt_ms = "%Y%m%d%H%M%S.%fZ"
def _value_sanitize(self, value):
if isinstance(value, binary_type):
try:
value_decoded = value.decode("ascii")
if isinstance(value, binary_type):
try:
value_decoded = value.decode("ascii")
self._value = default_obj.copy()._value
def _value_sanitize(self, value):
self._value = default_obj.copy()._value
def _value_sanitize(self, value):
if isinstance(value, tuple) and len(value) == 2:
choice, obj = value
spec = self.specs.get(choice)
if isinstance(value, tuple) and len(value) == 2:
choice, obj = value
spec = self.specs.get(choice)
if not isinstance(obj, spec.__class__):
raise InvalidValueType((spec,))
return (choice, spec(obj))
if not isinstance(obj, spec.__class__):
raise InvalidValueType((spec,))
return (choice, spec(obj))
raise InvalidValueType((self.__class__, tuple))
@property
def ready(self):
return self._value is not None and self._value[1].ready
raise InvalidValueType((self.__class__, tuple))
@property
def ready(self):
return self._value is not None and self._value[1].ready
return self._value[1].encode()
def _decode(self, tlv, offset, decode_path, ctx, tag_only):
return self._value[1].encode()
def _decode(self, tlv, offset, decode_path, ctx, tag_only):
asn1_type_name=self.asn1_type_name,
obj_name=self.__class__.__name__,
decode_path=decode_path,
asn1_type_name=self.asn1_type_name,
obj_name=self.__class__.__name__,
decode_path=decode_path,
if isinstance(value, self.__class__):
return value._value
if isinstance(value, Obj):
return value.encode()
if isinstance(value, self.__class__):
return value._value
if isinstance(value, Obj):
return value.encode()
raise InvalidValueType((self.__class__, Obj, binary_type))
@property
def ready(self):
return self._value is not None
raise InvalidValueType((self.__class__, Obj, binary_type))
@property
def ready(self):
return self._value is not None
asn1_type_name=self.asn1_type_name,
obj_name=self.__class__.__name__,
decode_path=decode_path,
asn1_type_name=self.asn1_type_name,
obj_name=self.__class__.__name__,
decode_path=decode_path,
expl_vlen=self.expl_vlen if self.expled else None,
expl_lenindef=self.expl_lenindef,
lenindef=self.lenindef,
expl_vlen=self.expl_vlen if self.expled else None,
expl_lenindef=self.expl_lenindef,
lenindef=self.lenindef,
)
defined_by, defined = self.defined or (None, None)
if defined_by is not None:
)
defined_by, defined = self.defined or (None, None)
if defined_by is not None:
def abs_decode_path(decode_path, rel_path):
"""Create an absolute decode path from current and relative ones
def abs_decode_path(decode_path, rel_path):
"""Create an absolute decode path from current and relative ones
: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
: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
- obj._value = {k: v.copy() for k, v in self._value.items()}
+ 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 iteritems(self._value)}
asn1_type_name=self.asn1_type_name,
obj_name=self.__class__.__name__,
decode_path=decode_path,
asn1_type_name=self.asn1_type_name,
obj_name=self.__class__.__name__,
decode_path=decode_path,
expl_vlen=self.expl_vlen if self.expled else None,
expl_lenindef=self.expl_lenindef,
lenindef=self.lenindef,
expl_vlen=self.expl_vlen if self.expled else None,
expl_lenindef=self.expl_lenindef,
lenindef=self.lenindef,
+
+ .. _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.
def _decode(self, tlv, offset, decode_path, ctx, tag_only):
try:
t, tlen, lv = tag_strip(tlv)
def _decode(self, tlv, offset, decode_path, ctx, tag_only):
try:
t, tlen, lv = tag_strip(tlv)
- sub_offset += value_len
- vlen += value_len
- v = v_tail
+ 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,
+ )
optional=self.optional,
_decoded=(offset, llen, vlen + (EOC_LEN if lenindef else 0)),
)
optional=self.optional,
_decoded=(offset, llen, vlen + (EOC_LEN if lenindef else 0)),
)
v = b"".join(self._encoded_values())
return b"".join((self.tag, len_encode(len(v)), v))
v = b"".join(self._encoded_values())
return b"".join((self.tag, len_encode(len(v)), v))
- def _decode(self, tlv, offset, decode_path, ctx, tag_only):
+ def _decode(self, tlv, offset, decode_path, ctx, tag_only, ordering_check=False):
try:
t, tlen, lv = tag_strip(tlv)
except DecodeError as err:
try:
t, tlen, lv = tag_strip(tlv)
except DecodeError as err:
try:
l, llen, v = len_decode(lv)
except LenIndefForm as err:
try:
l, llen, v = len_decode(lv)
except LenIndefForm as err:
+ 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)
asn1_type_name=self.asn1_type_name,
obj_name=self.__class__.__name__,
decode_path=decode_path,
asn1_type_name=self.asn1_type_name,
obj_name=self.__class__.__name__,
decode_path=decode_path,
expl_vlen=self.expl_vlen if self.expled else None,
expl_lenindef=self.expl_lenindef,
lenindef=self.lenindef,
expl_vlen=self.expl_vlen if self.expled else None,
expl_lenindef=self.expl_lenindef,
lenindef=self.lenindef,
)
for i, value in enumerate(self._value):
yield value.pps(decode_path=decode_path + (str(i),))
)
for i, value in enumerate(self._value):
yield value.pps(decode_path=decode_path + (str(i),))
+ 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,
+ )
+
choice = PrimitiveTypes()
choice.specs["SequenceOf"] = SequenceOf(schema=choice)
choice.specs["SetOf"] = SetOf(schema=choice)
choice = PrimitiveTypes()
choice.specs["SequenceOf"] = SequenceOf(schema=choice)
choice.specs["SetOf"] = SetOf(schema=choice)