#!/usr/bin/env python
# coding: utf-8
# PyDERASN -- Python ASN.1 DER/BER codec with abstract structures
-# Copyright (C) 2017-2018 Sergey Matveev <stargrave@stargrave.org>
+# Copyright (C) 2017-2019 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
-------
.. autofunction:: pyderasn.abs_decode_path
+.. autofunction:: pyderasn.colonize_hex
.. autofunction:: pyderasn.hexenc
.. autofunction:: pyderasn.hexdec
.. autofunction:: pyderasn.tag_encode
from codecs import getencoder
from collections import namedtuple
from collections import OrderedDict
+from copy import copy
from datetime import datetime
from math import ceil
from os import environ
+from string import ascii_letters
from string import digits
from six import add_metaclass
from six import PY2
from six import string_types
from six import text_type
+from six import unichr as six_unichr
from six.moves import xrange as six_xrange
try:
from termcolor import colored
-except ImportError:
+except ImportError: # pragma: no cover
def colored(what, *args):
return what
# Errors
########################################################################
-class DecodeError(Exception):
+class ASN1Error(ValueError):
+ pass
+
+
+class DecodeError(ASN1Error):
def __init__(self, msg="", klass=None, decode_path=(), offset=0):
"""
:param str msg: reason of decode failing
pass
-class ObjUnknown(ValueError):
+class ObjUnknown(ASN1Error):
def __init__(self, name):
super(ObjUnknown, self).__init__()
self.name = name
return "%s(%s)" % (self.__class__.__name__, self)
-class ObjNotReady(ValueError):
+class ObjNotReady(ASN1Error):
def __init__(self, name):
super(ObjNotReady, self).__init__()
self.name = name
return "%s(%s)" % (self.__class__.__name__, self)
-class InvalidValueType(ValueError):
+class InvalidValueType(ASN1Error):
def __init__(self, expected_types):
super(InvalidValueType, self).__init__()
self.expected_types = expected_types
return "%s(%s)" % (self.__class__.__name__, self)
-class BoundsError(ValueError):
+class BoundsError(ASN1Error):
def __init__(self, bound_min, value, bound_max):
super(BoundsError, self).__init__()
self.bound_min = bound_min
decode_path=(),
ctx=None,
tag_only=False,
+ _ctx_immutable=True,
):
"""Decode the data
:param int offset: initial data's offset
:param bool leavemm: do we need to leave memoryview of remaining
data as is, or convert it to bytes otherwise
- :param ctx: optional :ref:`context <ctx>` governing decoding process.
+ :param ctx: optional :ref:`context <ctx>` governing decoding process
: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
:returns: (Obj, remaining data)
"""
if ctx is None:
ctx = {}
+ elif _ctx_immutable:
+ ctx = copy(ctx)
tlv = memoryview(data)
if self._expl is None:
result = self._decode(
ctx=ctx,
tag_only=tag_only,
)
- if tag_only:
+ if tag_only: # pragma: no cover
return
obj, tail = result
eoc_expected, tail = tail[:EOC_LEN], tail[EOC_LEN:]
ctx=ctx,
tag_only=tag_only,
)
- if tag_only:
+ if tag_only: # pragma: no cover
return
obj, tail = result
if obj.tlvlen < l and not ctx.get("allow_expl_oob", False):
return self.expl_tlvlen if self.expled else self.tlvlen
def pps_lenindef(self, decode_path):
- if self.lenindef:
+ if self.lenindef and not (
+ getattr(self, "defined", None) is not None and
+ self.defined[1].lenindef
+ ):
yield _pp(
asn1_type_name="EOC",
obj_name="",
########################################################################
PP = namedtuple("PP", (
+ "obj",
"asn1_type_name",
"obj_name",
"decode_path",
def _pp(
+ obj=None,
asn1_type_name="unknown",
obj_name="unknown",
decode_path=(),
bered=False,
):
return PP(
+ obj,
asn1_type_name,
obj_name,
decode_path,
return colored(what, colour, attrs=attrs) if with_colours else what
+def colonize_hex(hexed):
+ """Separate hexadecimal string with colons
+ """
+ return ":".join(hexed[i:i + 2] for i in range(0, len(hexed), 2))
+
+
def pp_console_row(
pp,
oids=None,
value in oids
):
cols.append(_colourize("(%s)" % oids[value], "green", with_colours))
+ if pp.asn1_type_name == Integer.asn1_type_name:
+ hex_repr = hex(int(pp.obj._value))[2:].upper()
+ if len(hex_repr) % 2 != 0:
+ hex_repr = "0" + hex_repr
+ cols.append(_colourize(
+ "(%s)" % colonize_hex(hex_repr),
+ "green",
+ with_colours,
+ ))
if with_blob:
if isinstance(pp.blob, binary_type):
cols.append(hexenc(pp.blob))
blob = hexenc(pp.blob).upper()
for i in range(0, len(blob), 32):
chunk = blob[i:i + 32]
- yield " ".join(cols + [":".join(
- chunk[j:j + 2] for j in range(0, len(chunk), 2)
- )])
+ yield " ".join(cols + [colonize_hex(chunk)])
elif isinstance(pp.blob, tuple):
yield " ".join(cols + [", ".join(pp.blob)])
def pps(self, decode_path=()):
yield _pp(
+ obj=self,
asn1_type_name=self.asn1_type_name,
obj_name=self.__class__.__name__,
decode_path=decode_path,
def pps(self, decode_path=()):
yield _pp(
+ obj=self,
asn1_type_name=self.asn1_type_name,
obj_name=self.__class__.__name__,
decode_path=decode_path,
offset=offset,
)
if t == self.tag:
- if tag_only:
+ if tag_only: # pragma: no cover
return
return self._decode_chunk(lv, offset, decode_path, ctx)
if t == self.tag_constructed:
decode_path=decode_path,
offset=offset,
)
- if tag_only:
+ if tag_only: # pragma: no cover
return
lenindef = False
try:
decode_path=sub_decode_path,
leavemm=True,
ctx=ctx,
+ _ctx_immutable=False,
)
except TagMismatch:
raise DecodeError(
if len(self.specs) > 0:
blob = tuple(self.named)
yield _pp(
+ obj=self,
asn1_type_name=self.asn1_type_name,
obj_name=self.__class__.__name__,
decode_path=decode_path,
decode_path=sub_decode_path,
leavemm=True,
ctx=ctx,
+ _ctx_immutable=False,
)
except TagMismatch:
raise DecodeError(
def pps(self, decode_path=()):
yield _pp(
+ obj=self,
asn1_type_name=self.asn1_type_name,
obj_name=self.__class__.__name__,
decode_path=decode_path,
decode_path=decode_path,
offset=offset,
)
- if tag_only:
+ if tag_only: # pragma: no cover
return
try:
l, _, v = len_decode(lv)
def pps(self, decode_path=()):
yield _pp(
+ obj=self,
asn1_type_name=self.asn1_type_name,
obj_name=self.__class__.__name__,
decode_path=decode_path,
decode_path=decode_path,
offset=offset,
)
- if tag_only:
+ if tag_only: # pragma: no cover
return
try:
l, llen, v = len_decode(lv)
def pps(self, decode_path=()):
yield _pp(
+ obj=self,
asn1_type_name=self.asn1_type_name,
obj_name=self.__class__.__name__,
decode_path=decode_path,
if self.ready:
value = hexenc(bytes(self)) if no_unicode else self.__unicode__()
yield _pp(
+ obj=self,
asn1_type_name=self.asn1_type_name,
obj_name=self.__class__.__name__,
decode_path=decode_path,
asn1_type_name = "UTF8String"
-class NumericString(CommonString):
+class AllowableCharsMixin(object):
+ @property
+ def allowable_chars(self):
+ if PY2:
+ return self._allowable_chars
+ return set(six_unichr(c) for c in self._allowable_chars)
+
+
+class NumericString(AllowableCharsMixin, CommonString):
"""Numeric string
- Its value is properly sanitized: only ASCII digits can be stored.
+ Its value is properly sanitized: only ASCII digits with spaces can
+ be stored.
+
+ >>> NumericString().allowable_chars
+ set(['3', '4', '7', '5', '1', '0', '8', '9', ' ', '6', '2'])
"""
__slots__ = ()
tag_default = tag_encode(18)
encoding = "ascii"
asn1_type_name = "NumericString"
- allowable_chars = set(digits.encode("ascii"))
+ _allowable_chars = set(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 set(value) <= self._allowable_chars:
raise DecodeError("non-numeric value")
return value
-class PrintableString(CommonString):
+class PrintableString(AllowableCharsMixin, CommonString):
+ """Printable string
+
+ Its value is properly sanitized: see X.680 41.4 table 10.
+
+ >>> PrintableString().allowable_chars
+ >>> set([' ', "'", ..., 'z'])
+ """
__slots__ = ()
tag_default = tag_encode(19)
encoding = "ascii"
asn1_type_name = "PrintableString"
+ _allowable_chars = set(
+ (ascii_letters + digits + " '()+,-./:=?").encode("ascii")
+ )
+
+ def _value_sanitize(self, value):
+ value = super(PrintableString, self)._value_sanitize(value)
+ if not set(value) <= self._allowable_chars:
+ raise DecodeError("non-printable value")
+ return value
class TeletexString(CommonString):
def pps(self, decode_path=()):
yield _pp(
+ obj=self,
asn1_type_name=self.asn1_type_name,
obj_name=self.__class__.__name__,
decode_path=decode_path,
decode_path=sub_decode_path,
ctx=ctx,
tag_only=True,
+ _ctx_immutable=False,
)
except TagMismatch:
continue
decode_path=decode_path,
offset=offset,
)
- if tag_only:
+ if tag_only: # pragma: no cover
return
value, tail = spec.decode(
tlv,
leavemm=True,
decode_path=sub_decode_path,
ctx=ctx,
+ _ctx_immutable=False,
)
obj = self.__class__(
schema=self.specs,
def pps(self, decode_path=()):
yield _pp(
+ obj=self,
asn1_type_name=self.asn1_type_name,
obj_name=self.__class__.__name__,
decode_path=decode_path,
decode_path=decode_path + (str(chunk_i),),
leavemm=True,
ctx=ctx,
+ _ctx_immutable=False,
)
vlen += chunk.tlvlen
sub_offset += chunk.tlvlen
def pps(self, decode_path=()):
yield _pp(
+ obj=self,
asn1_type_name=self.asn1_type_name,
obj_name=self.__class__.__name__,
decode_path=decode_path,
def abs_decode_path(decode_path, rel_path):
"""Create an absolute decode path from current and relative ones
- :param decode_path: current decode path, starting point.
- Tuple of strings
+ :param decode_path: current decode path, starting point. Tuple of strings
:param rel_path: relative path to ``decode_path``. Tuple of strings.
If first tuple's element is "/", then treat it as
an absolute path, ignoring ``decode_path`` as
decode_path=decode_path,
offset=offset,
)
- if tag_only:
+ if tag_only: # pragma: no cover
return
lenindef = False
ctx_bered = ctx.get("bered", False)
leavemm=True,
decode_path=sub_decode_path,
ctx=ctx,
+ _ctx_immutable=False,
)
except TagMismatch:
if spec.optional:
leavemm=True,
decode_path=sub_sub_decode_path,
ctx=ctx,
+ _ctx_immutable=False,
)
if len(defined_tail) > 0:
raise DecodeError(
leavemm=True,
decode_path=sub_decode_path + (DecodePathDefBy(defined_by),),
ctx=ctx,
+ _ctx_immutable=False,
)
if len(defined_tail) > 0:
raise DecodeError(
def pps(self, decode_path=()):
yield _pp(
+ obj=self,
asn1_type_name=self.asn1_type_name,
obj_name=self.__class__.__name__,
decode_path=decode_path,
decode_path=sub_decode_path,
ctx=ctx,
tag_only=True,
+ _ctx_immutable=False,
)
except TagMismatch:
continue
leavemm=True,
decode_path=sub_decode_path,
ctx=ctx,
+ _ctx_immutable=False,
)
value_len = value.fulllen
if value_prev.tobytes() > v[:value_len].tobytes():
leavemm=True,
decode_path=sub_decode_path,
ctx=ctx,
+ _ctx_immutable=False,
)
value_len = value.fulllen
if ordering_check:
def pps(self, decode_path=()):
yield _pp(
+ obj=self,
asn1_type_name=self.asn1_type_name,
obj_name=self.__class__.__name__,
decode_path=decode_path,