doc/news.rst | 4 ++++ pyderasn.py | 55 +++++++++++++++++++++++++++++++++++++++++++++++++++++ tests/test_pyderasn.py | 15 +++++++++++++++ diff --git a/doc/news.rst b/doc/news.rst index d7a2b5ddf9492dfbbd7b0d19c5649b9e4bd00f92e545b8f54d3366076291b4a7..8701148fbaf15474c45edbcd134310c8c132270df679eb0825684dd906a4e1ea 100644 --- a/doc/news.rst +++ b/doc/news.rst @@ -8,6 +8,10 @@ --- * Convenient ``.decod()`` method, that raises if tail is not empty * Control characters (like newlines) of text fields in pprinted output are escaped +* Ability to allow asterisk and ampersand characters + (``allow_asterisk``, ``allow_ampersand`` kwargs) in + ``PrintableString``, that unfortunately could be met + in X.509 certificates .. _release5.5: diff --git a/pyderasn.py b/pyderasn.py index a509da1eda5183482a387254dcecae9ef9448e24d3ecfb56915d1727b44067c5..c94280cf010e46f0fa6714d7a0de9071ed75c460a8ade3a3bbea596b384d5a9f 100755 --- a/pyderasn.py +++ b/pyderasn.py @@ -570,6 +570,7 @@ PrintableString _______________ .. autoclass:: pyderasn.PrintableString + :members: __init__ UTCTime _______ @@ -3779,12 +3780,66 @@ asn1_type_name = "PrintableString" _allowable_chars = frozenset( (ascii_letters + digits + " '()+,-./:=?").encode("ascii") ) + _asterisk = frozenset("*".encode("ascii")) + _ampersand = frozenset("&".encode("ascii")) + + def __init__( + self, + value=None, + bounds=None, + impl=None, + expl=None, + default=None, + optional=False, + _decoded=(0, 0, 0), + allow_asterisk=False, + allow_ampersand=False, + ): + """ + :param allow_asterisk: allow asterisk character + :param allow_ampersand: allow ampersand character + """ + if allow_asterisk: + self._allowable_chars |= self._asterisk + if allow_ampersand: + self._allowable_chars |= self._ampersand + super(PrintableString, self).__init__( + value, bounds, impl, expl, default, optional, _decoded, + ) 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 copy(self): + obj = super(PrintableString, self).copy() + obj._allowable_chars = self._allowable_chars + return obj + + def __call__( + self, + value=None, + bounds=None, + impl=None, + expl=None, + default=None, + optional=None, + ): + return self.__class__( + value=value, + bounds=( + (self._bound_min, self._bound_max) + if bounds is None else bounds + ), + impl=self.tag if impl is None else impl, + expl=self._expl if expl is None else expl, + default=self.default if default is None else default, + optional=self.optional if optional is None else optional, + allow_asterisk=self._asterisk <= self._allowable_chars, + allow_ampersand=self._ampersand <= self._allowable_chars, + ) class TeletexString(CommonString): diff --git a/tests/test_pyderasn.py b/tests/test_pyderasn.py index aa2ca834d156dbd059df7ff9979373f3f3dbba89e24f6c3266b513c52ece0c73..b8c475f23b7586562309549e94ab85077a6ba9a7424cdd3d79e57c6ebf89db86 100644 --- a/tests/test_pyderasn.py +++ b/tests/test_pyderasn.py @@ -3506,6 +3506,21 @@ repr(err.exception) self.assertEqual(err.exception.offset, offset) self.assertEqual(err.exception.decode_path, decode_path) + def test_allowable_invalid_chars(self): + for c, kwargs in ( + ("*", {"allow_asterisk": True}), + ("&", {"allow_ampersand": True}), + ("&*", {"allow_asterisk": True, "allow_ampersand": True}), + ): + s = "hello invalid " + c + with assertRaisesRegex(self, DecodeError, "non-printable"): + self.base_klass(s) + self.base_klass(s, **kwargs) + klass = self.base_klass(**kwargs) + obj = klass(s) + obj = obj.copy() + obj(s) + class TestTeletexString( UnicodeDecodeErrorMixin,