Preferable way is to :ref:`download <download>` tarball with the
signature from `official website <http://pyderasn.cypherpunks.ru/>`__::
- $ [fetch|wget] http://pyderasn.cypherpunks.ru/pyderasn-7.5.tar.xz
- $ [fetch|wget] http://pyderasn.cypherpunks.ru/pyderasn-7.5.tar.xz.sig
- $ gpg --verify pyderasn-7.5.tar.xz.sig pyderasn-7.5.tar.xz
- $ xz --decompress --stdout pyderasn-7.5.tar.xz | tar xf -
- $ cd pyderasn-7.5
+ $ [fetch|wget] http://pyderasn.cypherpunks.ru/pyderasn-7.6.tar.xz
+ $ [fetch|wget] http://pyderasn.cypherpunks.ru/pyderasn-7.6.tar.xz.sig
+ $ gpg --verify pyderasn-7.6.tar.xz.sig pyderasn-7.6.tar.xz
+ $ xz --decompress --stdout pyderasn-7.6.tar.xz | tar xf -
+ $ cd pyderasn-7.6
$ python setup.py install
# or copy pyderasn.py (+six.py, possibly termcolor.py) to your PYTHONPATH
You could use pip (**no** OpenPGP authentication is performed!) with PyPI::
$ cat > requirements.txt <<EOF
- pyderasn==7.5 --hash=sha256:TO-BE-FILLED
+ pyderasn==7.6 --hash=sha256:TO-BE-FILLED
six==1.14.0 --hash=sha256:236bdbdce46e6e6a3d61a337c0f8b763ca1e8717c03b369e87a7ec7ce1319c0a
EOF
$ pip install --requirement requirements.txt
Limitations
===========
-* Strings (except for :py:class:`pyderasn.NumericString` and
- :py:class:`pyderasn.PrintableString`) are not validated
- in any way, except just trying to be decoded in ``ascii``,
- ``iso-8859-1``, ``utf-8/16/32`` correspondingly
* :py:class:`pyderasn.GeneralizedTime` does not support zero year
* No REAL, RELATIVE OID, EXTERNAL, INSTANCE OF, EMBEDDED PDV, CHARACTER STRING
News
====
+.. _release7.6:
+
+7.6
+---
+* Proper strict alphabet validation of VisibleString
+* VisibleString and IA5String also have ``allowable_chars`` property
+* Fixed TeletexString, T61String use ``iso-8859-1`` encoding (instead of
+ ``ascii``), because they are 8-bit encodings
+
.. _release7.5:
7.5
.. autoclass:: pyderasn.PrintableString
:members: __init__, allow_asterisk, allow_ampersand
+IA5String
+_________
+.. autoclass:: pyderasn.IA5String
+
+VisibleString
+_____________
+.. autoclass:: pyderasn.VisibleString
+
UTCTime
_______
.. autoclass:: pyderasn.UTCTime
def colored(what, *args, **kwargs):
return what
-__version__ = "7.5"
+__version__ = "7.6"
__all__ = (
"agg_octet_string",
: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`
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
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",
"""
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__(),
class TeletexString(CommonString):
__slots__ = ()
tag_default = tag_encode(20)
- encoding = "ascii"
+ encoding = "iso-8859-1"
asn1_type_name = "TeletexString"
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")
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(
asn1_type_name = "GraphicString"
-class ISO646String(VisibleString):
- __slots__ = ()
- asn1_type_name = "ISO646String"
-
-
class GeneralString(CommonString):
__slots__ = ()
tag_default = tag_encode(27)
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):
"07ba44cce54a2d723f9847f626dc054605076321ab469b9c78d5545b3d0c1ec86",
"48cb55023826fdbb8221c439607a8bb",
)))
- with assertRaisesRegex(self, DecodeError, "non-printable"):
+ with assertRaisesRegex(self, DecodeError, "alphabet value"):
crt = Certificate().decod(raw)
repr(err.exception)
def text_alphabet(self):
- if self.base_klass.encoding in ("ascii", "iso-8859-1"):
- return printable + whitespace
- return None
+ return "".join(six_unichr(c) for c in six_xrange(256))
@given(booleans())
def test_optional(self, optional):
@given(text(alphabet=ascii_letters, min_size=1, max_size=5))
def test_non_numeric(self, non_numeric_text):
- with assertRaisesRegex(self, DecodeError, "non-numeric"):
+ with assertRaisesRegex(self, DecodeError, "alphabet value"):
self.base_klass(non_numeric_text)
@given(
@given(text(alphabet=sorted(set(whitespace) - set(" ")), min_size=1, max_size=5))
def test_non_printable(self, non_printable_text):
- with assertRaisesRegex(self, DecodeError, "non-printable"):
+ with assertRaisesRegex(self, DecodeError, "alphabet value"):
self.base_klass(non_printable_text)
@given(
for prop in kwargs.keys():
self.assertFalse(getattr(obj, prop))
s += c
- with assertRaisesRegex(self, DecodeError, "non-printable"):
+ with assertRaisesRegex(self, DecodeError, "alphabet value"):
self.base_klass(s)
self.base_klass(s, **kwargs)
klass = self.base_klass(**kwargs)
):
base_klass = IA5String
+ def text_alphabet(self):
+ return "".join(six_unichr(c) for c in six_xrange(128))
+
+ @given(integers(min_value=128, max_value=255))
+ def test_alphabet_bad(self, code):
+ with self.assertRaises(DecodeError):
+ self.base_klass().decod(
+ self.base_klass.tag_default +
+ len_encode(1) +
+ bytes(bytearray([code])),
+ )
+
class TestGraphicString(
UnicodeDecodeErrorMixin,
):
base_klass = VisibleString
+ def text_alphabet(self):
+ return " !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~"
+
def test_x690_vector(self):
obj, tail = VisibleString().decode(hexdec("1A054A6F6E6573"))
self.assertSequenceEqual(tail, b"")
self.assertTrue(obj.lenindef)
self.assertTrue(obj.bered)
+ @given(one_of((
+ integers(min_value=0, max_value=ord(" ") - 1),
+ integers(min_value=ord("~") + 1, max_value=255),
+ )))
+ def test_alphabet_bad(self, code):
+ with self.assertRaises(DecodeError):
+ self.base_klass().decod(
+ self.base_klass.tag_default +
+ len_encode(1) +
+ bytes(bytearray([code])),
+ )
+
+ @given(
+ sets(integers(min_value=0, max_value=10), min_size=2, max_size=2),
+ integers(min_value=0),
+ decode_path_strat,
+ )
+ def test_invalid_bounds_while_decoding(self, ints, offset, decode_path):
+ value, bound_min = list(sorted(ints))
+
+ class String(self.base_klass):
+ bounds = (bound_min, bound_min)
+ with self.assertRaises(DecodeError) as err:
+ String().decode(
+ self.base_klass(b"1" * value).encode(),
+ offset=offset,
+ decode_path=decode_path,
+ )
+ repr(err.exception)
+ self.assertEqual(err.exception.offset, offset)
+ self.assertEqual(err.exception.decode_path, decode_path)
+
class TestGeneralString(
UnicodeDecodeErrorMixin,