(and its derivatives), ``SET``, ``SET OF``, ``UTCTime``, ``GeneralizedTime``
could be BERed.
-
.. _definedby:
DEFINED BY
Integer
_______
.. autoclass:: pyderasn.Integer
- :members: __init__
+ :members: __init__, named
BitString
_________
.. autoclass:: pyderasn.BitString
- :members: __init__
+ :members: __init__, bit_len, named
OctetString
___________
PrintableString
_______________
.. autoclass:: pyderasn.PrintableString
- :members: __init__
+ :members: __init__, allow_asterisk, allow_ampersand
UTCTime
_______
GeneralizedTime
_______________
.. autoclass:: pyderasn.GeneralizedTime
+ :members: __init__, todatetime
Special types
-------------
Choice
______
.. autoclass:: pyderasn.Choice
- :members: __init__
+ :members: __init__, choice, value
PrimitiveTypes
______________
.. autoclass:: pyderasn.ObjNotReady
.. autoclass:: pyderasn.InvalidValueType
.. autoclass:: pyderasn.BoundsError
+
+.. _cmdline:
+
+Command-line usage
+------------------
+
+You can decode DER/BER files using command line abilities::
+
+ $ python -m pyderasn --schema tests.test_crts:Certificate path/to/file
+
+If there is no schema for your file, then you can try parsing it without,
+but of course IMPLICIT tags will often make it impossible. But result is
+good enough for the certificate above::
+
+ $ python -m pyderasn path/to/file
+ 0 [1,3,1604] . >: SEQUENCE OF
+ 4 [1,3,1453] . . >: SEQUENCE OF
+ 8 [0,0, 5] . . . . >: [0] ANY
+ . . . . . A0:03:02:01:02
+ 13 [1,1, 3] . . . . >: INTEGER 61595
+ 18 [1,1, 13] . . . . >: SEQUENCE OF
+ 20 [1,1, 9] . . . . . . >: OBJECT IDENTIFIER 1.2.840.113549.1.1.5
+ 31 [1,1, 0] . . . . . . >: NULL
+ 33 [1,3, 274] . . . . >: SEQUENCE OF
+ 37 [1,1, 11] . . . . . . >: SET OF
+ 39 [1,1, 9] . . . . . . . . >: SEQUENCE OF
+ 41 [1,1, 3] . . . . . . . . . . >: OBJECT IDENTIFIER 2.5.4.6
+ 46 [1,1, 2] . . . . . . . . . . >: PrintableString PrintableString ES
+ [...]
+ 1409 [1,1, 50] . . . . . . >: SEQUENCE OF
+ 1411 [1,1, 8] . . . . . . . . >: OBJECT IDENTIFIER 1.3.6.1.5.5.7.1.1
+ 1421 [1,1, 38] . . . . . . . . >: OCTET STRING 38 bytes
+ . . . . . . . . . 30:24:30:22:06:08:2B:06:01:05:05:07:30:01:86:16
+ . . . . . . . . . 68:74:74:70:3A:2F:2F:6F:63:73:70:2E:69:70:73:63
+ . . . . . . . . . 61:2E:63:6F:6D:2F
+ 1461 [1,1, 13] . . >: SEQUENCE OF
+ 1463 [1,1, 9] . . . . >: OBJECT IDENTIFIER 1.2.840.113549.1.1.5
+ 1474 [1,1, 0] . . . . >: NULL
+ 1476 [1,2, 129] . . >: 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
+ [...]
+
+Human readable OIDs
+___________________
+
+If you have got dictionaries with ObjectIdentifiers, like example one
+from ``tests/test_crts.py``::
+
+ stroid2name = {
+ "1.2.840.113549.1.1.1": "id-rsaEncryption",
+ "1.2.840.113549.1.1.5": "id-sha1WithRSAEncryption",
+ [...]
+ "2.5.4.10": "id-at-organizationName",
+ "2.5.4.11": "id-at-organizationalUnitName",
+ }
+
+then you can pass it to pretty printer to see human readable OIDs::
+
+ $ python -m pyderasn --oids tests.test_crts:stroid2name path/to/file
+ [...]
+ 37 [1,1, 11] . . . . . . >: SET OF
+ 39 [1,1, 9] . . . . . . . . >: SEQUENCE OF
+ 41 [1,1, 3] . . . . . . . . . . >: OBJECT IDENTIFIER id-at-countryName (2.5.4.6)
+ 46 [1,1, 2] . . . . . . . . . . >: PrintableString PrintableString ES
+ 50 [1,1, 18] . . . . . . >: SET OF
+ 52 [1,1, 16] . . . . . . . . >: SEQUENCE OF
+ 54 [1,1, 3] . . . . . . . . . . >: OBJECT IDENTIFIER id-at-stateOrProvinceName (2.5.4.8)
+ 59 [1,1, 9] . . . . . . . . . . >: PrintableString PrintableString Barcelona
+ 70 [1,1, 18] . . . . . . >: SET OF
+ 72 [1,1, 16] . . . . . . . . >: SEQUENCE OF
+ 74 [1,1, 3] . . . . . . . . . . >: OBJECT IDENTIFIER id-at-localityName (2.5.4.7)
+ 79 [1,1, 9] . . . . . . . . . . >: PrintableString PrintableString Barcelona
+ [...]
+
+Decode paths
+____________
+
+Each decoded element has so-called decode path: sequence of structure
+names it is passing during the decode process. Each element has its own
+unique path inside the whole ASN.1 tree. You can print it out with
+``--print-decode-path`` option::
+
+ $ python -m pyderasn --schema path.to:Certificate --print-decode-path path/to/file
+ 0 [1,3,1604] Certificate SEQUENCE []
+ 4 [1,3,1453] . tbsCertificate: TBSCertificate SEQUENCE [tbsCertificate]
+ 10-2 [1,1, 1] . . version: [0] EXPLICIT Version INTEGER v3 OPTIONAL [tbsCertificate:version]
+ 13 [1,1, 3] . . serialNumber: CertificateSerialNumber INTEGER 61595 [tbsCertificate:serialNumber]
+ 18 [1,1, 13] . . signature: AlgorithmIdentifier SEQUENCE [tbsCertificate:signature]
+ 20 [1,1, 9] . . . algorithm: OBJECT IDENTIFIER 1.2.840.113549.1.1.5 [tbsCertificate:signature:algorithm]
+ 31 [0,0, 2] . . . parameters: [UNIV 5] ANY OPTIONAL [tbsCertificate:signature:parameters]
+ . . . . 05:00
+ 33 [0,0, 278] . . issuer: Name CHOICE rdnSequence [tbsCertificate:issuer]
+ 33 [1,3, 274] . . . rdnSequence: RDNSequence SEQUENCE OF [tbsCertificate:issuer:rdnSequence]
+ 37 [1,1, 11] . . . . 0: RelativeDistinguishedName SET OF [tbsCertificate:issuer:rdnSequence:0]
+ 39 [1,1, 9] . . . . . 0: AttributeTypeAndValue SEQUENCE [tbsCertificate:issuer:rdnSequence:0:0]
+ 41 [1,1, 3] . . . . . . type: AttributeType OBJECT IDENTIFIER 2.5.4.6 [tbsCertificate:issuer:rdnSequence:0:0:type]
+ 46 [0,0, 4] . . . . . . value: [UNIV 19] AttributeValue ANY [tbsCertificate:issuer:rdnSequence:0:0:value]
+ . . . . . . . 13:02:45:53
+ 46 [1,1, 2] . . . . . . . DEFINED BY 2.5.4.6: CountryName PrintableString ES [tbsCertificate:issuer:rdnSequence:0:0:value:DEFINED BY 2.5.4.6]
+ [...]
+
+Now you can print only the specified tree, for example signature algorithm::
+
+ $ python -m pyderasn --schema path.to:Certificate --decode-path-only tbsCertificate:signature path/to/file
+ 18 [1,1, 13] 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
"""
from codecs import getdecoder
from datetime import datetime
from datetime import timedelta
from math import ceil
+from operator import attrgetter
from string import ascii_letters
from string import digits
from sys import version_info
def colored(what, *args, **kwargs):
return what
-__version__ = "6.3"
+__version__ = "7.0"
__all__ = (
"Any",
BasicState = namedtuple("BasicState", (
"version",
"tag",
+ "tag_order",
"expl",
"default",
"optional",
"""
__slots__ = (
"tag",
+ "_tag_order",
"_value",
"_expl",
"default",
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")
+ if self.tag is None:
+ self._tag_order = None
+ else:
+ tag_class, _, tag_num = tag_decode(
+ self.tag if self._expl is None else self._expl
+ )
+ self._tag_order = (tag_class, tag_num)
if default is not None:
optional = True
self.optional = optional
if state.version != __version__:
raise ValueError("data is pickled by different PyDERASN version")
self.tag = state.tag
+ self._tag_order = state.tag_order
self._expl = state.expl
self.default = state.default
self.optional = state.optional
self.lenindef = state.lenindef
self.ber_encoded = state.ber_encoded
+ @property
+ def tag_order(self):
+ """Tag's (class, number) used for DER/CER sorting
+ """
+ return self._tag_order
+
@property
def tlen(self):
"""See :ref:`decoding`
return BooleanState(
__version__,
self.tag,
+ self._tag_order,
self._expl,
self.default,
self.optional,
return IntegerState(
__version__,
self.tag,
+ self._tag_order,
self._expl,
self.default,
self.optional,
@property
def named(self):
+ """Return named representation (if exists) of the value
+ """
for name, value in iteritems(self.specs):
if value == self._value:
return name
return BitStringState(
__version__,
self.tag,
+ self._tag_order,
self._expl,
self.default,
self.optional,
@property
def bit_len(self):
+ """Returns number of bits in the string
+ """
self._assert_ready()
return self._value[0]
@property
def named(self):
+ """Named representation (if exists) of the bits
+
+ :returns: [str(name), ...]
+ """
return [name for name, bit in iteritems(self.specs) if self[bit]]
def __call__(
return OctetStringState(
__version__,
self.tag,
+ self._tag_order,
self._expl,
self.default,
self.optional,
return NullState(
__version__,
self.tag,
+ self._tag_order,
self._expl,
self.default,
self.optional,
return ObjectIdentifierState(
__version__,
self.tag,
+ self._tag_order,
self._expl,
self.default,
self.optional,
self.default = default_obj
if value is None:
self._value = copy(default_obj._value)
+ if self._expl is not None:
+ tag_class, _, tag_num = tag_decode(self._expl)
+ self._tag_order = (tag_class, tag_num)
def _value_sanitize(self, value):
if (value.__class__ == tuple) and len(value) == 2:
return ChoiceState(
__version__,
self.tag,
+ self._tag_order,
self._expl,
self.default,
self.optional,
@property
def choice(self):
+ """Name of the choice
+ """
self._assert_ready()
return self._value[0]
@property
def value(self):
+ """Value of underlying choice
+ """
self._assert_ready()
return self._value[1]
+ @property
+ def tag_order(self):
+ self._assert_ready()
+ return self._value[1].tag_order if self._tag_order is None else self._tag_order
+
def __getitem__(self, key):
if key not in self.specs:
raise ObjUnknown(key)
"""
:param value: set the value. Either any kind of pyderasn's
**ready** object, or bytes. Pay attention that
- **no** validation is performed is raw binary value
- is valid TLV
+ **no** validation is performed if raw binary value
+ is valid TLV, except just tag decoding
:param bytes expl: override default tag with ``EXPLICIT`` one
:param bool optional: is object ``OPTIONAL`` in sequence
"""
super(Any, self).__init__(None, expl, None, optional, _decoded)
- self._value = None if value is None else self._value_sanitize(value)
+ if value is None:
+ self._value = None
+ else:
+ value = self._value_sanitize(value)
+ self._value = value
+ if self._expl is None:
+ if value.__class__ == binary_type:
+ tag_class, _, tag_num = tag_decode(tag_strip(value)[0])
+ else:
+ tag_class, tag_num = value.tag_order
+ else:
+ tag_class, _, tag_num = tag_decode(self._expl)
+ self._tag_order = (tag_class, tag_num)
self.defined = None
def _value_sanitize(self, value):
if value.__class__ == binary_type:
+ if len(value) == 0:
+ raise ValueError("Any value can not be empty")
return value
if isinstance(value, self.__class__):
return value._value
def ready(self):
return self._value is not None
+ @property
+ def tag_order(self):
+ self._assert_ready()
+ return self._tag_order
+
@property
def bered(self):
if self.expl_lenindef or self.lenindef:
return AnyState(
__version__,
self.tag,
+ self._tag_order,
self._expl,
None,
self.optional,
return SequenceState(
__version__,
self.tag,
+ self._tag_order,
self._expl,
self.default,
self.optional,
asn1_type_name = "SET"
def _encode(self):
- raws = [v.encode() for v in self._values_for_encoding()]
- raws.sort()
- v = b"".join(raws)
+ v = b"".join(value.encode() for value in sorted(
+ self._values_for_encoding(),
+ key=attrgetter("tag_order"),
+ ))
return b"".join((self.tag, len_encode(len(v)), v))
- def _specs_items(self):
- return iteritems(self.specs)
-
def _decode(self, tlv, offset, decode_path, ctx, tag_only):
try:
t, tlen, lv = tag_strip(tlv)
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])
+ tag_order_prev = (0, 0)
+ _specs_items = copy(self.specs)
while len(v) > 0:
if lenindef and v[:EOC_LEN].tobytes() == EOC:
break
- for name, spec in self._specs_items():
+ for name, spec in iteritems(_specs_items):
sub_decode_path = decode_path + (name,)
try:
spec.decode(
ctx=ctx,
_ctx_immutable=False,
)
+ value_tag_order = value.tag_order
value_len = value.fulllen
- if value_prev.tobytes() > v[:value_len].tobytes():
+ if tag_order_prev >= value_tag_order:
if ctx_bered or ctx_allow_unordered_set:
ber_encoded = True
else:
offset=sub_offset,
)
values[name] = value
- value_prev = v[:value_len]
+ del _specs_items[name]
+ tag_order_prev = value_tag_order
sub_offset += value_len
vlen += value_len
v = v_tail
+
obj = self.__class__(
schema=self.specs,
impl=self.tag,
return SequenceOfState(
__version__,
self.tag,
+ self._tag_order,
self._expl,
self.default,
self.optional,
asn1_type_name = "SET OF"
def _encode(self):
- raws = [v.encode() for v in self._values_for_encoding()]
- raws.sort()
- v = b"".join(raws)
+ v = b"".join(sorted(v.encode() for v in self._values_for_encoding()))
return b"".join((self.tag, len_encode(len(v)), v))
def _decode(self, tlv, offset, decode_path, ctx, tag_only):