#!/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
-# published by the Free Software Foundation, either version 3 of the
-# License, or (at your option) any later version.
+# published by the Free Software Foundation, version 3 of the License.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# 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
>>> print(pprint(obj))
0 [1,1, 2] INTEGER -12345
+.. _pprint_example:
+
+Example certificate::
+
+ >>> print(pprint(crt))
+ 0 [1,3,1604] Certificate SEQUENCE
+ 4 [1,3,1453] . tbsCertificate: TBSCertificate SEQUENCE
+ 10-2 [1,1, 1] . . version: [0] EXPLICIT Version INTEGER v3 OPTIONAL
+ 13 [1,1, 3] . . serialNumber: CertificateSerialNumber INTEGER 61595
+ 18 [1,1, 13] . . signature: AlgorithmIdentifier SEQUENCE
+ 20 [1,1, 9] . . . algorithm: OBJECT IDENTIFIER 1.2.840.113549.1.1.5
+ 31 [0,0, 2] . . . parameters: [UNIV 5] ANY OPTIONAL
+ . . . . 05:00
+ 33 [0,0, 278] . . issuer: Name CHOICE rdnSequence
+ 33 [1,3, 274] . . . rdnSequence: RDNSequence SEQUENCE OF
+ 37 [1,1, 11] . . . . 0: RelativeDistinguishedName SET OF
+ 39 [1,1, 9] . . . . . 0: AttributeTypeAndValue SEQUENCE
+ 41 [1,1, 3] . . . . . . type: AttributeType OBJECT IDENTIFIER 2.5.4.6
+ 46 [0,0, 4] . . . . . . value: [UNIV 19] AttributeValue ANY
+ . . . . . . . 13:02:45:53
+ [...]
+ 1461 [1,1, 13] . signatureAlgorithm: AlgorithmIdentifier SEQUENCE
+ 1463 [1,1, 9] . . algorithm: OBJECT IDENTIFIER 1.2.840.113549.1.1.5
+ 1474 [0,0, 2] . . parameters: [UNIV 5] ANY OPTIONAL
+ . . . 05:00
+ 1476 [1,2, 129] . signatureValue: BIT STRING 1024 bits
+ . . 68:EE:79:97:97:DD:3B:EF:16:6A:06:F2:14:9A:6E:CD
+ . . 9E:12:F7:AA:83:10:BD:D1:7C:98:FA:C7:AE:D4:0E:2C
+ [...]
+
+ Trailing data: 0a
+
+Let's parse that output, human::
+
+ 10-2 [1,1, 1] . . version: [0] EXPLICIT Version INTEGER v3 OPTIONAL
+ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^
+ 0 1 2 3 4 5 6 7 8 9 10 11
+
+::
+
+ 20 [1,1, 9] . . . algorithm: OBJECT IDENTIFIER 1.2.840.113549.1.1.5
+ ^ ^ ^ ^ ^ ^ ^ ^
+ 0 2 3 4 5 6 9 10
+
+::
+
+ 33 [0,0, 278] . . issuer: Name CHOICE rdnSequence
+ ^ ^ ^ ^ ^ ^ ^ ^ ^
+ 0 2 3 4 5 6 8 9 10
+
+::
+
+ 52-2∞ B [1,1,1054]∞ . . . . eContent: [0] EXPLICIT BER OCTET STRING 1046 bytes
+ ^ ^ ^ ^ ^
+ 12 13 14 9 10
+
+:0:
+ Offset of the object, where its DER/BER encoding begins.
+ Pay attention that it does **not** include explicit tag.
+:1:
+ If explicit tag exists, then this is its length (tag + encoded length).
+:2:
+ Length of object's tag. For example CHOICE does not have its own tag,
+ so it is zero.
+:3:
+ Length of encoded length.
+:4:
+ Length of encoded value.
+:5:
+ Visual indentation to show the depth of object in the hierarchy.
+:6:
+ Object's name inside SEQUENCE/CHOICE.
+:7:
+ If either IMPLICIT or EXPLICIT tag is set, then it will be shown
+ here. "IMPLICIT" is omitted.
+:8:
+ Object's class name, if set. Omitted if it is just an ordinary simple
+ value (like with ``algorithm`` in example above).
+:9:
+ Object's ASN.1 type.
+:10:
+ Object's value, if set. Can consist of multiple words (like OCTET/BIT
+ STRINGs above). We see ``v3`` value in Version, because it is named.
+ ``rdnSequence`` is the choice of CHOICE type.
+:11:
+ Possible other flags like OPTIONAL and DEFAULT, if value equals to the
+ default one, specified in the schema.
+:12:
+ Shows does object contains any kind of BER encoded data (possibly
+ Sequence holding BER-encoded underlying value).
+:13:
+ Only applicable to BER encoded data. Indefinite length encoding mark.
+:14:
+ Only applicable to BER encoded data. If object has BER-specific
+ encoding, then ``BER`` will be shown. It does not depend on indefinite
+ length encoding. ``EOC``, ``BOOLEAN``, ``BIT STRING``, ``OCTET STRING``
+ (and its derivatives), ``SET``, ``SET OF`` could be BERed.
+
+
.. _definedby:
DEFINED BY
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``
structures it may hold. Also, automatically decode ``controlSequence``
of ``PKIResponse``::
- content_info, tail = ContentInfo().decode(data, defines_by_path=(
+ content_info, tail = ContentInfo().decode(data, ctx={"defines_by_path": (
(
("contentType",),
((("content",), {id_signedData: SignedData()}),),
id_cmc_transactionId: TransactionId(),
})),
),
- ))
+ )})
Pay attention for :py:class:`pyderasn.DecodePathDefBy` and ``any``.
First function is useful for path construction when some automatic
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
---------------
_____________
.. autoclass:: pyderasn.NumericString
+PrintableString
+_______________
+.. autoclass:: pyderasn.PrintableString
+
UTCTime
_______
.. autoclass:: pyderasn.UTCTime
.. autofunction:: pyderasn.tag_decode
.. autofunction:: pyderasn.tag_ctxp
.. autofunction:: pyderasn.tag_ctxc
-.. autoclass:: pyderasn.Obj
.. autoclass:: pyderasn.DecodeError
:members: __init__
.. autoclass:: pyderasn.NotEnoughData
try:
from termcolor import colored
except ImportError: # pragma: no cover
- def colored(what, *args):
+ def colored(what, *args, **kwargs):
return what
+__version__ = "5.5"
__all__ = (
"Any",
########################################################################
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(
@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):
def pp_console_row(
pp,
- oids=None,
+ oid_maps=(),
with_offsets=False,
with_blob=True,
with_colours=False,
if isinstance(ent, DecodePathDefBy):
cols.append(_colourize("DEFINED BY", "red", with_colours, ("reverse",)))
value = str(ent.defined_by)
+ oid_name = None
if (
- oids is not None and
+ len(oid_maps) > 0 and
ent.defined_by.asn1_type_name ==
- ObjectIdentifier.asn1_type_name and
- value in oids
+ ObjectIdentifier.asn1_type_name
):
- cols.append(_colourize("%s:" % oids[value], "green", with_colours))
- else:
+ for oid_map in oid_maps:
+ oid_name = oid_map.get(value)
+ if oid_name is not None:
+ cols.append(_colourize("%s:" % oid_name, "green", with_colours))
+ break
+ if oid_name is None:
cols.append(_colourize("%s:" % value, "white", with_colours, ("reverse",)))
else:
cols.append(_colourize("%s:" % ent, "yellow", with_colours, ("reverse",)))
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
+ len(oid_maps) > 0 and
+ pp.asn1_type_name == ObjectIdentifier.asn1_type_name
):
- cols.append(_colourize("(%s)" % oids[value], "green", with_colours))
+ for oid_map in oid_maps:
+ oid_name = oid_map.get(value)
+ if oid_name is not None:
+ cols.append(_colourize("(%s)" % oid_name, "green", with_colours))
+ break
if pp.asn1_type_name == Integer.asn1_type_name:
hex_repr = hex(int(pp.obj._value))[2:].upper()
if len(hex_repr) % 2 != 0:
def pprint(
obj,
- oids=None,
+ oid_maps=(),
big_blobs=False,
with_colours=False,
with_decode_path=False,
"""Pretty print object
:param Obj obj: object you want to pretty print
- :param oids: ``OID <-> humand readable string`` dictionary. When OID
- from it is met, then its humand readable form is printed
+ :param oid_maps: list of ``OID <-> humand readable string`` dictionary.
+ When OID from it is met, then its humand readable form
+ is printed
:param big_blobs: if large binary objects are met (like OctetString
values), do we need to print them too, on separate
lines
if big_blobs:
yield pp_console_row(
pp,
- oids=oids,
+ oid_maps=oid_maps,
with_offsets=True,
with_blob=False,
with_colours=with_colours,
else:
yield pp_console_row(
pp,
- oids=oids,
+ oid_maps=oid_maps,
with_offsets=True,
with_blob=True,
with_colours=with_colours,
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
)
if t == self.tag:
if tag_only: # pragma: no cover
- return
+ return None
return self._decode_chunk(lv, offset, decode_path, ctx)
if t == self.tag_constructed:
if not ctx.get("bered", False):
offset=offset,
)
if tag_only: # pragma: no cover
- return
+ return None
lenindef = False
try:
l, llen, v = len_decode(lv)
)
if t == self.tag:
if tag_only:
- return
+ return None
return self._decode_chunk(lv, offset, decode_path, ctx)
if t == self.tag_constructed:
if not ctx.get("bered", False):
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:
offset=offset,
)
if tag_only: # pragma: no cover
- return
+ return None
try:
l, llen, v = len_decode(lv)
except DecodeError as err:
be stored.
>>> NumericString().allowable_chars
- set(['3', '4', '7', '5', '1', '0', '8', '9', ' ', '6', '2'])
+ frozenset(['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', ' '])
"""
__slots__ = ()
tag_default = tag_encode(18)
Its value is properly sanitized: see X.680 41.4 table 10.
>>> PrintableString().allowable_chars
- >>> set([' ', "'", ..., 'z'])
+ frozenset([' ', "'", ..., 'z'])
"""
__slots__ = ()
tag_default = tag_encode(19)
.. warning::
- No BER encodings are supported. Only DER.
+ BER encoding is unsupported.
"""
__slots__ = ()
tag_default = tag_encode(23)
try:
value_decoded = value.decode("ascii")
except (UnicodeEncodeError, UnicodeDecodeError) as err:
- raise DecodeError("invalid UTCTime encoding")
+ raise DecodeError("invalid UTCTime encoding: %r" % err)
try:
self._strptime(value_decoded)
except (TypeError, ValueError) as err:
.. warning::
- No BER encodings are supported. Only DER.
+ BER encoding is unsupported.
.. warning::
try:
value_decoded = value.decode("ascii")
except (UnicodeEncodeError, UnicodeDecodeError) as err:
- raise DecodeError("invalid GeneralizedTime encoding")
+ raise DecodeError("invalid GeneralizedTime encoding: %r" % err)
try:
self._strptime(value_decoded)
except (TypeError, ValueError) as err:
offset=offset,
)
if tag_only: # pragma: no cover
- return
+ return None
value, tail = spec.decode(
tlv,
offset=offset,
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:
offset=offset,
)
if tag_only:
- return
+ return None
lenindef = False
ctx_bered = ctx.get("bered", False)
try:
def pprint_any(
obj,
- oids=None,
+ oid_maps=(),
with_colours=False,
with_decode_path=False,
decode_path_only=(),
pp = _pp(**pp_kwargs)
yield pp_console_row(
pp,
- oids=oids,
+ oid_maps=oid_maps,
with_offsets=True,
with_blob=False,
with_colours=with_colours,
)
parser.add_argument(
"--oids",
- help="Python path to dictionary with OIDs",
+ help="Python paths to dictionary with OIDs, comma separated",
)
parser.add_argument(
"--schema",
args.DERFile.seek(args.skip)
der = memoryview(args.DERFile.read())
args.DERFile.close()
- oids = obj_by_path(args.oids) if args.oids else {}
+ oid_maps = (
+ [obj_by_path(_path) for _path in (args.oids or "").split(",")]
+ if args.oids else ()
+ )
if args.schema:
schema = obj_by_path(args.schema)
from functools import partial
obj, tail = schema().decode(der, ctx=ctx)
print(pprinter(
obj,
- oids=oids,
+ oid_maps=oid_maps,
with_colours=True if environ.get("NO_COLOR") is None else False,
with_decode_path=args.print_decode_path,
decode_path_only=(