]> Cypherpunks.ru repositories - pyderasn.git/blobdiff - pyderasn.py
Fix example SignedAttributes bounds
[pyderasn.git] / pyderasn.py
index f611efa195369727b2a2a20455a8c5b796431389..e3ac23f2edac814ec2c6f806eadf8e40f012286f 100755 (executable)
@@ -355,6 +355,11 @@ Let's parse that output, human::
  (and its derivatives), ``SET``, ``SET OF``, ``UTCTime``, ``GeneralizedTime``
  could be BERed.
 
+Also it could be helpful to add quick ASN.1 pprinting command in your
+pdb's configuration file::
+
+    alias pp1 import pyderasn ;; print(pyderasn.pprint(%1, oid_maps=(locals().get("OID_STR_TO_NAME", {}),)))
+
 .. _definedby:
 
 DEFINED BY
@@ -765,7 +770,7 @@ forcefully encoded in DER during CER encoding, by specifying
 
     class SignedAttributes(SetOf):
         schema = Attribute()
-        bounds = (1, 32)
+        bounds = (1, float("+inf"))
         der_forced = True
 
 .. _agg_octet_string:
@@ -962,6 +967,14 @@ _______________
 .. autoclass:: pyderasn.PrintableString
    :members: __init__, allow_asterisk, allow_ampersand
 
+IA5String
+_________
+.. autoclass:: pyderasn.IA5String
+
+VisibleString
+_____________
+.. autoclass:: pyderasn.VisibleString
+
 UTCTime
 _______
 .. autoclass:: pyderasn.UTCTime
@@ -1162,8 +1175,6 @@ from datetime import datetime
 from datetime import timedelta
 from io import BytesIO
 from math import ceil
-from mmap import mmap
-from mmap import PROT_READ
 from operator import attrgetter
 from string import ascii_letters
 from string import digits
@@ -1193,7 +1204,7 @@ except ImportError:  # pragma: no cover
     def colored(what, *args, **kwargs):
         return what
 
