VERSION | 2 +- doc/install.rst | 12 ++++++------ doc/limitations.rst | 4 ---- doc/news.rst | 9 +++++++++ pyderasn.py | 91 ++++++++++++++++++++++++++++++++++------------------- tests/test_crts.py | 2 +- tests/test_pyderasn.py | 57 +++++++++++++++++++++++++++++++++++++++++++++++------ diff --git a/VERSION b/VERSION index 6157a06cac3415588c0c844ff730c24131966163de78aa0211b34d48f340dd9b..6a538de689d650ac1f33a64ae526b38b082592fa7dfa0f1755d7f9cff728bb3f 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -7.5 +7.6 diff --git a/doc/install.rst b/doc/install.rst index f1da956b71e3c8133c0b9cb2e6dbaaa2f6f89fed4e6d19c218c773bdca4ff4fa..3386cdd90b489b2c096a6210e3c14fbea1875d907cb0bf0404f1351161ee2fe3 100644 --- a/doc/install.rst +++ b/doc/install.rst @@ -4,11 +4,11 @@ Preferable way is to :ref:`download ` tarball with the signature from `official website `__:: - $ [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 @@ -21,7 +21,7 @@ You could use pip (**no** OpenPGP authentication is performed!) with PyPI:: $ cat > requirements.txt <>> 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") @@ -5001,11 +5017,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( @@ -5423,11 +5455,6 @@ __slots__ = () tag_default = tag_encode(25) encoding = "iso-8859-1" asn1_type_name = "GraphicString" - - -class ISO646String(VisibleString): - __slots__ = () - asn1_type_name = "ISO646String" class GeneralString(CommonString): @@ -7415,7 +7442,7 @@ 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): diff --git a/tests/test_crts.py b/tests/test_crts.py index ede57b743ce55419845fc6f3681244097926ff93d7c3cfd8b391955fd1081e6b..0a940aa09e8682b54c113bb07331d926672175d12e93cb9ec97eae001827a108 100644 --- a/tests/test_crts.py +++ b/tests/test_crts.py @@ -430,5 +430,5 @@ "b4ad7b9dc889e3778415bf782a5f2ba41255a901a1e4538a1525875942644fb20", "07ba44cce54a2d723f9847f626dc054605076321ab469b9c78d5545b3d0c1ec86", "48cb55023826fdbb8221c439607a8bb", ))) - with assertRaisesRegex(self, DecodeError, "non-printable"): + with assertRaisesRegex(self, DecodeError, "alphabet value"): crt = Certificate().decod(raw) diff --git a/tests/test_pyderasn.py b/tests/test_pyderasn.py index 02fcd3347078a95ea0ef04865effdd94ec93d753e2c16b333e4f384a4452e01d..32d1d4a3fc84a5ace35c5560045c00f12cf239fa16f013f0fd07f066c0431532 100644 --- a/tests/test_pyderasn.py +++ b/tests/test_pyderasn.py @@ -3462,9 +3462,7 @@ self.base_klass((1, 2)) 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): @@ -3828,7 +3826,7 @@ return digits + " " @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( @@ -3878,7 +3876,7 @@ return ascii_letters + digits + " '()+,-./:=?" @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( @@ -3912,7 +3910,7 @@ obj = self.base_klass(s) 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) @@ -3951,6 +3949,18 @@ TestCase, ): 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, @@ -3968,6 +3978,9 @@ CommonMixin, TestCase, ): base_klass = VisibleString + + def text_alphabet(self): + return " !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~" def test_x690_vector(self): obj, tail = VisibleString().decode(hexdec("1A054A6F6E6573")) @@ -4004,6 +4017,38 @@ obj = copy(obj) self.assertTrue(obj.ber_encoded) 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(