#!/usr/bin/env python
# coding: utf-8
-# PyDERASN -- Python ASN.1 DER codec with abstract structures
+# PyDERASN -- Python ASN.1 DER/BER codec with abstract structures
# Copyright (C) 2017-2018 Sergey Matveev <stargrave@stargrave.org>
#
# This program is free software: you can redistribute it and/or modify
# 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/>.
-"""Python ASN.1 DER codec with abstract structures
+"""Python ASN.1 DER/BER codec with abstract structures
-This library allows you to marshal and unmarshal various structures in
-ASN.1 DER format, like this:
+This library allows you to marshal various structures in ASN.1 DER
+format, unmarshal them in BER/CER/DER ones.
>>> i = Integer(123)
>>> raw = i.encode()
lesser than ``offset``), ``expl_tlen``, ``expl_llen``, ``expl_vlen``
(that actually equals to ordinary ``tlvlen``).
-When error occurs, then :py:exc:`pyderasn.DecodeError` is raised.
+When error occurs, :py:exc:`pyderasn.DecodeError` is raised.
.. _ctx:
Currently available context options:
+* :ref:`bered <bered_ctx>`
* :ref:`defines_by_path <defines_by_path_ctx>`
* :ref:`strict_default_existence <strict_default_existence_ctx>`
decoding is already done. ``any`` means literally any value it meet --
useful for SEQUENCE/SET OF-s.
+.. _bered_ctx:
+
+BER encoding
+------------
+
+.. warning::
+
+ Currently BER support is not extensively tested.
+
+By default PyDERASN accepts only DER encoded data. It always encodes to
+DER. But you can optionally enable BER decoding with setting ``bered``
+:ref:`context <ctx>` argument to True. Indefinite lengths and
+constructed primitive types should be parsed successfully.
+
+* If object is encoded in BER form (not the DER one), then ``bered``
+ attribute is set to True. Only ``BOOLEAN``, ``BIT STRING``, ``OCTET
+ STRING`` can contain it.
+* If object has an indefinite length encoding, then its ``lenindef``
+ attribute is set to True. Only ``BIT STRING``, ``OCTET STRING``,
+ ``SEQUENCE``, ``SET``, ``SEQUENCE OF``, ``SET OF``, ``ANY`` can
+ contain it.
+* If object has an indefinite length encoded explicit tag, then
+ ``expl_lenindef`` is set to True.
+
+EOC (end-of-contents) token's length is taken in advance in object's
+value length.
+
Primitive types
---------------
____________
.. autoclass:: pyderasn.CommonString
+NumericString
+_____________
+.. autoclass:: pyderasn.NumericString
+
UTCTime
_______
.. autoclass:: pyderasn.UTCTime
.. autofunction:: pyderasn.tag_ctxp
.. autofunction:: pyderasn.tag_ctxc
.. autoclass:: pyderasn.Obj
+.. autoclass:: pyderasn.DecodeError
+ :members: __init__
+.. autoclass:: pyderasn.NotEnoughData
+.. autoclass:: pyderasn.LenIndefForm
+.. autoclass:: pyderasn.TagMismatch
+.. autoclass:: pyderasn.InvalidLength
+.. autoclass:: pyderasn.InvalidOID
+.. autoclass:: pyderasn.ObjUnknown
+.. autoclass:: pyderasn.ObjNotReady
+.. autoclass:: pyderasn.InvalidValueType
+.. autoclass:: pyderasn.BoundsError
"""
from codecs import getdecoder
"InvalidOID",
"InvalidValueType",
"ISO646String",
+ "LenIndefForm",
"NotEnoughData",
"Null",
"NumericString",
self.tag = getattr(self, "impl", self.tag_default) if impl is None else impl
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"
- )
+ raise ValueError("implicit and explicit tags can not be set simultaneously")
if default is not None:
optional = True
self.optional = optional
eoc_expected, tail = tail[:EOC_LEN], tail[EOC_LEN:]
if eoc_expected.tobytes() != EOC:
raise DecodeError(
- msg="no EOC",
+ "no EOC",
+ klass=self.__class__,
decode_path=decode_path,
offset=offset,
)
['nonRepudiation', 'keyEncipherment']
>>> b.specs
{'nonRepudiation': 1, 'digitalSignature': 0, 'keyEncipherment': 2}
+
+ .. note::
+
+ Pay attention that BIT STRING can be encoded both in primitive
+ and constructed forms. Decoder always checks constructed form tag
+ additionally to specified primitive one. If BER decoding is
+ :ref:`not enabled <bered_ctx>`, then decoder will fail, because
+ of DER restrictions.
"""
__slots__ = ("tag_constructed", "specs", "defined")
tag_default = tag_encode(3)
if t == self.tag_constructed:
if not ctx.get("bered", False):
raise DecodeError(
- msg="unallowed BER constructed encoding",
+ "unallowed BER constructed encoding",
+ klass=self.__class__,
decode_path=decode_path,
offset=offset,
)
break
if vlen > l:
raise DecodeError(
- msg="chunk out of bounds",
+ "chunk out of bounds",
+ klass=self.__class__,
decode_path=len(chunks) - 1,
offset=chunks[-1].offset,
)
)
except TagMismatch:
raise DecodeError(
- msg="expected BitString encoded chunk",
+ "expected BitString encoded chunk",
+ klass=self.__class__,
decode_path=sub_decode_path,
offset=sub_offset,
)
v = v_tail
if len(chunks) == 0:
raise DecodeError(
- msg="no chunks",
+ "no chunks",
+ klass=self.__class__,
decode_path=decode_path,
offset=offset,
)
for chunk_i, chunk in enumerate(chunks[:-1]):
if chunk.bit_len % 8 != 0:
raise DecodeError(
- msg="BitString chunk is not multiple of 8 bit",
+ "BitString chunk is not multiple of 8 bit",
+ klass=self.__class__,
decode_path=decode_path + (str(chunk_i),),
offset=chunk.offset,
)
pyderasn.BoundsError: unsatisfied bounds: 4 <= 5 <= 4
>>> OctetString(b"hell", bounds=(4, 4))
OCTET STRING 4 bytes 68656c6c
+
+ .. note::
+
+ Pay attention that OCTET STRING can be encoded both in primitive
+ and constructed forms. Decoder always checks constructed form tag
+ additionally to specified primitive one. If BER decoding is
+ :ref:`not enabled <bered_ctx>`, then decoder will fail, because
+ of DER restrictions.
"""
__slots__ = ("tag_constructed", "_bound_min", "_bound_max", "defined")
tag_default = tag_encode(4)
if t == self.tag_constructed:
if not ctx.get("bered", False):
raise DecodeError(
- msg="unallowed BER constructed encoding",
+ "unallowed BER constructed encoding",
+ klass=self.__class__,
decode_path=decode_path,
offset=offset,
)
break
if vlen > l:
raise DecodeError(
- msg="chunk out of bounds",
+ "chunk out of bounds",
+ klass=self.__class__,
decode_path=len(chunks) - 1,
offset=chunks[-1].offset,
)
)
except TagMismatch:
raise DecodeError(
- msg="expected OctetString encoded chunk",
+ "expected OctetString encoded chunk",
+ klass=self.__class__,
decode_path=sub_decode_path,
offset=sub_offset,
)
v = v_tail
if len(chunks) == 0:
raise DecodeError(
- msg="no chunks",
+ "no chunks",
+ klass=self.__class__,
decode_path=decode_path,
offset=offset,
)
class NumericString(CommonString):
+ """Numeric string
+
+ Its value is properly sanitized: only ASCII digits can be stored.
+ """
__slots__ = ()
tag_default = tag_encode(18)
encoding = "ascii"
obj._value = values
if not obj.ready:
raise DecodeError(
- msg="not all values are ready",
+ "not all values are ready",
klass=self.__class__,
decode_path=decode_path,
offset=offset,
def main(): # pragma: no cover
import argparse
- parser = argparse.ArgumentParser(description="PyDERASN ASN.1 DER decoder")
+ parser = argparse.ArgumentParser(description="PyDERASN ASN.1 BER/DER decoder")
parser.add_argument(
"--skip",
type=int,