#!/usr/bin/env python
# coding: utf-8
# PyDERASN -- Python ASN.1 DER/BER codec with abstract structures
-# Copyright (C) 2017-2019 Sergey Matveev <stargrave@stargrave.org>
+# Copyright (C) 2017-2020 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
# GNU Lesser General Public License for more details.
#
# 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/>.
+# License along with this program. If not, see <http://www.gnu.org/licenses/>.
"""Python ASN.1 DER/BER codec with abstract structures
This library allows you to marshal various structures in ASN.1 DER
ability to specify mapping between some OID and field that must be
decoded with specific specification.
+.. _defines:
+
defines kwarg
_____________
where ``decode_path`` is a tuple holding so-called decode path to the
exact :py:class:`pyderasn.ObjectIdentifier` field you want to apply
-``defines``, holding exactly the same value as accepted in its keyword
-argument.
+``defines``, holding exactly the same value as accepted in its
+:ref:`keyword argument <defines>`.
For example, again for CMS, you want to automatically decode
``SignedData`` and CMC's (:rfc:`5272`) ``PKIData`` and ``PKIResponse``
This option should be used only for skipping some decode errors, just
to see the decoded structure somehow.
+Base Obj
+--------
+.. autoclass:: pyderasn.Obj
+ :members:
+
Primitive types
---------------
PrintableString
_______________
.. autoclass:: pyderasn.PrintableString
+ :members: __init__
UTCTime
_______
.. autofunction:: pyderasn.tag_decode
.. autofunction:: pyderasn.tag_ctxp
.. autofunction:: pyderasn.tag_ctxc
-.. autoclass:: pyderasn.Obj
.. autoclass:: pyderasn.DecodeError
:members: __init__
.. autoclass:: pyderasn.NotEnoughData
+.. autoclass:: pyderasn.ExceedingData
.. autoclass:: pyderasn.LenIndefForm
.. autoclass:: pyderasn.TagMismatch
.. autoclass:: pyderasn.InvalidLength
from os import environ
from string import ascii_letters
from string import digits
+from unicodedata import category as unicat
from six import add_metaclass
from six import binary_type
def colored(what, *args, **kwargs):
return what
+__version__ = "5.6"
__all__ = (
"Any",
"DecodeError",
"DecodePathDefBy",
"Enumerated",
+ "ExceedingData",
"GeneralizedTime",
"GeneralString",
"GraphicString",
pass
+class ExceedingData(ASN1Error):
+ def __init__(self, nbytes):
+ super(ExceedingData, self).__init__()
+ self.nbytes = nbytes
+
+ def __str__(self):
+ return "%d trailing bytes" % self.nbytes
+
+ def __repr__(self):
+ return "%s(%s)" % (self.__class__.__name__, self)
+
+
class LenIndefForm(DecodeError):
pass
########################################################################
class AutoAddSlots(type):
- def __new__(mcs, name, bases, _dict):
+ def __new__(cls, name, bases, _dict):
_dict["__slots__"] = _dict.get("__slots__", ())
- return type.__new__(mcs, name, bases, _dict)
+ return type.__new__(cls, name, bases, _dict)
@add_metaclass(AutoAddSlots)
@property
def tlen(self):
+ """See :ref:`decoding`
+ """
return len(self.tag)
@property
def tlvlen(self):
+ """See :ref:`decoding`
+ """
return self.tlen + self.llen + self.vlen
def __str__(self): # pragma: no cover
raise NotImplementedError()
def encode(self):
+ """Encode the structure
+
+ :returns: DER representation
+ """
raw = self._encode()
if self._expl is None:
return raw
determine if tag satisfies the scheme)
:param _ctx_immutable: do we need to copy ``ctx`` before using it
:returns: (Obj, remaining data)
+
+ .. seealso:: :ref:`decoding`
"""
if ctx is None:
ctx = {}
tag_only=tag_only,
)
if tag_only:
- return
+ return None
obj, tail = result
else:
try:
tag_only=tag_only,
)
if tag_only: # pragma: no cover
- return
+ return None
obj, tail = result
eoc_expected, tail = tail[:EOC_LEN], tail[EOC_LEN:]
if eoc_expected.tobytes() != EOC:
tag_only=tag_only,
)
if tag_only: # pragma: no cover
- return
+ return None
obj, tail = result
if obj.tlvlen < l and not ctx.get("allow_expl_oob", False):
raise DecodeError(
)
return obj, (tail if leavemm else tail.tobytes())
+ def decod(self, data, offset=0, decode_path=(), ctx=None):
+ """Decode the data, check that tail is empty
+
+ :raises ExceedingData: if tail is not empty
+
+ This is just a wrapper over :py:meth:`pyderasn.Obj.decode`
+ (decode without tail) that also checks that there is no
+ trailing data left.
+ """
+ obj, tail = self.decode(
+ data,
+ offset=offset,
+ decode_path=decode_path,
+ ctx=ctx,
+ leavemm=True,
+ )
+ if len(tail) > 0:
+ raise ExceedingData(len(tail))
+ return obj
+
@property
def expled(self):
+ """See :ref:`decoding`
+ """
return self._expl is not None
@property
def expl_tag(self):
+ """See :ref:`decoding`
+ """
return self._expl
@property
def expl_tlen(self):
+ """See :ref:`decoding`
+ """
return len(self._expl)
@property
def expl_llen(self):
+ """See :ref:`decoding`
+ """
if self.expl_lenindef:
return 1
return len(len_encode(self.tlvlen))
@property
def expl_offset(self):
+ """See :ref:`decoding`
+ """
return self.offset - self.expl_tlen - self.expl_llen
@property
def expl_vlen(self):
+ """See :ref:`decoding`
+ """
return self.tlvlen
@property
def expl_tlvlen(self):
+ """See :ref:`decoding`
+ """
return self.expl_tlen + self.expl_llen + self.expl_vlen
@property
def fulloffset(self):
+ """See :ref:`decoding`
+ """
return self.expl_offset if self.expled else self.offset
@property
def fulllen(self):
+ """See :ref:`decoding`
+ """
return self.expl_tlvlen if self.expled else self.tlvlen
def pps_lenindef(self, decode_path):
offset=offset,
)
if tag_only:
- return
+ return None
try:
l, _, v = len_decode(lv)
except DecodeError as err:
for name, value in iteritems(self.specs):
if value == self._value:
return name
+ return None
def __call__(
self,
offset=offset,
)
if tag_only:
- return
+ return None
try:
l, llen, v = len_decode(lv)
except DecodeError as err:
if not frozenset(value) <= SET01:
raise ValueError("B's coding contains unacceptable chars")
return self._bits2octets(value)
- elif value.endswith("'H"):
+ if value.endswith("'H"):
value = value[1:-2]
return (
len(value) * 4,
)
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
octets,
))
- def _decode_chunk(self, lv, offset, decode_path, ctx):
+ def _decode_chunk(self, lv, offset, decode_path):
try:
l, llen, v = len_decode(lv)
except DecodeError as err:
)
if t == self.tag:
if tag_only: # pragma: no cover
- return
- return self._decode_chunk(lv, offset, decode_path, ctx)
+ return None
+ return self._decode_chunk(lv, offset, decode_path)
if t == self.tag_constructed:
if not ctx.get("bered", False):
raise DecodeError(
offset=offset,
)
if tag_only: # pragma: no cover
- return
+ return None
lenindef = False
try:
l, llen, v = len_decode(lv)
:param default: set default value. Type same as in ``value``
:param bool optional: is object ``OPTIONAL`` in sequence
"""
- super(OctetString, self).__init__(
- impl,
- expl,
- default,
- optional,
- _decoded,
- )
+ super(OctetString, self).__init__(impl, expl, default, optional, _decoded)
self._value = value
self._bound_min, self._bound_max = getattr(
self,
self._value,
))
- def _decode_chunk(self, lv, offset, decode_path, ctx):
+ def _decode_chunk(self, lv, offset, decode_path):
try:
l, llen, v = len_decode(lv)
except DecodeError as err:
)
if t == self.tag:
if tag_only:
- return
- return self._decode_chunk(lv, offset, decode_path, ctx)
+ return None
+ return self._decode_chunk(lv, offset, decode_path)
if t == self.tag_constructed:
if not ctx.get("bered", False):
raise DecodeError(
offset=offset,
)
if tag_only:
- return
+ return None
lenindef = False
try:
l, llen, v = len_decode(lv)
offset=offset,
)
if tag_only: # pragma: no cover
- return
+ return None
try:
l, _, v = len_decode(lv)
except DecodeError as err:
:param default: set default value. Type same as in ``value``
:param bool optional: is object ``OPTIONAL`` in sequence
"""
- super(ObjectIdentifier, self).__init__(
- impl,
- expl,
- default,
- optional,
- _decoded,
- )
+ super(ObjectIdentifier, self).__init__(impl, expl, default, optional, _decoded)
self._value = value
if value is not None:
self._value = self._value_sanitize(value)
offset=offset,
)
if tag_only: # pragma: no cover
- return
+ return None
try:
l, llen, v = len_decode(lv)
except DecodeError as err:
bounds=None, # dummy argument, workability for Integer.decode
):
super(Enumerated, self).__init__(
- value=value,
- impl=impl,
- expl=expl,
- default=default,
- optional=optional,
- _specs=_specs,
- _decoded=_decoded,
+ value, bounds, impl, expl,default, optional, _specs, _decoded,
)
if len(self.specs) == 0:
raise ValueError("schema must be specified")
)
+def escape_control_unicode(c):
+ if unicat(c).startswith("C"):
+ c = repr(c).lstrip("u").strip("'")
+ return c
+
+
class CommonString(OctetString):
"""Common class for all strings
* - :py:class:`pyderasn.BMPString`
- utf-16-be
"""
- __slots__ = ("encoding",)
+ __slots__ = ()
def _value_sanitize(self, value):
value_raw = None
def pps(self, decode_path=(), no_unicode=False):
value = None
if self.ready:
- value = hexenc(bytes(self)) if no_unicode else self.__unicode__()
+ value = (
+ hexenc(bytes(self)) if no_unicode else
+ "".join(escape_control_unicode(c) for c in self.__unicode__())
+ )
yield _pp(
obj=self,
asn1_type_name=self.asn1_type_name,
_allowable_chars = frozenset(
(ascii_letters + digits + " '()+,-./:=?").encode("ascii")
)
+ _asterisk = frozenset("*".encode("ascii"))
+ _ampersand = frozenset("&".encode("ascii"))
+
+ def __init__(
+ self,
+ value=None,
+ bounds=None,
+ impl=None,
+ expl=None,
+ default=None,
+ optional=False,
+ _decoded=(0, 0, 0),
+ allow_asterisk=False,
+ allow_ampersand=False,
+ ):
+ """
+ :param allow_asterisk: allow asterisk character
+ :param allow_ampersand: allow ampersand character
+ """
+ if allow_asterisk:
+ self._allowable_chars |= self._asterisk
+ if allow_ampersand:
+ self._allowable_chars |= self._ampersand
+ super(PrintableString, self).__init__(
+ value, bounds, impl, expl, default, optional, _decoded,
+ )
def _value_sanitize(self, value):
value = super(PrintableString, self)._value_sanitize(value)
raise DecodeError("non-printable value")
return value
+ def copy(self):
+ obj = super(PrintableString, self).copy()
+ obj._allowable_chars = self._allowable_chars
+ return obj
+
+ def __call__(
+ self,
+ value=None,
+ bounds=None,
+ impl=None,
+ expl=None,
+ default=None,
+ optional=None,
+ ):
+ return self.__class__(
+ value=value,
+ bounds=(
+ (self._bound_min, self._bound_max)
+ if bounds is None else bounds
+ ),
+ impl=self.tag if impl is None else impl,
+ expl=self._expl if expl is None else expl,
+ default=self.default if default is None else default,
+ optional=self.optional if optional is None else optional,
+ allow_asterisk=self._asterisk <= self._allowable_chars,
+ allow_ampersand=self._ampersand <= self._allowable_chars,
+ )
+
class TeletexString(CommonString):
__slots__ = ()
:param bool optional: is object ``OPTIONAL`` in sequence
"""
super(UTCTime, self).__init__(
- impl=impl,
- expl=expl,
- default=default,
- optional=optional,
- _decoded=_decoded,
+ None, None, impl, expl, default, optional, _decoded,
)
self._value = value
if value is not None:
offset=offset,
)
if tag_only: # pragma: no cover
- return
+ return None
value, tail = spec.decode(
tlv,
offset=offset,
_decoded=(offset, 0, tlvlen),
)
obj.lenindef = True
- obj.tag = t
+ obj.tag = t.tobytes()
return obj, v[EOC_LEN:]
except DecodeError as err:
raise err.__class__(
optional=self.optional,
_decoded=(offset, 0, tlvlen),
)
- obj.tag = t
+ obj.tag = t.tobytes()
return obj, tail
def __repr__(self):
if spec.optional:
continue
return False
- else:
- if not value.ready:
- return False
+ if not value.ready:
+ return False
return True
@property
offset=offset,
)
if tag_only: # pragma: no cover
- return
+ return None
lenindef = False
ctx_bered = ctx.get("bered", False)
try:
ctx=ctx,
_ctx_immutable=False,
)
- except TagMismatch:
- if spec.optional:
+ except TagMismatch as err:
+ if (len(err.decode_path) == len(decode_path) + 1) and spec.optional:
continue
raise
offset=offset,
)
if tag_only:
- return
+ return None
lenindef = False
ctx_bered = ctx.get("bered", False)
try:
optional=False,
_decoded=(0, 0, 0),
):
- super(SequenceOf, self).__init__(
- impl,
- expl,
- default,
- optional,
- _decoded,
- )
+ super(SequenceOf, self).__init__(impl, expl, default, optional, _decoded)
if schema is None:
schema = getattr(self, "schema", None)
if schema is None:
offset=offset,
)
if tag_only:
- return
+ return None
lenindef = False
ctx_bered = ctx.get("bered", False)
try:
print(pprinter(
obj,
oid_maps=oid_maps,
- with_colours=True if environ.get("NO_COLOR") is None else False,
+ with_colours=environ.get("NO_COLOR") is None,
with_decode_path=args.print_decode_path,
decode_path_only=(
() if args.decode_path_only is None else