#
# 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
>>> 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
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
_____________
.. autoclass:: pyderasn.NumericString
+PrintableString
+_______________
+.. autoclass:: pyderasn.PrintableString
+
UTCTime
_______
.. autoclass:: pyderasn.UTCTime
try:
from termcolor import colored
except ImportError: # pragma: no cover
- def colored(what, *args):
+ def colored(what, *args, **kwargs):
return what
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,
self._value = default
def _value_sanitize(self, value):
- if issubclass(value.__class__, Boolean):
- return value._value
if isinstance(value, bool):
return value
+ if issubclass(value.__class__, Boolean):
+ return value._value
raise InvalidValueType((self.__class__, bool))
@property
self._value = default
def _value_sanitize(self, value):
- if issubclass(value.__class__, Integer):
- value = value._value
- elif isinstance(value, integer_types):
+ if isinstance(value, integer_types):
pass
+ elif issubclass(value.__class__, Integer):
+ value = value._value
elif isinstance(value, str):
value = self.specs.get(value)
if value is None:
yield pp
+SET01 = frozenset(("0", "1"))
+
+
class BitString(Obj):
"""``BIT STRING`` bit string type
return bit_len, bytes(octets)
def _value_sanitize(self, value):
- if issubclass(value.__class__, BitString):
- return value._value
if isinstance(value, (string_types, binary_type)):
if (
isinstance(value, string_types) and
):
if value.endswith("'B"):
value = value[1:-2]
- if not set(value) <= set(("0", "1")):
+ if not frozenset(value) <= SET01:
raise ValueError("B's coding contains unacceptable chars")
return self._bits2octets(value)
elif value.endswith("'H"):
bits.append(bit)
if len(bits) == 0:
return self._bits2octets("")
- bits = set(bits)
+ bits = frozenset(bits)
return self._bits2octets("".join(
("1" if bit in bits else "0")
for bit in six_xrange(max(bits) + 1)
))
+ if issubclass(value.__class__, BitString):
+ return value._value
raise InvalidValueType((self.__class__, binary_type, string_types))
@property
)
def _value_sanitize(self, value):
- if issubclass(value.__class__, OctetString):
- value = value._value
- elif isinstance(value, binary_type):
+ if isinstance(value, binary_type):
pass
+ elif issubclass(value.__class__, OctetString):
+ value = value._value
else:
raise InvalidValueType((self.__class__, bytes))
if not self._bound_min <= len(value) <= self._bound_max:
def allowable_chars(self):
if PY2:
return self._allowable_chars
- return set(six_unichr(c) for c in self._allowable_chars)
+ return frozenset(six_unichr(c) for c in self._allowable_chars)
class NumericString(AllowableCharsMixin, CommonString):
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)
encoding = "ascii"
asn1_type_name = "NumericString"
- _allowable_chars = set(digits.encode("ascii") + b" ")
+ _allowable_chars = frozenset(digits.encode("ascii") + b" ")
def _value_sanitize(self, value):
value = super(NumericString, self)._value_sanitize(value)
- if not set(value) <= self._allowable_chars:
+ if not frozenset(value) <= self._allowable_chars:
raise DecodeError("non-numeric value")
return value
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)
encoding = "ascii"
asn1_type_name = "PrintableString"
- _allowable_chars = set(
+ _allowable_chars = frozenset(
(ascii_letters + digits + " '()+,-./:=?").encode("ascii")
)
def _value_sanitize(self, value):
value = super(PrintableString, self)._value_sanitize(value)
- if not set(value) <= self._allowable_chars:
+ if not frozenset(value) <= self._allowable_chars:
raise DecodeError("non-printable value")
return value
datetime.datetime(2017, 9, 30, 22, 7, 50)
>>> UTCTime(datetime(2057, 9, 30, 22, 7, 50)).todatetime()
datetime.datetime(1957, 9, 30, 22, 7, 50)
+
+ .. warning::
+
+ BER encoding is unsupported.
"""
__slots__ = ()
tag_default = tag_encode(23)
encoding = "ascii"
asn1_type_name = "UTCTime"
- fmt = "%y%m%d%H%M%SZ"
-
def __init__(
self,
value=None,
if self._value is None:
self._value = default
+ def _strptime(self, value):
+ # datetime.strptime's format: %y%m%d%H%M%SZ
+ if len(value) != LEN_YYMMDDHHMMSSZ:
+ raise ValueError("invalid UTCTime length")
+ if value[-1] != "Z":
+ raise ValueError("non UTC timezone")
+ return datetime(
+ 2000 + int(value[:2]), # %y
+ int(value[2:4]), # %m
+ int(value[4:6]), # %d
+ int(value[6:8]), # %H
+ int(value[8:10]), # %M
+ int(value[10:12]), # %S
+ )
+
def _value_sanitize(self, value):
- if isinstance(value, self.__class__):
- return value._value
- if isinstance(value, datetime):
- return value.strftime(self.fmt).encode("ascii")
if isinstance(value, binary_type):
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 (TypeError, ValueError):
- raise DecodeError("invalid UTCTime format")
- return value
- else:
- raise DecodeError("invalid UTCTime length")
+ raise DecodeError("invalid UTCTime encoding: %r" % err)
+ try:
+ self._strptime(value_decoded)
+ except (TypeError, ValueError) as err:
+ raise DecodeError("invalid UTCTime format: %r" % err)
+ return value
+ if isinstance(value, self.__class__):
+ return value._value
+ if isinstance(value, datetime):
+ return value.strftime("%y%m%d%H%M%SZ").encode("ascii")
raise InvalidValueType((self.__class__, datetime))
def __eq__(self, their):
having < 50 years are treated as 20xx, 19xx otherwise, according
to X.509 recomendation.
"""
- value = datetime.strptime(self._value.decode("ascii"), self.fmt)
+ value = self._strptime(self._value.decode("ascii"))
year = value.year % 100
return datetime(
year=(2000 + year) if year < 50 else (1900 + year),
'20170930220750.000123Z'
>>> t = GeneralizedTime(datetime(2057, 9, 30, 22, 7, 50))
GeneralizedTime GeneralizedTime 2057-09-30T22:07:50
+
+ .. warning::
+
+ BER encoding is unsupported.
+
+ .. warning::
+
+ Only microsecond fractions are supported.
+ :py:exc:`pyderasn.DecodeError` will be raised during decoding of
+ higher precision values.
"""
__slots__ = ()
tag_default = tag_encode(24)
asn1_type_name = "GeneralizedTime"
- fmt = "%Y%m%d%H%M%SZ"
- fmt_ms = "%Y%m%d%H%M%S.%fZ"
+ def _strptime(self, value):
+ l = len(value)
+ if l == LEN_YYYYMMDDHHMMSSZ:
+ # datetime.strptime's format: %y%m%d%H%M%SZ
+ if value[-1] != "Z":
+ raise ValueError("non UTC timezone")
+ return datetime(
+ int(value[:4]), # %Y
+ int(value[4:6]), # %m
+ int(value[6:8]), # %d
+ int(value[8:10]), # %H
+ int(value[10:12]), # %M
+ int(value[12:14]), # %S
+ )
+ if l >= LEN_YYYYMMDDHHMMSSDMZ:
+ # datetime.strptime's format: %Y%m%d%H%M%S.%fZ
+ if value[-1] != "Z":
+ raise ValueError("non UTC timezone")
+ if value[14] != ".":
+ raise ValueError("no fractions separator")
+ us = value[15:-1]
+ if us[-1] == "0":
+ raise ValueError("trailing zero")
+ us_len = len(us)
+ if us_len > 6:
+ raise ValueError("only microsecond fractions are supported")
+ us = int(us + ("0" * (6 - us_len)))
+ decoded = datetime(
+ int(value[:4]), # %Y
+ int(value[4:6]), # %m
+ int(value[6:8]), # %d
+ int(value[8:10]), # %H
+ int(value[10:12]), # %M
+ int(value[12:14]), # %S
+ us, # %f
+ )
+ return decoded
+ raise ValueError("invalid GeneralizedTime length")
def _value_sanitize(self, value):
- if isinstance(value, self.__class__):
- return value._value
- if isinstance(value, datetime):
- return value.strftime(
- self.fmt_ms if value.microsecond > 0 else self.fmt
- ).encode("ascii")
if isinstance(value, binary_type):
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 (TypeError, ValueError):
- raise DecodeError(
- "invalid GeneralizedTime (without ms) format",
- )
- return value
- elif len(value_decoded) >= LEN_YYYYMMDDHHMMSSDMZ:
- try:
- datetime.strptime(value_decoded, self.fmt_ms)
- except (TypeError, ValueError):
- raise DecodeError(
- "invalid GeneralizedTime (with ms) format",
- )
- return value
- else:
+ raise DecodeError("invalid GeneralizedTime encoding: %r" % err)
+ try:
+ self._strptime(value_decoded)
+ except (TypeError, ValueError) as err:
raise DecodeError(
- "invalid GeneralizedTime length",
+ "invalid GeneralizedTime format: %r" % err,
klass=self.__class__,
)
+ return value
+ if isinstance(value, self.__class__):
+ return value._value
+ if isinstance(value, datetime):
+ encoded = value.strftime("%Y%m%d%H%M%S")
+ if value.microsecond > 0:
+ encoded = encoded + (".%06d" % value.microsecond).rstrip("0")
+ return (encoded + "Z").encode("ascii")
raise InvalidValueType((self.__class__, datetime))
def todatetime(self):
- value = self._value.decode("ascii")
- if len(value) == LEN_YYYYMMDDHHMMSSZ:
- return datetime.strptime(value, self.fmt)
- return datetime.strptime(value, self.fmt_ms)
+ return self._strptime(self._value.decode("ascii"))
class GraphicString(CommonString):
self._value = default_obj.copy()._value
def _value_sanitize(self, value):
- if isinstance(value, self.__class__):
- return value._value
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 isinstance(value, self.__class__):
+ return value._value
raise InvalidValueType((self.__class__, tuple))
@property
self.defined = None
def _value_sanitize(self, value):
+ if isinstance(value, binary_type):
+ return value
if isinstance(value, self.__class__):
return value._value
if isinstance(value, Obj):
return value.encode()
- if isinstance(value, binary_type):
- return value
raise InvalidValueType((self.__class__, Obj, binary_type))
@property
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=(