vzGeBXtCD2yUIeJgSeF/3VoEq8lxJ+rwHwcsIqHF7QdqJCc7S0wviHUEEBEIAB0W
IQTPYOiaWSMeduJjZCKuGoEJ5JhX7wUCWcLAIAAKCRCuGoEJ5JhX7+lbAP9+WNA4
Uk0pNH5BAASabuT+zllnHZ5SqZoKWbs7bzWfogD+NWmjTfSJCr7GSZ4Suy3Vw4nn
-hUu3L6dceWUU+hAEOBw=
-=Qodb
+hUu3L6dceWUU+hAEOByIdQQQFgoAHRYhBBKtMmicZg1CaWf9dcuCBWMhB62KBQJi
+6jwUAAoJEMuCBWMhB62KYHMBAOQ6VHkVXpBrQAWCNYUEo9LZAvM2CokI6HVpJps1
+7mZNAP0RI3s/4v8N7a4b+ghbaEtxBIWWlXxqlBgDj/Rbnke0Dg==
+=0AVp
-----END PGP PUBLIC KEY BLOCK-----
rm -fr _build
html=_build/html
PYTHONPATH=.. ${PYTHON:=python} -msphinx . $html
-[ -d download ] && cp -r download $html || echo No download directory, skipping
+[ -d download ] && cp -a download $html || echo No download directory, skipping
rm -r $html/.doctrees $html/.buildinfo
find $html -type d -exec chmod 755 {} +
find $html -type f -exec chmod 644 {} +
source_suffix = ".rst"
master_doc = "index"
project = "pyderasn"
-copyright = "2017-2021, Sergey Matveev"
+copyright = "2017-2023, Sergey Matveev"
author = "Sergey Matveev"
version = version
release = version
-language = None
exclude_patterns = ["_build"]
pygments_style = "sphinx"
todo_include_todos = False
conveniently replace utilities like either ``dumpasn1`` or
``openssl asn1parse``
- .. figure:: pprinting.png
+ .. figure:: pprinting.webp
:alt: Pretty printing example output
An example of pretty printed X.509 certificate with automatically
parsed DEFINED BY fields.
* :ref:`ASN.1 browser <browser>`
- .. figure:: browser.png
+ .. figure:: browser.webp
:alt: ASN.1 browser example
An example of browser running.
* `Как я написал ASN.1 библиотеку с slots and blobs <https://m.habr.com/ru/post/444272/>`__ (on russian)
* `Как я добавил big-data поддержку <https://m.habr.com/ru/post/498014/>`__ (on russian)
-.. figure:: pprinting.png
+.. figure:: pprinting.webp
:alt: Pretty printing example output
An example of pretty printed X.509 certificate with automatically
parsed DEFINED BY fields.
-.. figure:: browser.png
+.. figure:: browser.webp
:alt: ASN.1 browser example
An example of browser running.
Preferable way is to :ref:`download <download>` tarball with the
signature from `official website <http://www.pyderasn.cypherpunks.ru/>`__::
- $ [fetch|wget] http://www.pyderasn.cypherpunks.ru/download/pyderasn-9.1.tar.zst
- $ [fetch|wget] http://www.pyderasn.cypherpunks.ru/download/pyderasn-9.1.tar.zst.sig
- $ gpg --verify pyderasn-9.1.tar.zst.sig pyderasn-9.1.tar.zst
- $ zstd -d < pyderasn-9.1.tar.zst | tar xf -
- $ cd pyderasn-9.1
+ $ [fetch|wget] http://www.pyderasn.cypherpunks.ru/download/pyderasn-9.3.tar.zst
+ $ [fetch|wget] http://www.pyderasn.cypherpunks.ru/download/pyderasn-9.3.tar.zst.asc
+ $ gpg --verify pyderasn-9.3.tar.zst.asc pyderasn-9.3.tar.zst
+ $ zstd -d < pyderasn-9.3.tar.zst | tar xf -
+ $ cd pyderasn-9.3
$ python setup.py install
# or copy pyderasn.py (possibly termcolor.py) to your PYTHONPATH
* ``urwid`` is an optional dependency used for :ref:`interactive browser <browser>`.
* ``dateutil`` is an optional dependency used for ``.totzdatetime()`` method.
-You could use pip (**no** OpenPGP authentication is performed!) with PyPI::
-
- $ echo pyderasn==9.1 --hash=sha256:TO-BE-FILLED > requirements.txt
- $ pip install --requirement requirements.txt
-
You have to verify downloaded tarballs integrity and authenticity to be
sure that you retrieved trusted and untampered software. `GNU Privacy
Guard <https://www.gnupg.org/>`__ is used for that purpose.
uid PyDERASN releases <pyderasn@cypherpunks.ru>
$ gpg --auto-key-locate dane --locate-keys pyderasn at cypherpunks dot ru
- $ gpg --auto-key-locate wkd --locate-keys pyderasn at cypherpunks dot ru
+ $ gpg --auto-key-locate wkd --locate-keys pyderasn at cypherpunks dot ru
.. literalinclude:: ../PUBKEY.asc
News
====
+.. _release9.3:
+
+9.3
+---
+* CommonString's ``.memoryview()`` raises ValueError now for
+ friendliness with linters
+
+.. _release9.2:
+
+9.2
+---
+* ``keep_memoryview`` context option appeared, respected by OctetString
+ and Any objects during DER decoding. If set, then their internal
+ values will keep memoryview reference instead of full bytes copy
+* Correspondingly OctetString and Any have ``.memoryview()`` method
+
.. _release9.1:
9.1
echo pyderasn.py
echo setup.py
find $(perl -lane 'print $F[1]' MANIFEST.in)
-} | tar cfI - - | tar xfC - $tmp/pyderasn-"$release"
+} | tar cfT - - | tar xfC - $tmp/pyderasn-"$release"
PYTHONPATH="$tmp/pyderasn-$release" redo $tmp/pyderasn-"$release"/doc/build.log
rm -r $tmp/pyderasn-"$release"/doc/.redo $tmp/pyderasn-"$release"/doc/build.log
tar xvfC doc/download/termcolor-1.1.0.tar.gz $tmp --include "*/termcolor.py"
mv -v $tmp/termcolor-*/termcolor.py $tmp/pyderasn-"$release"
-pip_hash=$(pip hash dist/pyderasn-"$release".tar.gz | sed -n '$p')
-
cd $tmp
find . -type d -exec chmod 755 {} +
find . -type f -exec chmod 644 {} +
chmod 755 pyderasn-"$release"/pyderasn.py
tar cvf pyderasn-"$release".tar --uid=0 --gid=0 --numeric-owner pyderasn-"$release"
zstd -19 -v pyderasn-"$release".tar
-gpg --detach-sign --sign --local-user 04A933D1BA20327A pyderasn-"$release".tar.zst
-
tarball=pyderasn-"$release".tar.zst
+gpg --armor --detach-sign --sign --local-user pyderasn@cypherpunks.ru $tarball
+meta4-create -fn "$tarball" -mtime "$tarball" -sig "$tarball".asc \
+ http://www.pyderasn.cypherpunks.ru/download/"$tarball" \
+ http://y.www.pyderasn.cypherpunks.ru/download/"$tarball" < "$tarball" > "$tarball".meta4
+
size=$(( $(stat -f %z $tarball) / 1024 ))
-hash=$(gpg --print-md SHA256 < $tarball)
release_date=$(date "+%Y-%m-%d")
cat <<EOF
* - \`\`pyderasn\`\` :ref:\`$release <release$release>\`
- $release_date
- $size KiB
- - \`link <download/pyderasn-${release}.tar.zst>\`__
- \`sign <download/pyderasn-${release}.tar.zst.sig>\`__
- - \`\`$hash\`\`
-
-pyderasn==$release $pip_hash
+ - \`meta4 <download/pyderasn-${release}.tar.zst.meta4>\`__
+ \`tar <download/pyderasn-${release}.tar.zst>\`__
+ \`sig <download/pyderasn-${release}.tar.zst.asc>\`__
EOF
-mv $tmp/$tarball $tmp/"$tarball".sig $cur/doc/download
+mv $tmp/$tarball $tmp/"$tarball".asc $tmp/"$tarball".meta4 $cur/doc/download
cat <<EOF
Subject: PyDERASN $release release announcement
Source code and its signature for that version can be found here:
http://www.pyderasn.cypherpunks.ru/download/pyderasn-${release}.tar.zst ($size KiB)
- http://www.pyderasn.cypherpunks.ru/download/pyderasn-${release}.tar.zst.sig
+ http://www.pyderasn.cypherpunks.ru/download/pyderasn-${release}.tar.zst.asc
-SHA256 hash: $hash
GPG key: 2ED6 C846 3051 02DF 5B4E 0383 04A9 33D1 BA20 327A
PyDERASN releases <pyderasn at cypherpunks dot ru>
-pip'es requirements file:
-
- pyderasn==$release $pip_hash
-
Please send questions regarding the use of PyDERASN, bug reports and patches
to mailing list: http://lists.cypherpunks.ru/pyderasn_002ddevel.html
EOF
attrs==19.3.0
coverage==4.5.4
-hypothesis==3.57.0
+hypothesis==6.39.4
python-dateutil==2.8.1
# pylint: disable=line-too-long,superfluous-parens,protected-access,too-many-lines
# pylint: disable=too-many-return-statements,too-many-branches,too-many-statements
# PyDERASN -- Python ASN.1 DER/CER/BER codec with abstract structures
-# Copyright (C) 2017-2021 Sergey Matveev <stargrave@stargrave.org>
+# Copyright (C) 2017-2024 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
* :ref:`bered <bered_ctx>`
* :ref:`defines_by_path <defines_by_path_ctx>`
* :ref:`evgen_mode_upto <evgen_mode_upto_ctx>`
+* :ref:`keep_memoryview <keep_memoryview_ctx>`
.. _pprinting:
page cache used for mmaps. It can take twice the necessary size in
the memory: both in page cache and ZFS ARC.
+.. _keep_memoryview_ctx:
+
+That read-only memoryview could be safe to be used as a value inside
+decoded :py:class:`pyderasn.OctetString` and :py:class:`pyderasn.Any`
+objects. You can enable that by setting `"keep_memoryview": True` in
+:ref:`decode context <ctx>`. No OCTET STRING and ANY values will be
+copied to memory. Of course that works only in DER encoding, where the
+value is continuously encoded.
+
CER encoding
____________
tzUTC = "missing"
-__version__ = "9.1"
+__version__ = "9.3"
__all__ = (
"agg_octet_string",
tag_default = tag_encode(4)
asn1_type_name = "OCTET STRING"
evgen_mode_skip_value = True
+ memoryview_safe = True
def __init__(
self,
self._assert_ready()
return bytes(self._value)
+ def memoryview(self):
+ self._assert_ready()
+ return memoryview(self._value)
+
def __eq__(self, their):
if their.__class__ == bytes:
return self._value == their
decode_path=decode_path,
offset=offset,
)
+ if evgen_mode and self.evgen_mode_skip_value:
+ value = None
+ elif self.memoryview_safe and ctx.get("keep_memoryview", False):
+ value = v
+ else:
+ value = v.tobytes()
try:
obj = self.__class__(
- value=(
- None if (evgen_mode and self.evgen_mode_skip_value)
- else v.tobytes()
- ),
+ value=value,
bounds=(self._bound_min, self._bound_max),
impl=self.tag,
expl=self._expl,
- utf-16-be
"""
__slots__ = ()
+ memoryview_safe = False
def _value_sanitize(self, value):
value_raw = None
return self._value.decode(self.encoding)
return str(self._value)
+ def memoryview(self):
+ raise ValueError("CommonString does not support .memoryview()")
+
def __repr__(self):
return pp_console_row(next(self.pps()))
value = self._value_sanitize(value)
self._value = value
if self._expl is None:
- if value.__class__ == bytes:
+ if value.__class__ == bytes or value.__class__ == memoryview:
tag_class, _, tag_num = tag_decode(tag_strip(value)[0])
else:
tag_class, tag_num = value.tag_order
self.defined = None
def _value_sanitize(self, value):
- if value.__class__ == bytes:
+ if value.__class__ == bytes or value.__class__ == memoryview:
if len(value) == 0:
raise ValueError("%s value can not be empty" % self.__class__.__name__)
return value
self.defined = state.defined
def __eq__(self, their):
- if their.__class__ == bytes:
- if self._value.__class__ == bytes:
+ if their.__class__ == bytes or their.__class__ == memoryview:
+ if self._value.__class__ == bytes or their.__class__ == memoryview:
return self._value == their
return self._value.encode() == their
if issubclass(their.__class__, Any):
if self.ready and their.ready:
- return bytes(self) == bytes(their)
+ return self.memoryview() == their.memoryview()
return self.ready == their.ready
return False
value = self._value
if value.__class__ == bytes:
return value
+ if value.__class__ == memoryview:
+ return bytes(value)
return self._value.encode()
+ def memoryview(self):
+ self._assert_ready()
+ value = self._value
+ if value.__class__ == memoryview:
+ return memoryview(value)
+ return memoryview(bytes(self))
+
@property
def tlen(self):
return 0
def _encode(self):
self._assert_ready()
value = self._value
- if value.__class__ == bytes:
- return value
+ if value.__class__ == bytes or value.__class__ == memoryview:
+ return bytes(self)
return value.encode()
def _encode1st(self, state):
self._assert_ready()
value = self._value
- if value.__class__ == bytes:
+ if value.__class__ == bytes or value.__class__ == memoryview:
return len(value), state
return value.encode1st(state)
def _encode2nd(self, writer, state_iter):
value = self._value
- if value.__class__ == bytes:
+ if value.__class__ == bytes or value.__class__ == memoryview:
write_full(writer, value)
else:
value.encode2nd(writer, state_iter)
def _encode_cer(self, writer):
self._assert_ready()
value = self._value
- if value.__class__ == bytes:
+ if value.__class__ == bytes or value.__class__ == memoryview:
write_full(writer, value)
else:
value.encode_cer(writer)
)
tlvlen = tlen + llen + l
v, tail = tlv[:tlvlen], v[l:]
+ if evgen_mode:
+ value = None
+ elif ctx.get("keep_memoryview", False):
+ value = v
+ else:
+ value = v.tobytes()
obj = self.__class__(
- value=None if evgen_mode else v.tobytes(),
+ value=value,
expl=self._expl,
optional=self.optional,
_decoded=(offset, 0, tlvlen),
value = self._value
if value is None:
pass
- elif value.__class__ == bytes:
+ elif value.__class__ == bytes or value.__class__ == memoryview:
value = None
else:
value = repr(value)
obj_name=self.__class__.__name__,
decode_path=decode_path,
value=value,
- blob=self._value if self._value.__class__ == bytes else None,
+ blob=self._value if (
+ self._value.__class__ == bytes or
+ value.__class__ == memoryview
+ ) else None,
optional=self.optional,
default=self == self.default,
impl=None if self.tag == self.tag_default else tag_decode(self.tag),
# coding: utf-8
# PyDERASN -- Python ASN.1 DER/CER/BER codec with abstract structures
-# Copyright (C) 2017-2021 Sergey Matveev <stargrave@stargrave.org>
+# Copyright (C) 2017-2024 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
# coding: utf-8
# PyDERASN -- Python ASN.1 DER/CER/BER codec with abstract structures
-# Copyright (C) 2017-2021 Sergey Matveev <stargrave@stargrave.org>
+# Copyright (C) 2017-2024 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
# coding: utf-8
# PyDERASN -- Python ASN.1 DER/CER/BER codec with abstract structures
-# Copyright (C) 2017-2021 Sergey Matveev <stargrave@stargrave.org>
+# Copyright (C) 2017-2024 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
"ba3ca12568fdc6c7b4511cd40a7f659980402df2b998bb9a4a8cbeb34c0f0a78c",
"f8d91ede14a5ed76bf116fe360aafa8821490435",
)))
- crt = Certificate().decod(raw)
+ crt = Certificate().decod(raw, ctx={"keep_memoryview": True})
tbs = crt["tbsCertificate"]
self.assertEqual(tbs["version"], 0)
self.assertFalse(tbs["version"].decoded)
"998bb9a4a8cbeb34c0f0a78cf8d91ede14a5ed76bf116fe360aafa8821490435",
)))))
self.assertSequenceEqual(crt.encode(), raw)
+ crt = Certificate().decod(raw)
pprint(crt)
repr(crt)
pickle_loads(pickle_dumps(crt, pickle_proto))
# coding: utf-8
# PyDERASN -- Python ASN.1 DER/CER/BER codec with abstract structures
-# Copyright (C) 2017-2021 Sergey Matveev <stargrave@stargrave.org>
+# Copyright (C) 2017-2024 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
repr(obj)
list(obj.pps())
- @given(integers(min_value=2))
+ @given(integers(min_value=2, max_value=10))
def test_invalid_len(self, l):
with self.assertRaises(InvalidLength):
Boolean().decode(b"".join((
if generation_choice == 2 or draw(booleans()):
return draw(binary(max_size=len(schema) // 8))
if generation_choice == 3 or draw(booleans()):
+ if len(schema) == 0:
+ return ()
return tuple(draw(lists(sampled_from([name for name, _ in schema]))))
return None
value = _value(value_required)
integers(min_value=0),
binary(max_size=5),
decode_path_strat,
+ booleans(),
)
- def test_symmetric(self, values, value, tag_expl, offset, tail_junk, decode_path):
+ def test_symmetric(
+ self,
+ values,
+ value,
+ tag_expl,
+ offset,
+ tail_junk,
+ decode_path,
+ keep_memoryview,
+ ):
for klass in (OctetString, OctetStringInherited):
_, _, _, _, default, optional, _decoded = values
obj = klass(
obj_expled.decod(obj_expled_cer, ctx={"bered": True}).encode(),
obj_expled_encoded,
)
+ ctx_dummy["keep_memoryview"] = keep_memoryview
ctx_copied = deepcopy(ctx_dummy)
obj_decoded, tail = obj_expled.decode(
obj_expled_encoded + tail_junk,
self.assertNotEqual(obj_decoded, obj)
self.assertEqual(bytes(obj_decoded), bytes(obj_expled))
self.assertEqual(bytes(obj_decoded), bytes(obj))
+ self.assertIsInstance(
+ obj_decoded._value,
+ memoryview if keep_memoryview else bytes,
+ )
self.assertSequenceEqual(obj_decoded.encode(), obj_expled_encoded)
self.assertSequenceEqual(obj_decoded.expl_tag, tag_expl)
self.assertEqual(obj_decoded.expl_tlen, len(tag_expl))
if first_arc in (0, 1):
second_arc = draw(integers(min_value=0, max_value=39))
else:
- second_arc = draw(integers(min_value=0))
- other_arcs = draw(lists(integers(min_value=0)))
+ second_arc = draw(integers(min_value=0, max_value=1 << 63))
+ other_arcs = draw(lists(integers(min_value=0, max_value=1 << 63)))
return tuple([first_arc, second_arc] + other_arcs)
with self.assertRaisesRegex(DecodeError, "unfinished OID"):
obj.decode(data)
- @given(integers(min_value=0))
+ @given(integers(min_value=0, max_value=1 << 63))
def test_invalid_short(self, value):
with self.assertRaises(InvalidOID):
ObjectIdentifier((value,))
with self.assertRaises(InvalidOID):
ObjectIdentifier("%d" % value)
- @given(integers(min_value=3), integers(min_value=0))
+ @given(
+ integers(min_value=3, max_value=1 << 63),
+ integers(min_value=0, max_value=1 << 63),
+ )
def test_invalid_first_arc(self, first_arc, second_arc):
with self.assertRaises(InvalidOID):
ObjectIdentifier((first_arc, second_arc))
with self.assertRaises(InvalidOID):
ObjectIdentifier("%d.%d" % (first_arc, second_arc))
- @given(integers(min_value=0, max_value=1), integers(min_value=40))
+ @given(
+ integers(min_value=0, max_value=1),
+ integers(min_value=40, max_value=1 << 63),
+ )
def test_invalid_second_arc(self, first_arc, second_arc):
with self.assertRaises(InvalidOID):
ObjectIdentifier((first_arc, second_arc))
integers(min_value=0),
binary(max_size=5),
decode_path_strat,
+ booleans(),
)
- def test_symmetric(self, values, value, tag_expl, offset, tail_junk, decode_path):
+ def test_symmetric(
+ self,
+ values,
+ value,
+ tag_expl,
+ offset,
+ tail_junk,
+ decode_path,
+ keep_memoryview,
+ ):
for klass in (Any, AnyInherited):
_, _, optional, _decoded = values
obj = klass(value=value, optional=optional, _decoded=_decoded)
list(obj_expled.pps())
pprint(obj_expled, big_blobs=True, with_decode_path=True)
obj_expled_encoded = obj_expled.encode()
+ ctx_dummy["keep_memoryview"] = keep_memoryview
ctx_copied = deepcopy(ctx_dummy)
obj_decoded, tail = obj_expled.decode(
obj_expled_encoded + tail_junk,
self.assertEqual(obj_decoded, obj_expled)
self.assertEqual(bytes(obj_decoded), bytes(obj_expled))
self.assertEqual(bytes(obj_decoded), bytes(obj))
+ self.assertIsInstance(
+ obj_decoded._value,
+ memoryview if keep_memoryview else bytes,
+ )
self.assertSequenceEqual(obj_decoded.encode(), obj_expled_encoded)
self.assertSequenceEqual(obj_decoded.expl_tag, tag_expl)
self.assertEqual(obj_decoded.expl_tlen, len(tag_expl))
schema = Boolean()
bound_min = d.draw(integers(min_value=1, max_value=1 << 7))
bound_max = d.draw(integers(min_value=bound_min, max_value=1 << 7))
- value = [Boolean(False)] * d.draw(integers(max_value=bound_min - 1))
+ value = [Boolean(False)] * d.draw(integers(min_value=0, max_value=bound_min - 1))
with self.assertRaises(BoundsError) as err:
SeqOf(value=value, bounds=(bound_min, bound_max))
repr(err.exception)
def test_oid_printing(self, d):
oids = {
str(ObjectIdentifier(k)): v * 2
- for k, v in d.draw(dictionaries(oid_strategy(), text_letters())).items()
+ for k, v in d.draw(dictionaries(
+ oid_strategy(),
+ text_letters(),
+ min_size=1,
+ )).items()
}
chosen = d.draw(sampled_from(sorted(oids)))
chosen_id = oids[chosen]