-__version__ = "7.5"
+__version__ = "8.1"
 
 __all__ = (
     "agg_octet_string",
@@ -1284,14 +1295,21 @@ def file_mmaped(fd):
 
     :param fd: file object
     :returns: memoryview over read-only mmap-ing of the whole file
+
+    .. warning::
+
+       It is known to work under neither Python 2.x nor Windows.
     """
-    return memoryview(mmap(fd.fileno(), 0, prot=PROT_READ))
+    import mmap
+    return memoryview(mmap.mmap(fd.fileno(), length=0, prot=mmap.PROT_READ))
+
 
 def pureint(value):
     if not set(value) <= DECIMALS:
         raise ValueError("non-pure integer")
     return int(value)
 
+
 def fractions2float(fractions_raw):
     pureint(fractions_raw)
     return float("0." + fractions_raw)
@@ -1304,7 +1322,7 @@ def get_def_by_path(defines_by_path, sub_decode_path):
         if len(path) != len(sub_decode_path):
             continue
         for p1, p2 in zip(path, sub_decode_path):
-            if (not p1 is any) and (p1 != p2):
+            if (p1 is not any) and (p1 != p2):
                 break
         else:
             return define
@@ -1548,6 +1566,10 @@ def tag_strip(data):
             raise DecodeError("unfinished tag")
         if indexbytes(data, i) & 0x80 == 0:
             break
+    if i == 1 and indexbytes(data, 1) < 0x1F:
+        raise DecodeError("unexpected long form")
+    if i > 1 and indexbytes(data, 1) & 0x7F == 0:
+        raise DecodeError("leading zero byte in tag value")
     i += 1
     return data[:i], i, data[i:]
 
@@ -1963,7 +1985,7 @@ class Obj(object):
                     yield None
                     return
                 _decode_path, obj, tail = result
-                if not _decode_path is decode_path:
+                if _decode_path is not decode_path:
                     yield result
         else:
             try:
@@ -2005,7 +2027,7 @@ class Obj(object):
                         yield None
                         return
                     _decode_path, obj, tail = result
-                    if not _decode_path is decode_path:
+                    if _decode_path is not decode_path:
                         yield result
                 eoc_expected, tail = tail[:EOC_LEN], tail[EOC_LEN:]
                 if eoc_expected.tobytes() != EOC:
@@ -2044,7 +2066,7 @@ class Obj(object):
                         yield None
                         return
                     _decode_path, obj, tail = result
-                    if not _decode_path is decode_path:
+                    if _decode_path is not decode_path:
                         yield result
                 if obj.tlvlen < l and not ctx.get("allow_expl_oob", False):
                     raise DecodeError(
@@ -3373,7 +3395,7 @@ class BitString(Obj):
                 int2byte(0),
                 octets[offset:offset + 999],
             )))
-        tail = octets[offset+999:]
+        tail = octets[offset + 999:]
         if len(tail) > 0:
             tail = int2byte((8 - bit_len % 8) % 8) + tail
             write_full(writer, b"".join((
@@ -3833,7 +3855,7 @@ class OctetString(Obj):
                 LEN1K,
                 octets[offset:offset + 1000],
             )))
-        tail = octets[offset+1000:]
+        tail = octets[offset + 1000:]
         if len(tail) > 0:
             write_full(writer, b"".join((
                 OctetString.tag_default,
@@ -4708,27 +4730,25 @@ class CommonString(OctetString):
        :header-rows: 1
 
        * - Class
-         - Text Encoding
+         - Text Encoding, validation
        * - :py:class:`pyderasn.UTF8String`
          - utf-8
        * - :py:class:`pyderasn.NumericString`
-         - ascii
+         - proper alphabet validation
        * - :py:class:`pyderasn.PrintableString`
-         - ascii
+         - proper alphabet validation
        * - :py:class:`pyderasn.TeletexString`
-         - ascii
+         - iso-8859-1
        * - :py:class:`pyderasn.T61String`
-         - ascii
+         - iso-8859-1
        * - :py:class:`pyderasn.VideotexString`
          - iso-8859-1
        * - :py:class:`pyderasn.IA5String`
-         - ascii
+         - proper alphabet validation
        * - :py:class:`pyderasn.GraphicString`
          - iso-8859-1
-       * - :py:class:`pyderasn.VisibleString`
-         - ascii
-       * - :py:class:`pyderasn.ISO646String`
-         - ascii
+       * - :py:class:`pyderasn.VisibleString`, :py:class:`pyderasn.ISO646String`
+         - proper alphabet validation
        * - :py:class:`pyderasn.GeneralString`
          - iso-8859-1
        * - :py:class:`pyderasn.UniversalString`
@@ -4836,6 +4856,12 @@ class AllowableCharsMixin(object):
             return self._allowable_chars
         return frozenset(six_unichr(c) for c in self._allowable_chars)
 
+    def _value_sanitize(self, value):
+        value = super(AllowableCharsMixin, self)._value_sanitize(value)
+        if not frozenset(value) <= self._allowable_chars:
+            raise DecodeError("non satisfying alphabet value")
+        return value
+
 
 class NumericString(AllowableCharsMixin, CommonString):
     """Numeric string
@@ -4852,12 +4878,6 @@ class NumericString(AllowableCharsMixin, CommonString):
     asn1_type_name = "NumericString"
     _allowable_chars = frozenset(digits.encode("ascii") + b" ")
 
-    def _value_sanitize(self, value):
-        value = super(NumericString, self)._value_sanitize(value)
-        if not frozenset(value) <= self._allowable_chars:
-            raise DecodeError("non-numeric value")
-        return value
-
 
 PrintableStringState = namedtuple(
     "PrintableStringState",
@@ -4925,12 +4945,6 @@ class PrintableString(AllowableCharsMixin, CommonString):
         """
         return self._ampersand <= self._allowable_chars
 
-    def _value_sanitize(self, value):
-        value = super(PrintableString, self)._value_sanitize(value)
-        if not frozenset(value) <= self._allowable_chars:
-            raise DecodeError("non-printable value")
-        return value
-
     def __getstate__(self):
         return PrintableStringState(
             *super(PrintableString, self).__getstate__(),
@@ -4968,7 +4982,7 @@ class PrintableString(AllowableCharsMixin, CommonString):
 class TeletexString(CommonString):
     __slots__ = ()
     tag_default = tag_encode(20)
-    encoding = "ascii"
+    encoding = "iso-8859-1"
     asn1_type_name = "TeletexString"
 
 
@@ -4984,11 +4998,27 @@ class VideotexString(CommonString):
     asn1_type_name = "VideotexString"
 
 
-class IA5String(CommonString):
+class IA5String(AllowableCharsMixin, CommonString):
+    """IA5 string
+
+    Its value is properly sanitized: it is a mix of
+
+    * http://www.itscj.ipsj.or.jp/iso-ir/006.pdf (G)
+    * http://www.itscj.ipsj.or.jp/iso-ir/001.pdf (C0)
+    * DEL character (0x7F)
+
+    It is just 7-bit ASCII.
+
+    >>> IA5String().allowable_chars
+    frozenset(["NUL", ... "DEL"])
+    """
     __slots__ = ()
     tag_default = tag_encode(22)
     encoding = "ascii"
     asn1_type_name = "IA5"
+    _allowable_chars = frozenset(b"".join(
+        six_unichr(c).encode("ascii") for c in six_xrange(128)
+    ))
 
 
 LEN_YYMMDDHHMMSSZ = len("YYMMDDHHMMSSZ")
@@ -4999,11 +5029,27 @@ LEN_YYYYMMDDHHMMSSZ = len("YYYYMMDDHHMMSSZ")
 LEN_LEN_YYYYMMDDHHMMSSZ = len_encode(LEN_YYYYMMDDHHMMSSZ)
 
 
-class VisibleString(CommonString):
+class VisibleString(AllowableCharsMixin, CommonString):
+    """Visible string
+
+    Its value is properly sanitized. ASCII subset from space to tilde is
+    allowed: http://www.itscj.ipsj.or.jp/iso-ir/006.pdf
+
+    >>> VisibleString().allowable_chars
+    frozenset([" ", ... "~"])
+    """
     __slots__ = ()
     tag_default = tag_encode(26)
     encoding = "ascii"
     asn1_type_name = "VisibleString"
+    _allowable_chars = frozenset(b"".join(
+        six_unichr(c).encode("ascii") for c in six_xrange(ord(" "), ord("~") + 1)
+    ))
+
+
+class ISO646String(VisibleString):
+    __slots__ = ()
+    asn1_type_name = "ISO646String"
 
 
 UTCTimeState = namedtuple(
@@ -5042,13 +5088,20 @@ class UTCTime(VisibleString):
 
     .. warning::
 
-       Pay attention that UTCTime can not hold full year, so all years
-       having < 50 years are treated as 20xx, 19xx otherwise, according
-       to X.509 recommendation.
+       Only **naive** ``datetime`` objects are supported.
+       Library assumes that all work is done in UTC.
+
+    .. warning::
+
+       Pay attention that ``UTCTime`` can not hold full year, so all years
+       having < 50 years are treated as 20xx, 19xx otherwise, according to
+       X.509 recommendation. Use ``GeneralizedTime`` instead for
+       removing ambiguity.
 
     .. warning::
 
-       No strict validation of UTC offsets are made, but very crude:
+       No strict validation of UTC offsets are made (only applicable to
+       **BER**), but very crude:
 
        * minutes are not exceeding 60
        * offset value is not exceeding 14 hours
@@ -5175,6 +5228,8 @@ class UTCTime(VisibleString):
         if isinstance(value, self.__class__):
             return value._value, None
         if value.__class__ == datetime:
+            if value.tzinfo is not None:
+                raise ValueError("only naive datetime supported")
             return self._dt_sanitize(value), None
         raise InvalidValueType((self.__class__, datetime))
 
@@ -5285,23 +5340,23 @@ class GeneralizedTime(UTCTime):
 
     .. warning::
 
-       Only microsecond fractions are supported in DER encoding.
-       :py:exc:`pyderasn.DecodeError` will be raised during decoding of
-       higher precision values.
+       Only **naive** datetime objects are supported.
+       Library assumes that all work is done in UTC.
 
     .. warning::
 
-       BER encoded data can loss information (accuracy) during decoding
-       because of float transformations.
+       Only **microsecond** fractions are supported in DER encoding.
+       :py:exc:`pyderasn.DecodeError` will be raised during decoding of
+       higher precision values.
 
     .. warning::
 
-       Local times (without explicit timezone specification) are treated
-       as UTC one, no transformations are made.
+       **BER** encoded data can loss information (accuracy) during
+       decoding because of float transformations.
 
     .. warning::
 
-       Zero year is unsupported.
+       **Zero** year is unsupported.
     """
     __slots__ = ()
     tag_default = tag_encode(24)
@@ -5423,11 +5478,6 @@ class GraphicString(CommonString):
     asn1_type_name = "GraphicString"
 
 
-class ISO646String(VisibleString):
-    __slots__ = ()
-    asn1_type_name = "ISO646String"
-
-
 class GeneralString(CommonString):
     __slots__ = ()
     tag_default = tag_encode(27)
@@ -5845,7 +5895,7 @@ class Any(Obj):
     def _value_sanitize(self, value):
         if value.__class__ == binary_type:
             if len(value) == 0:
-                raise ValueError("Any value can not be empty")
+                raise ValueError("%s value can not be empty" % self.__class__.__name__)
             return value
         if isinstance(value, self.__class__):
             return value._value
@@ -7413,7 +7463,7 @@ def ascii_visualize(ba):
         92 2b 39 20 65 91 e6 8e  95 93 1a 58 df 02 78 ea  |.+9 e......X..x.|
                                                            ^^^^^^^^^^^^^^^^
     """
-    return "".join((chr(b) if 0x20 <= b <= 0x7E else ".") for b in ba)
+    return "".join((six_unichr(b) if 0x20 <= b <= 0x7E else ".") for b in ba)
 
 
 def hexdump(raw):
@@ -7855,12 +7905,12 @@ def main():  # pragma: no cover
         help="Path to BER/CER/DER file you want to decode",
     )
     args = parser.parse_args()
-    if PY2:
+    try:
+        raw = file_mmaped(args.RAWFile)[args.skip:]
+    except:
         args.RAWFile.seek(args.skip)
         raw = memoryview(args.RAWFile.read())
         args.RAWFile.close()
-    else:
-        raw = file_mmaped(args.RAWFile)[args.skip:]
     oid_maps = (
         [obj_by_path(_path) for _path in (args.oids or "").split(",")]
         if args.oids else ()