#!/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
>>> i = Integer(123)
>>> raw = i.encode()
- >>> Integer().decode(raw) == i
+ >>> Integer().decod(raw) == i
True
There are primitive types, holding single values
Most types in ASN.1 has specific tag for them. ``Obj.tag_default`` is
the default tag used during coding process. You can override it with
-either ``IMPLICIT`` (using ``impl`` keyword argument), or
-``EXPLICIT`` one (using ``expl`` keyword argument). Both arguments take
-raw binary string, containing that tag. You can **not** set implicit and
-explicit tags simultaneously.
+either ``IMPLICIT`` (using either ``impl`` keyword argument or ``impl``
+class attribute), or ``EXPLICIT`` one (using either ``expl`` keyword
+argument or ``expl`` class attribute). Both arguments take raw binary
+string, containing that tag. You can **not** set implicit and explicit
+tags simultaneously.
There are :py:func:`pyderasn.tag_ctxp` and :py:func:`pyderasn.tag_ctxc`
functions, allowing you to easily create ``CONTEXT``
ready to be encoded. If that kind of action is performed on unready
object, then :py:exc:`pyderasn.ObjNotReady` exception will be raised.
-All objects have ``copy()`` method, that returns their copy, that can be
+All objects are friendly to ``copy.copy()`` and copied objects can be
safely mutated.
+Also all objects can be safely ``pickle``-d, but pay attention that
+pickling among different PyDERASN versions is prohibited.
+
.. _decoding:
Decoding
--------
-Decoding is performed using ``decode()`` method. ``offset`` optional
-argument could be used to set initial object's offset in the binary
-data, for convenience. It returns decoded object and remaining
-unmarshalled data (tail). Internally all work is done on
+Decoding is performed using :py:meth:`pyderasn.Obj.decode` method.
+``offset`` optional argument could be used to set initial object's
+offset in the binary data, for convenience. It returns decoded object
+and remaining unmarshalled data (tail). Internally all work is done on
``memoryview(data)``, and you can leave returning tail as a memoryview,
by specifying ``leavemm=True`` argument.
+Also note convenient :py:meth:`pyderasn.Obj.decod` method, that
+immediately checks and raises if there is non-empty tail.
+
When object is decoded, ``decoded`` property is true and you can safely
use following properties:
Context
_______
-You can specify so called context keyword argument during ``decode()``
-invocation. It is dictionary containing various options governing
-decoding process.
+You can specify so called context keyword argument during
+:py:meth:`pyderasn.Obj.decode` invocation. It is dictionary containing
+various options governing decoding process.
Currently available context options:
>>> 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 = ContentInfo().decod(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
+ :members: __init__
+
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
+.. 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
try:
from termcolor import colored
except ImportError: # pragma: no cover
- def colored(what, *args):
+ def colored(what, *args, **kwargs):
return what
+__version__ = "6.1"
__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)
"""
return (self.llen + self.vlen) > 0
- def copy(self): # pragma: no cover
- """Make a copy of object, safe to be mutated
+ def __getstate__(self): # pragma: no cover
+ """Used for making safe to be mutable pickleable copies
"""
raise NotImplementedError()
+ def __setstate__(self, state):
+ if state.version != __version__:
+ raise ValueError("data is pickled by different PyDERASN version")
+ self.tag = self.tag_default
+ self._value = None
+ self._expl = None
+ self.default = None
+ self.optional = False
+ self.offset = 0
+ self.llen = 0
+ self.vlen = 0
+ self.expl_lenindef = False
+ self.lenindef = False
+ self.ber_encoded = False
+
@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
return b"".join((self._expl, len_encode(len(raw)), raw))
+ def hexencode(self):
+ """Do hexadecimal encoded :py:meth:`pyderasn.Obj.encode`
+ """
+ return hexenc(self.encode())
+
def decode(
self,
data,
: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 _ctx_immutable: do we need to copy ``ctx`` before using it
+ :param _ctx_immutable: do we need to ``copy.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
+
+ def hexdecode(self, data, *args, **kwargs):
+ """Do :py:meth:`pyderasn.Obj.decode` with hexadecimal decoded data
+ """
+ return self.decode(hexdec(data), *args, **kwargs)
+
+ def hexdecod(self, data, *args, **kwargs):
+ """Do :py:meth:`pyderasn.Obj.decod` with hexadecimal decoded data
+ """
+ return self.decod(hexdec(data), *args, **kwargs)
+
@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):
if self.lenindef and not (
- getattr(self, "defined", None) is not None and
- self.defined[1].lenindef
+ getattr(self, "defined", None) is not None and
+ self.defined[1].lenindef
):
yield _pp(
asn1_type_name="EOC",
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 ``str(OID) <-> human readable string`` dictionary.
+ Its human readable form is printed when OID is met
:param big_blobs: if large binary objects are met (like OctetString
values), do we need to print them too, on separate
lines
for pp in pps:
if hasattr(pp, "_fields"):
if (
- decode_path_only != () and
- tuple(
- str(p) for p in pp.decode_path[:len(decode_path_only)]
- ) != decode_path_only
+ decode_path_only != () and
+ tuple(
+ str(p) for p in pp.decode_path[:len(decode_path_only)]
+ ) != decode_path_only
):
continue
if big_blobs:
yield pp_console_row(
pp,
- oids=oids,
+ oid_maps=oid_maps,
with_offsets=True,
with_blob=False,
with_colours=with_colours,
decode_path_len_decrease=len(decode_path_only),
)
for row in pp_console_blob(
- pp,
- decode_path_len_decrease=len(decode_path_only),
+ pp,
+ decode_path_len_decrease=len(decode_path_only),
):
yield row
else:
yield pp_console_row(
pp,
- oids=oids,
+ oid_maps=oid_maps,
with_offsets=True,
with_blob=True,
with_colours=with_colours,
# ASN.1 primitive types
########################################################################
+BooleanState = namedtuple("BooleanState", (
+ "version",
+ "value",
+ "tag",
+ "expl",
+ "default",
+ "optional",
+ "offset",
+ "llen",
+ "vlen",
+ "expl_lenindef",
+ "lenindef",
+ "ber_encoded",
+))
+
+
class Boolean(Obj):
"""``BOOLEAN`` boolean type
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
def ready(self):
return self._value is not None
- def copy(self):
- obj = self.__class__()
- obj._value = self._value
- obj.tag = self.tag
- obj._expl = self._expl
- obj.default = self.default
- obj.optional = self.optional
- obj.offset = self.offset
- obj.llen = self.llen
- obj.vlen = self.vlen
- obj.expl_lenindef = self.expl_lenindef
- obj.lenindef = self.lenindef
- obj.ber_encoded = self.ber_encoded
- return obj
+ def __getstate__(self):
+ return BooleanState(
+ __version__,
+ self._value,
+ self.tag,
+ self._expl,
+ self.default,
+ self.optional,
+ self.offset,
+ self.llen,
+ self.vlen,
+ self.expl_lenindef,
+ self.lenindef,
+ self.ber_encoded,
+ )
+
+ def __setstate__(self, state):
+ super(Boolean, self).__setstate__(state)
+ self._value = state.value
+ self.tag = state.tag
+ self._expl = state.expl
+ self.default = state.default
+ self.optional = state.optional
+ self.offset = state.offset
+ self.llen = state.llen
+ self.vlen = state.vlen
+ self.expl_lenindef = state.expl_lenindef
+ self.lenindef = state.lenindef
+ self.ber_encoded = state.ber_encoded
def __nonzero__(self):
self._assert_ready()
offset=offset,
)
if tag_only:
- return
+ return None
try:
l, _, v = len_decode(lv)
except DecodeError as err:
yield pp
+IntegerState = namedtuple("IntegerState", (
+ "version",
+ "specs",
+ "value",
+ "bound_min",
+ "bound_max",
+ "tag",
+ "expl",
+ "default",
+ "optional",
+ "offset",
+ "llen",
+ "vlen",
+ "expl_lenindef",
+ "lenindef",
+ "ber_encoded",
+))
+
+
class Integer(Obj):
"""``INTEGER`` integer type
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:
def ready(self):
return self._value is not None
- def copy(self):
- obj = self.__class__(_specs=self.specs)
- obj._value = self._value
- obj._bound_min = self._bound_min
- obj._bound_max = self._bound_max
- obj.tag = self.tag
- obj._expl = self._expl
- obj.default = self.default
- obj.optional = self.optional
- obj.offset = self.offset
- obj.llen = self.llen
- obj.vlen = self.vlen
- obj.expl_lenindef = self.expl_lenindef
- obj.lenindef = self.lenindef
- obj.ber_encoded = self.ber_encoded
- return obj
+ def __getstate__(self):
+ return IntegerState(
+ __version__,
+ self.specs,
+ self._value,
+ self._bound_min,
+ self._bound_max,
+ self.tag,
+ self._expl,
+ self.default,
+ self.optional,
+ self.offset,
+ self.llen,
+ self.vlen,
+ self.expl_lenindef,
+ self.lenindef,
+ self.ber_encoded,
+ )
+
+ def __setstate__(self, state):
+ super(Integer, self).__setstate__(state)
+ self.specs = state.specs
+ self._value = state.value
+ self._bound_min = state.bound_min
+ self._bound_max = state.bound_max
+ self.tag = state.tag
+ self._expl = state.expl
+ self.default = state.default
+ self.optional = state.optional
+ self.offset = state.offset
+ self.llen = state.llen
+ self.vlen = state.vlen
+ self.expl_lenindef = state.expl_lenindef
+ self.lenindef = state.lenindef
+ self.ber_encoded = state.ber_encoded
def __int__(self):
self._assert_ready()
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:
yield pp
+SET01 = frozenset(("0", "1"))
+BitStringState = namedtuple("BitStringState", (
+ "version",
+ "specs",
+ "value",
+ "tag",
+ "expl",
+ "default",
+ "optional",
+ "offset",
+ "llen",
+ "vlen",
+ "expl_lenindef",
+ "lenindef",
+ "ber_encoded",
+ "tag_constructed",
+ "defined",
+))
+
+
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"):
+ 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
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 ready(self):
return self._value is not None
- def copy(self):
- obj = self.__class__(_specs=self.specs)
- value = self._value
- if value is not None:
- value = (value[0], value[1])
- obj._value = value
- obj.tag = self.tag
- obj._expl = self._expl
- obj.default = self.default
- obj.optional = self.optional
- obj.offset = self.offset
- obj.llen = self.llen
- obj.vlen = self.vlen
- obj.expl_lenindef = self.expl_lenindef
- obj.lenindef = self.lenindef
- obj.ber_encoded = self.ber_encoded
- return obj
+ def __getstate__(self):
+ return BitStringState(
+ __version__,
+ self.specs,
+ self._value,
+ self.tag,
+ self._expl,
+ self.default,
+ self.optional,
+ self.offset,
+ self.llen,
+ self.vlen,
+ self.expl_lenindef,
+ self.lenindef,
+ self.ber_encoded,
+ self.tag_constructed,
+ self.defined,
+ )
+
+ def __setstate__(self, state):
+ super(BitString, self).__setstate__(state)
+ self.specs = state.specs
+ self._value = state.value
+ self.tag = state.tag
+ self._expl = state.expl
+ self.default = state.default
+ self.optional = state.optional
+ self.offset = state.offset
+ self.llen = state.llen
+ self.vlen = state.vlen
+ self.expl_lenindef = state.expl_lenindef
+ self.lenindef = state.lenindef
+ self.ber_encoded = state.ber_encoded
+ self.tag_constructed = state.tag_constructed
+ self.defined = state.defined
def __iter__(self):
self._assert_ready()
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)
yield pp
+OctetStringState = namedtuple("OctetStringState", (
+ "version",
+ "value",
+ "bound_min",
+ "bound_max",
+ "tag",
+ "expl",
+ "default",
+ "optional",
+ "offset",
+ "llen",
+ "vlen",
+ "expl_lenindef",
+ "lenindef",
+ "ber_encoded",
+ "tag_constructed",
+ "defined",
+))
+
+
class OctetString(Obj):
"""``OCTET STRING`` binary string type
: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,
)
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 ready(self):
return self._value is not None
- def copy(self):
- obj = self.__class__()
- obj._value = self._value
- obj._bound_min = self._bound_min
- obj._bound_max = self._bound_max
- obj.tag = self.tag
- obj._expl = self._expl
- obj.default = self.default
- obj.optional = self.optional
- obj.offset = self.offset
- obj.llen = self.llen
- obj.vlen = self.vlen
- obj.expl_lenindef = self.expl_lenindef
- obj.lenindef = self.lenindef
- obj.ber_encoded = self.ber_encoded
- return obj
+ def __getstate__(self):
+ return OctetStringState(
+ __version__,
+ self._value,
+ self._bound_min,
+ self._bound_max,
+ self.tag,
+ self._expl,
+ self.default,
+ self.optional,
+ self.offset,
+ self.llen,
+ self.vlen,
+ self.expl_lenindef,
+ self.lenindef,
+ self.ber_encoded,
+ self.tag_constructed,
+ self.defined,
+ )
+
+ def __setstate__(self, state):
+ super(OctetString, self).__setstate__(state)
+ self._value = state.value
+ self._bound_min = state.bound_min
+ self._bound_max = state.bound_max
+ self.tag = state.tag
+ self._expl = state.expl
+ self.default = state.default
+ self.optional = state.optional
+ self.offset = state.offset
+ self.llen = state.llen
+ self.vlen = state.vlen
+ self.expl_lenindef = state.expl_lenindef
+ self.lenindef = state.lenindef
+ self.ber_encoded = state.ber_encoded
+ self.tag_constructed = state.tag_constructed
+ self.defined = state.defined
def __bytes__(self):
self._assert_ready()
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)
yield pp
+NullState = namedtuple("NullState", (
+ "version",
+ "tag",
+ "expl",
+ "default",
+ "optional",
+ "offset",
+ "llen",
+ "vlen",
+ "expl_lenindef",
+ "lenindef",
+ "ber_encoded",
+))
+
+
class Null(Obj):
"""``NULL`` null object
def ready(self):
return True
- def copy(self):
- obj = self.__class__()
- obj.tag = self.tag
- obj._expl = self._expl
- obj.default = self.default
- obj.optional = self.optional
- obj.offset = self.offset
- obj.llen = self.llen
- obj.vlen = self.vlen
- obj.expl_lenindef = self.expl_lenindef
- obj.lenindef = self.lenindef
- obj.ber_encoded = self.ber_encoded
- return obj
+ def __getstate__(self):
+ return NullState(
+ __version__,
+ self.tag,
+ self._expl,
+ self.default,
+ self.optional,
+ self.offset,
+ self.llen,
+ self.vlen,
+ self.expl_lenindef,
+ self.lenindef,
+ self.ber_encoded,
+ )
+
+ def __setstate__(self, state):
+ super(Null, self).__setstate__(state)
+ self.tag = state.tag
+ self._expl = state.expl
+ self.default = state.default
+ self.optional = state.optional
+ self.offset = state.offset
+ self.llen = state.llen
+ self.vlen = state.vlen
+ self.expl_lenindef = state.expl_lenindef
+ self.lenindef = state.lenindef
+ self.ber_encoded = state.ber_encoded
def __eq__(self, their):
if not issubclass(their.__class__, Null):
offset=offset,
)
if tag_only: # pragma: no cover
- return
+ return None
try:
l, _, v = len_decode(lv)
except DecodeError as err:
yield pp
+ObjectIdentifierState = namedtuple("ObjectIdentifierState", (
+ "version",
+ "value",
+ "tag",
+ "expl",
+ "default",
+ "optional",
+ "offset",
+ "llen",
+ "vlen",
+ "expl_lenindef",
+ "lenindef",
+ "ber_encoded",
+ "defines",
+))
+
+
+def pureint(value):
+ i = int(value)
+ if (value[0] in "+- ") or (value[-1] == " "):
+ raise ValueError("non-pure integer")
+ return i
+
+
class ObjectIdentifier(Obj):
"""``OBJECT IDENTIFIER`` OID type
: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)
return value._value
if isinstance(value, string_types):
try:
- value = tuple(int(arc) for arc in value.split("."))
+ value = tuple(pureint(arc) for arc in value.split("."))
except ValueError:
raise InvalidOID("unacceptable arcs values")
if isinstance(value, tuple):
pass
else:
raise InvalidOID("unacceptable first arc value")
+ if not all(arc >= 0 for arc in value):
+ raise InvalidOID("negative arc value")
return value
raise InvalidValueType((self.__class__, str, tuple))
def ready(self):
return self._value is not None
- def copy(self):
- obj = self.__class__()
- obj._value = self._value
- obj.defines = self.defines
- obj.tag = self.tag
- obj._expl = self._expl
- obj.default = self.default
- obj.optional = self.optional
- obj.offset = self.offset
- obj.llen = self.llen
- obj.vlen = self.vlen
- obj.expl_lenindef = self.expl_lenindef
- obj.lenindef = self.lenindef
- obj.ber_encoded = self.ber_encoded
- return obj
+ def __getstate__(self):
+ return ObjectIdentifierState(
+ __version__,
+ self._value,
+ self.tag,
+ self._expl,
+ self.default,
+ self.optional,
+ self.offset,
+ self.llen,
+ self.vlen,
+ self.expl_lenindef,
+ self.lenindef,
+ self.ber_encoded,
+ self.defines,
+ )
+
+ def __setstate__(self, state):
+ super(ObjectIdentifier, self).__setstate__(state)
+ self._value = state.value
+ self.tag = state.tag
+ self._expl = state.expl
+ self.default = state.default
+ self.optional = state.optional
+ self.offset = state.offset
+ self.llen = state.llen
+ self.vlen = state.vlen
+ self.expl_lenindef = state.expl_lenindef
+ self.lenindef = state.lenindef
+ self.ber_encoded = state.ber_encoded
+ self.defines = state.defines
def __iter__(self):
self._assert_ready()
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")
raise InvalidValueType((self.__class__, int, str))
return value
- def copy(self):
- obj = self.__class__(_specs=self.specs)
- obj._value = self._value
- obj._bound_min = self._bound_min
- obj._bound_max = self._bound_max
- obj.tag = self.tag
- obj._expl = self._expl
- obj.default = self.default
- obj.optional = self.optional
- obj.offset = self.offset
- obj.llen = self.llen
- obj.vlen = self.vlen
- obj.expl_lenindef = self.expl_lenindef
- obj.lenindef = self.lenindef
- obj.ber_encoded = self.ber_encoded
- return obj
-
def __call__(
self,
value=None,
)
+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,
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
+PrintableStringState = namedtuple(
+ "PrintableStringState",
+ OctetStringState._fields + ("allowable_chars",),
+)
+
+
class PrintableString(AllowableCharsMixin, CommonString):
"""Printable string
Its value is properly sanitized: see X.680 41.4 table 10.
>>> PrintableString().allowable_chars
- >>> set([' ', "'", ..., 'z'])
+ frozenset([' ', "'", ..., 'z'])
+ >>> obj = PrintableString("foo*bar", allow_asterisk=True)
+ PrintableString PrintableString foo*bar
+ >>> obj.allow_asterisk, obj.allow_ampersand
+ (True, False)
"""
__slots__ = ()
tag_default = tag_encode(19)
encoding = "ascii"
asn1_type_name = "PrintableString"
- _allowable_chars = set(
+ _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,
+ )
+
+ @property
+ def allow_asterisk(self):
+ """Is asterisk character allowed?
+ """
+ return self._asterisk <= self._allowable_chars
+
+ @property
+ def allow_ampersand(self):
+ """Is ampersand character allowed?
+ """
+ return self._ampersand <= self._allowable_chars
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
+ def __getstate__(self):
+ return PrintableStringState(
+ *super(PrintableString, self).__getstate__(),
+ **{"allowable_chars": self._allowable_chars}
+ )
+
+ def __setstate__(self, state):
+ super(PrintableString, self).__setstate__(state)
+ self._allowable_chars = state.allowable_chars
+
+ 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.allow_asterisk,
+ allow_ampersand=self.allow_ampersand,
+ )
+
class TeletexString(CommonString):
__slots__ = ()
LEN_YYYYMMDDHHMMSSZ = len("YYYYMMDDHHMMSSZ")
-class UTCTime(CommonString):
+class VisibleString(CommonString):
+ __slots__ = ()
+ tag_default = tag_encode(26)
+ encoding = "ascii"
+ asn1_type_name = "VisibleString"
+
+
+class UTCTime(VisibleString):
"""``UTCTime`` datetime type
>>> t = UTCTime(datetime(2017, 9, 30, 22, 7, 50, 123))
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,
: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:
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.
+
+ .. warning::
+
+ Zero year is unsupported.
"""
__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):
asn1_type_name = "GraphicString"
-class VisibleString(CommonString):
- __slots__ = ()
- tag_default = tag_encode(26)
- encoding = "ascii"
- asn1_type_name = "VisibleString"
-
-
class ISO646String(VisibleString):
__slots__ = ()
asn1_type_name = "ISO646String"
asn1_type_name = "BMPString"
+ChoiceState = namedtuple("ChoiceState", (
+ "version",
+ "specs",
+ "value",
+ "tag",
+ "expl",
+ "default",
+ "optional",
+ "offset",
+ "llen",
+ "vlen",
+ "expl_lenindef",
+ "lenindef",
+ "ber_encoded",
+))
+
+
class Choice(Obj):
"""``CHOICE`` special type
default_obj._value = default_value
self.default = default_obj
if value is None:
- self._value = default_obj.copy()._value
+ self._value = copy(default_obj._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._value[1].bered
)
- def copy(self):
- obj = self.__class__(schema=self.specs)
- obj._expl = self._expl
- obj.default = self.default
- obj.optional = self.optional
- obj.offset = self.offset
- obj.llen = self.llen
- obj.vlen = self.vlen
- obj.expl_lenindef = self.expl_lenindef
- obj.lenindef = self.lenindef
- obj.ber_encoded = self.ber_encoded
- value = self._value
- if value is not None:
- obj._value = (value[0], value[1].copy())
- return obj
+ def __getstate__(self):
+ return ChoiceState(
+ __version__,
+ self.specs,
+ copy(self._value),
+ self.tag,
+ self._expl,
+ self.default,
+ self.optional,
+ self.offset,
+ self.llen,
+ self.vlen,
+ self.expl_lenindef,
+ self.lenindef,
+ self.ber_encoded,
+ )
+
+ def __setstate__(self, state):
+ super(Choice, self).__setstate__(state)
+ self.specs = state.specs
+ self._value = state.value
+ self._expl = state.expl
+ self.default = state.default
+ self.optional = state.optional
+ self.offset = state.offset
+ self.llen = state.llen
+ self.vlen = state.vlen
+ self.expl_lenindef = state.expl_lenindef
+ self.lenindef = state.lenindef
+ self.ber_encoded = state.ber_encoded
def __eq__(self, their):
if isinstance(their, tuple) and len(their) == 2:
offset=offset,
)
if tag_only: # pragma: no cover
- return
+ return None
value, tail = spec.decode(
tlv,
offset=offset,
It could be useful for general decoding of some unspecified values:
- >>> PrimitiveTypes().decode(hexdec("0403666f6f"))[0].value
+ >>> PrimitiveTypes().decod(hexdec("0403666f6f")).value
OCTET STRING 3 bytes 666f6f
- >>> PrimitiveTypes().decode(hexdec("0203123456"))[0].value
+ >>> PrimitiveTypes().decod(hexdec("0203123456")).value
INTEGER 1193046
"""
__slots__ = ()
))
+AnyState = namedtuple("AnyState", (
+ "version",
+ "value",
+ "tag",
+ "expl",
+ "optional",
+ "offset",
+ "llen",
+ "vlen",
+ "expl_lenindef",
+ "lenindef",
+ "ber_encoded",
+ "defined",
+))
+
+
class Any(Obj):
"""``ANY`` special type
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
return False
return self.defined[1].bered
- def copy(self):
- obj = self.__class__()
- obj._value = self._value
- obj.tag = self.tag
- obj._expl = self._expl
- obj.optional = self.optional
- obj.offset = self.offset
- obj.llen = self.llen
- obj.vlen = self.vlen
- obj.expl_lenindef = self.expl_lenindef
- obj.lenindef = self.lenindef
- obj.ber_encoded = self.ber_encoded
- return obj
+ def __getstate__(self):
+ return AnyState(
+ __version__,
+ self._value,
+ self.tag,
+ self._expl,
+ self.optional,
+ self.offset,
+ self.llen,
+ self.vlen,
+ self.expl_lenindef,
+ self.lenindef,
+ self.ber_encoded,
+ self.defined,
+ )
+
+ def __setstate__(self, state):
+ super(Any, self).__setstate__(state)
+ self._value = state.value
+ self.tag = state.tag
+ self._expl = state.expl
+ self.optional = state.optional
+ self.offset = state.offset
+ self.llen = state.llen
+ self.vlen = state.vlen
+ self.expl_lenindef = state.expl_lenindef
+ self.lenindef = state.lenindef
+ self.ber_encoded = state.ber_encoded
+ self.defined = state.defined
def __eq__(self, their):
if isinstance(their, binary_type):
_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):
return decode_path + rel_path
+SequenceState = namedtuple("SequenceState", (
+ "version",
+ "specs",
+ "value",
+ "tag",
+ "expl",
+ "default",
+ "optional",
+ "offset",
+ "llen",
+ "vlen",
+ "expl_lenindef",
+ "lenindef",
+ "ber_encoded",
+))
+
+
class Sequence(Obj):
"""``SEQUENCE`` structure type
default_obj._value = default_value
self.default = default_obj
if value is None:
- self._value = default_obj.copy()._value
+ self._value = copy(default_obj._value)
@property
def ready(self):
if spec.optional:
continue
return False
- else:
- if not value.ready:
- return False
+ if not value.ready:
+ return False
return True
@property
return True
return any(value.bered for value in itervalues(self._value))
- def copy(self):
- obj = self.__class__(schema=self.specs)
- obj.tag = self.tag
- obj._expl = self._expl
- obj.default = self.default
- obj.optional = self.optional
- obj.offset = self.offset
- obj.llen = self.llen
- obj.vlen = self.vlen
- 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)}
- return obj
+ def __getstate__(self):
+ return SequenceState(
+ __version__,
+ self.specs,
+ {k: copy(v) for k, v in iteritems(self._value)},
+ self.tag,
+ self._expl,
+ self.default,
+ self.optional,
+ self.offset,
+ self.llen,
+ self.vlen,
+ self.expl_lenindef,
+ self.lenindef,
+ self.ber_encoded,
+ )
+
+ def __setstate__(self, state):
+ super(Sequence, self).__setstate__(state)
+ self.specs = state.specs
+ self._value = state.value
+ self.tag = state.tag
+ self._expl = state.expl
+ self.default = state.default
+ self.optional = state.optional
+ self.offset = state.offset
+ self.llen = state.llen
+ self.vlen = state.vlen
+ self.expl_lenindef = state.expl_lenindef
+ self.lenindef = state.lenindef
+ self.ber_encoded = state.ber_encoded
def __eq__(self, their):
if not isinstance(their, self.__class__):
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:
return obj, tail
+SequenceOfState = namedtuple("SequenceOfState", (
+ "version",
+ "spec",
+ "value",
+ "bound_min",
+ "bound_max",
+ "tag",
+ "expl",
+ "default",
+ "optional",
+ "offset",
+ "llen",
+ "vlen",
+ "expl_lenindef",
+ "lenindef",
+ "ber_encoded",
+))
+
+
class SequenceOf(Obj):
"""``SEQUENCE OF`` sequence type
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:
default_obj._value = default_value
self.default = default_obj
if value is None:
- self._value = default_obj.copy()._value
+ self._value = copy(default_obj._value)
def _value_sanitize(self, value):
if issubclass(value.__class__, SequenceOf):
return True
return any(v.bered for v in self._value)
- def copy(self):
- obj = self.__class__(schema=self.spec)
- obj._bound_min = self._bound_min
- obj._bound_max = self._bound_max
- obj.tag = self.tag
- obj._expl = self._expl
- obj.default = self.default
- obj.optional = self.optional
- obj.offset = self.offset
- obj.llen = self.llen
- obj.vlen = self.vlen
- obj.expl_lenindef = self.expl_lenindef
- obj.lenindef = self.lenindef
- obj.ber_encoded = self.ber_encoded
- obj._value = [v.copy() for v in self._value]
- return obj
+ def __getstate__(self):
+ return SequenceOfState(
+ __version__,
+ self.spec,
+ [copy(v) for v in self._value],
+ self._bound_min,
+ self._bound_max,
+ self.tag,
+ self._expl,
+ self.default,
+ self.optional,
+ self.offset,
+ self.llen,
+ self.vlen,
+ self.expl_lenindef,
+ self.lenindef,
+ self.ber_encoded,
+ )
+
+ def __setstate__(self, state):
+ super(SequenceOf, self).__setstate__(state)
+ self.spec = state.spec
+ self._value = state.value
+ self._bound_min = state.bound_min
+ self._bound_max = state.bound_max
+ self.tag = state.tag
+ self._expl = state.expl
+ self.default = state.default
+ self.optional = state.optional
+ self.offset = state.offset
+ self.llen = state.llen
+ self.vlen = state.vlen
+ self.expl_lenindef = state.expl_lenindef
+ self.lenindef = state.lenindef
+ self.ber_encoded = state.ber_encoded
def __eq__(self, their):
if isinstance(their, self.__class__):
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=(),
for pp in pps:
if hasattr(pp, "_fields"):
if (
- decode_path_only != () and
- pp.decode_path[:len(decode_path_only)] != decode_path_only
+ decode_path_only != () and
+ pp.decode_path[:len(decode_path_only)] != decode_path_only
):
continue
if pp.asn1_type_name == Choice.asn1_type_name:
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,
decode_path_len_decrease=len(decode_path_only),
)
for row in pp_console_blob(
- pp,
- decode_path_len_decrease=len(decode_path_only),
+ pp,
+ decode_path_len_decrease=len(decode_path_only),
):
yield row
else:
)
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,
- with_colours=True if environ.get("NO_COLOR") is None else False,
+ oid_maps=oid_maps,
+ 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