From 8c1812c389d97869f3feee514f408f79cb828010 Mon Sep 17 00:00:00 2001 From: Facundo Tuesca Date: Wed, 24 Sep 2025 02:09:19 +0200 Subject: [PATCH 1/8] asn1: Add support for PrintableString Signed-off-by: Facundo Tuesca --- src/cryptography/hazmat/asn1/__init__.py | 8 +++- src/cryptography/hazmat/asn1/asn1.py | 3 ++ .../bindings/_rust/declarative_asn1.pyi | 4 ++ src/rust/src/declarative_asn1/decode.rs | 12 +++++- src/rust/src/declarative_asn1/encode.rs | 16 +++++++- src/rust/src/declarative_asn1/types.rs | 27 ++++++++++++- src/rust/src/lib.rs | 2 +- tests/hazmat/asn1/test_serialization.py | 38 ++++++++++++++++++- 8 files changed, 103 insertions(+), 7 deletions(-) diff --git a/src/cryptography/hazmat/asn1/__init__.py b/src/cryptography/hazmat/asn1/__init__.py index 5b4bc48a4ee2..1126d14c10a9 100644 --- a/src/cryptography/hazmat/asn1/__init__.py +++ b/src/cryptography/hazmat/asn1/__init__.py @@ -2,9 +2,15 @@ # 2.0, and the BSD License. See the LICENSE file in the root of this repository # for complete details. -from cryptography.hazmat.asn1.asn1 import decode_der, encode_der, sequence +from cryptography.hazmat.asn1.asn1 import ( + PrintableString, + decode_der, + encode_der, + sequence, +) __all__ = [ + "PrintableString", "decode_der", "encode_der", "sequence", diff --git a/src/cryptography/hazmat/asn1/asn1.py b/src/cryptography/hazmat/asn1/asn1.py index b8fb8509995b..a5cd7a6074ff 100644 --- a/src/cryptography/hazmat/asn1/asn1.py +++ b/src/cryptography/hazmat/asn1/asn1.py @@ -115,3 +115,6 @@ def sequence(cls: type[U]) -> type[U]: )(cls) _register_asn1_sequence(dataclass_cls) return dataclass_cls + + +PrintableString = declarative_asn1.PrintableString diff --git a/src/cryptography/hazmat/bindings/_rust/declarative_asn1.pyi b/src/cryptography/hazmat/bindings/_rust/declarative_asn1.pyi index 5bcc9bd49f1f..bb2c2b0c2d0b 100644 --- a/src/cryptography/hazmat/bindings/_rust/declarative_asn1.pyi +++ b/src/cryptography/hazmat/bindings/_rust/declarative_asn1.pyi @@ -34,3 +34,7 @@ class AnnotatedTypeObject: def __new__( cls, annotated_type: AnnotatedType, value: typing.Any ) -> AnnotatedTypeObject: ... + +class PrintableString: + def __new__(cls, inner: str) -> PrintableString: ... + def as_str(self) -> str: ... diff --git a/src/rust/src/declarative_asn1/decode.rs b/src/rust/src/declarative_asn1/decode.rs index d851dd43c67c..e13dfb9a8f5c 100644 --- a/src/rust/src/declarative_asn1/decode.rs +++ b/src/rust/src/declarative_asn1/decode.rs @@ -6,7 +6,7 @@ use asn1::Parser; use pyo3::types::PyAnyMethods; use crate::asn1::big_byte_slice_to_py_int; -use crate::declarative_asn1::types::{AnnotatedType, Type}; +use crate::declarative_asn1::types::{AnnotatedType, PrintableString, Type}; use crate::error::CryptographyError; type ParseResult = Result; @@ -48,6 +48,15 @@ fn decode_pystr<'a>( Ok(pyo3::types::PyString::new(py, value.as_str())) } +fn decode_printable_string<'a>( + py: pyo3::Python<'a>, + parser: &mut Parser<'a>, +) -> ParseResult> { + let value = parser.read_element::>()?.as_str(); + let inner = pyo3::types::PyString::new(py, value).unbind(); + Ok(pyo3::Bound::new(py, PrintableString { inner })?) +} + pub(crate) fn decode_annotated_type<'a>( py: pyo3::Python<'a>, parser: &mut Parser<'a>, @@ -78,5 +87,6 @@ pub(crate) fn decode_annotated_type<'a>( Type::PyInt() => Ok(decode_pyint(py, parser)?.into_any()), Type::PyBytes() => Ok(decode_pybytes(py, parser)?.into_any()), Type::PyStr() => Ok(decode_pystr(py, parser)?.into_any()), + Type::PrintableString() => Ok(decode_printable_string(py, parser)?.into_any()), } } diff --git a/src/rust/src/declarative_asn1/encode.rs b/src/rust/src/declarative_asn1/encode.rs index d9b5b19f4905..4df7bbb57311 100644 --- a/src/rust/src/declarative_asn1/encode.rs +++ b/src/rust/src/declarative_asn1/encode.rs @@ -5,7 +5,7 @@ use asn1::{SimpleAsn1Writable, Writer}; use pyo3::types::PyAnyMethods; -use crate::declarative_asn1::types::{AnnotatedType, AnnotatedTypeObject, Type}; +use crate::declarative_asn1::types::{AnnotatedType, AnnotatedTypeObject, PrintableString, Type}; fn write_value( writer: &mut Writer<'_>, @@ -73,6 +73,20 @@ impl asn1::Asn1Writable for AnnotatedTypeObject<'_> { let asn1_string: asn1::Utf8String<'_> = asn1::Utf8String::new(&val); write_value(writer, &asn1_string) } + Type::PrintableString() => { + let val: &pyo3::Bound<'_, PrintableString> = value + .downcast() + .map_err(|_| asn1::WriteError::AllocationError)?; + let inner_str: pyo3::pybacked::PyBackedStr = val + .get() + .inner + .extract(py) + .map_err(|_| asn1::WriteError::AllocationError)?; + let printable_string: asn1::PrintableString<'_> = + asn1::PrintableString::new(&inner_str) + .ok_or(asn1::WriteError::AllocationError)?; + write_value(writer, &printable_string) + } } } } diff --git a/src/rust/src/declarative_asn1/types.rs b/src/rust/src/declarative_asn1/types.rs index e3fc9d3d5fca..0a561830af61 100644 --- a/src/rust/src/declarative_asn1/types.rs +++ b/src/rust/src/declarative_asn1/types.rs @@ -32,6 +32,9 @@ pub enum Type { /// `str` -> `UTF8String` #[pyo3(constructor = ())] PyStr(), + /// PrintableString (`str`) + #[pyo3(constructor = ())] + PrintableString(), } /// A type that we know how to encode/decode, along with any @@ -70,6 +73,26 @@ impl Annotation { } } +#[derive(pyo3::FromPyObject)] +#[pyo3::pyclass(frozen, module = "cryptography.hazmat.bindings._rust.asn1")] +pub struct PrintableString { + pub(crate) inner: pyo3::Py, +} + +#[pyo3::pymethods] +impl PrintableString { + #[new] + #[pyo3(signature = (inner,))] + fn new(inner: pyo3::Py) -> Self { + PrintableString { inner } + } + + #[pyo3(signature = ())] + pub fn as_str(&self, py: pyo3::Python<'_>) -> pyo3::PyResult> { + Ok(self.inner.clone_ref(py)) + } +} + /// Utility function for converting builtin Python types /// to their Rust `Type` equivalent. #[pyo3::pyfunction] @@ -85,6 +108,8 @@ pub fn non_root_python_to_rust<'p>( Type::PyStr().into_pyobject(py) } else if class.is(pyo3::types::PyBytes::type_object(py)) { Type::PyBytes().into_pyobject(py) + } else if class.is(PrintableString::type_object(py)) { + Type::PrintableString().into_pyobject(py) } else { Err(pyo3::exceptions::PyTypeError::new_err(format!( "cannot handle type: {class:?}" @@ -131,5 +156,5 @@ pub(crate) fn python_class_to_annotated<'p>( #[pyo3::pymodule(gil_used = false)] pub(crate) mod types { #[pymodule_export] - use super::{AnnotatedType, Annotation, Type}; + use super::{AnnotatedType, Annotation, PrintableString, Type}; } diff --git a/src/rust/src/lib.rs b/src/rust/src/lib.rs index 9bd62a740f6e..6286046bf419 100644 --- a/src/rust/src/lib.rs +++ b/src/rust/src/lib.rs @@ -124,7 +124,7 @@ mod _rust { #[pymodule_export] use crate::declarative_asn1::types::{ - non_root_python_to_rust, AnnotatedType, Annotation, Type, + non_root_python_to_rust, AnnotatedType, Annotation, PrintableString, Type, }; } diff --git a/tests/hazmat/asn1/test_serialization.py b/tests/hazmat/asn1/test_serialization.py index 80fed3706d2c..483bcf4d349d 100644 --- a/tests/hazmat/asn1/test_serialization.py +++ b/tests/hazmat/asn1/test_serialization.py @@ -6,6 +6,8 @@ import sys import typing +import pytest + import cryptography.hazmat.asn1 as asn1 U = typing.TypeVar("U") @@ -34,8 +36,15 @@ def _comparable_dataclass(cls: typing.Type[U]) -> typing.Type[U]: )(cls) +# Checks that the encoding-decoding roundtrip results +# in the expected values and is consistent. +# +# The `decoded_eq` argument is the equality function to use +# for the decoded values. It's useful for types that aren't +# directly comparable, like `PrintableString`. def assert_roundtrips( - test_cases: typing.List[typing.Tuple[typing.Any, bytes]], + test_cases: typing.List[typing.Tuple[U, bytes]], + decoded_eq: typing.Optional[typing.Callable[[U, U], bool]] = None, ) -> None: for obj, obj_bytes in test_cases: encoded = asn1.encode_der(obj) @@ -43,7 +52,10 @@ def assert_roundtrips( decoded = asn1.decode_der(type(obj), encoded) assert isinstance(decoded, type(obj)) - assert decoded == obj + if decoded_eq: + assert decoded_eq(decoded, obj) + else: + assert decoded == obj class TestBool: @@ -105,6 +117,28 @@ def test_string(self) -> None: ) +class TestPrintableString: + def test_ok_printable_string(self) -> None: + def decoded_eq(a: asn1.PrintableString, b: asn1.PrintableString): + return a.as_str() == b.as_str() + + assert_roundtrips( + [ + (asn1.PrintableString(""), b"\x13\x00"), + (asn1.PrintableString("hello"), b"\x13\x05hello"), + (asn1.PrintableString("Test User 1"), b"\x13\x0bTest User 1"), + ], + decoded_eq, + ) + + def test_invalid_printable_string(self) -> None: + with pytest.raises(ValueError, match="allocation error"): + asn1.encode_der(asn1.PrintableString("café")) + + with pytest.raises(ValueError, match="error parsing asn1 value"): + asn1.decode_der(asn1.PrintableString, b"\x0c\x05caf\xc3\xa9") + + class TestSequence: def test_ok_sequence_single_field(self) -> None: @asn1.sequence From 14a19a7941cfcac101d6ca4e874c5552d1feddf0 Mon Sep 17 00:00:00 2001 From: Facundo Tuesca Date: Wed, 24 Sep 2025 03:07:08 +0200 Subject: [PATCH 2/8] Add __repr__ to PrintableString Signed-off-by: Facundo Tuesca --- src/rust/src/declarative_asn1/types.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/rust/src/declarative_asn1/types.rs b/src/rust/src/declarative_asn1/types.rs index 0a561830af61..3368ff624c47 100644 --- a/src/rust/src/declarative_asn1/types.rs +++ b/src/rust/src/declarative_asn1/types.rs @@ -91,6 +91,10 @@ impl PrintableString { pub fn as_str(&self, py: pyo3::Python<'_>) -> pyo3::PyResult> { Ok(self.inner.clone_ref(py)) } + + pub fn __repr__(&self) -> String { + format!("PrintableString({})", self.inner) + } } /// Utility function for converting builtin Python types From 71c058639aa51724688ce5a9a8459952d9db4ac5 Mon Sep 17 00:00:00 2001 From: Facundo Tuesca Date: Wed, 24 Sep 2025 03:07:25 +0200 Subject: [PATCH 3/8] Remove PyBackedStr usage Signed-off-by: Facundo Tuesca --- src/rust/src/declarative_asn1/encode.rs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/rust/src/declarative_asn1/encode.rs b/src/rust/src/declarative_asn1/encode.rs index 4df7bbb57311..5752f37bf7a5 100644 --- a/src/rust/src/declarative_asn1/encode.rs +++ b/src/rust/src/declarative_asn1/encode.rs @@ -3,7 +3,7 @@ // for complete details. use asn1::{SimpleAsn1Writable, Writer}; -use pyo3::types::PyAnyMethods; +use pyo3::types::{PyAnyMethods, PyStringMethods}; use crate::declarative_asn1::types::{AnnotatedType, AnnotatedTypeObject, PrintableString, Type}; @@ -77,13 +77,14 @@ impl asn1::Asn1Writable for AnnotatedTypeObject<'_> { let val: &pyo3::Bound<'_, PrintableString> = value .downcast() .map_err(|_| asn1::WriteError::AllocationError)?; - let inner_str: pyo3::pybacked::PyBackedStr = val + let inner_str = val .get() .inner - .extract(py) + .bind(py) + .to_str() .map_err(|_| asn1::WriteError::AllocationError)?; let printable_string: asn1::PrintableString<'_> = - asn1::PrintableString::new(&inner_str) + asn1::PrintableString::new(inner_str) .ok_or(asn1::WriteError::AllocationError)?; write_value(writer, &printable_string) } From bf37c1089d345ffe6904c8f69e44f88075e800fe Mon Sep 17 00:00:00 2001 From: Facundo Tuesca Date: Fri, 26 Sep 2025 00:25:36 +0200 Subject: [PATCH 4/8] Add test for PrintableString's __repr__ Signed-off-by: Facundo Tuesca --- tests/hazmat/asn1/test_api.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/tests/hazmat/asn1/test_api.py b/tests/hazmat/asn1/test_api.py index 23aaaddfca58..d187fbeed78b 100644 --- a/tests/hazmat/asn1/test_api.py +++ b/tests/hazmat/asn1/test_api.py @@ -9,7 +9,15 @@ import cryptography.hazmat.asn1 as asn1 -class TestClassAPI: +class TestTypesAPI: + def test_repr_printable_string(self) -> None: + assert ( + repr(asn1.PrintableString("MyString")) + == "PrintableString(MyString)" + ) + + +class TestSequenceAPI: def test_fail_unsupported_field(self) -> None: # Not a sequence class Unsupported: From 73b85419ef28b38f10b708af0eb835ffce71f3b7 Mon Sep 17 00:00:00 2001 From: Facundo Tuesca Date: Fri, 26 Sep 2025 00:32:00 +0200 Subject: [PATCH 5/8] Use `PyStringMethods::to_cow` instead of `to_str` Signed-off-by: Facundo Tuesca --- src/rust/src/declarative_asn1/encode.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/rust/src/declarative_asn1/encode.rs b/src/rust/src/declarative_asn1/encode.rs index 5752f37bf7a5..e1707e443672 100644 --- a/src/rust/src/declarative_asn1/encode.rs +++ b/src/rust/src/declarative_asn1/encode.rs @@ -81,10 +81,10 @@ impl asn1::Asn1Writable for AnnotatedTypeObject<'_> { .get() .inner .bind(py) - .to_str() + .to_cow() .map_err(|_| asn1::WriteError::AllocationError)?; let printable_string: asn1::PrintableString<'_> = - asn1::PrintableString::new(inner_str) + asn1::PrintableString::new(&inner_str) .ok_or(asn1::WriteError::AllocationError)?; write_value(writer, &printable_string) } From 3b22b142f3efa994c6c4905f5cdf8f0482834bb4 Mon Sep 17 00:00:00 2001 From: Facundo Tuesca Date: Fri, 26 Sep 2025 00:37:42 +0200 Subject: [PATCH 6/8] Remove unneeded `bind` call Signed-off-by: Facundo Tuesca --- src/rust/src/declarative_asn1/encode.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/rust/src/declarative_asn1/encode.rs b/src/rust/src/declarative_asn1/encode.rs index e1707e443672..3e3afba57fb8 100644 --- a/src/rust/src/declarative_asn1/encode.rs +++ b/src/rust/src/declarative_asn1/encode.rs @@ -3,7 +3,7 @@ // for complete details. use asn1::{SimpleAsn1Writable, Writer}; -use pyo3::types::{PyAnyMethods, PyStringMethods}; +use pyo3::types::PyAnyMethods; use crate::declarative_asn1::types::{AnnotatedType, AnnotatedTypeObject, PrintableString, Type}; @@ -80,8 +80,7 @@ impl asn1::Asn1Writable for AnnotatedTypeObject<'_> { let inner_str = val .get() .inner - .bind(py) - .to_cow() + .to_cow(py) .map_err(|_| asn1::WriteError::AllocationError)?; let printable_string: asn1::PrintableString<'_> = asn1::PrintableString::new(&inner_str) From 3e968d778285b1b26a473d6736ea04d752ef4d9f Mon Sep 17 00:00:00 2001 From: Facundo Tuesca Date: Fri, 26 Sep 2025 01:16:54 +0200 Subject: [PATCH 7/8] Address review comments Signed-off-by: Facundo Tuesca --- .../hazmat/bindings/_rust/declarative_asn1.pyi | 2 ++ src/rust/src/declarative_asn1/types.rs | 8 ++++++-- tests/hazmat/asn1/test_api.py | 5 +++-- tests/hazmat/asn1/test_serialization.py | 16 ++-------------- 4 files changed, 13 insertions(+), 18 deletions(-) diff --git a/src/cryptography/hazmat/bindings/_rust/declarative_asn1.pyi b/src/cryptography/hazmat/bindings/_rust/declarative_asn1.pyi index bb2c2b0c2d0b..e4b2a99f5864 100644 --- a/src/cryptography/hazmat/bindings/_rust/declarative_asn1.pyi +++ b/src/cryptography/hazmat/bindings/_rust/declarative_asn1.pyi @@ -34,6 +34,8 @@ class AnnotatedTypeObject: def __new__( cls, annotated_type: AnnotatedType, value: typing.Any ) -> AnnotatedTypeObject: ... + def __repr__(self) -> str: ... + def __eq__(self, other: object) -> bool: ... class PrintableString: def __new__(cls, inner: str) -> PrintableString: ... diff --git a/src/rust/src/declarative_asn1/types.rs b/src/rust/src/declarative_asn1/types.rs index 3368ff624c47..b0dae3b6ca8f 100644 --- a/src/rust/src/declarative_asn1/types.rs +++ b/src/rust/src/declarative_asn1/types.rs @@ -92,8 +92,12 @@ impl PrintableString { Ok(self.inner.clone_ref(py)) } - pub fn __repr__(&self) -> String { - format!("PrintableString({})", self.inner) + fn __eq__(&self, py: pyo3::Python<'_>, other: pyo3::PyRef<'_, Self>) -> pyo3::PyResult { + (**self.inner.bind(py)).eq(other.inner.bind(py)) + } + + pub fn __repr__(&self, py: pyo3::Python<'_>) -> pyo3::PyResult { + Ok(format!("PrintableString({})", self.inner.bind(py).repr()?)) } } diff --git a/tests/hazmat/asn1/test_api.py b/tests/hazmat/asn1/test_api.py index d187fbeed78b..0cb147f22d31 100644 --- a/tests/hazmat/asn1/test_api.py +++ b/tests/hazmat/asn1/test_api.py @@ -11,9 +11,10 @@ class TestTypesAPI: def test_repr_printable_string(self) -> None: + my_string = "MyString" assert ( - repr(asn1.PrintableString("MyString")) - == "PrintableString(MyString)" + repr(asn1.PrintableString(my_string)) + == f"PrintableString({my_string!r})" ) diff --git a/tests/hazmat/asn1/test_serialization.py b/tests/hazmat/asn1/test_serialization.py index 483bcf4d349d..5763dd5193ef 100644 --- a/tests/hazmat/asn1/test_serialization.py +++ b/tests/hazmat/asn1/test_serialization.py @@ -38,13 +38,8 @@ def _comparable_dataclass(cls: typing.Type[U]) -> typing.Type[U]: # Checks that the encoding-decoding roundtrip results # in the expected values and is consistent. -# -# The `decoded_eq` argument is the equality function to use -# for the decoded values. It's useful for types that aren't -# directly comparable, like `PrintableString`. def assert_roundtrips( test_cases: typing.List[typing.Tuple[U, bytes]], - decoded_eq: typing.Optional[typing.Callable[[U, U], bool]] = None, ) -> None: for obj, obj_bytes in test_cases: encoded = asn1.encode_der(obj) @@ -52,10 +47,7 @@ def assert_roundtrips( decoded = asn1.decode_der(type(obj), encoded) assert isinstance(decoded, type(obj)) - if decoded_eq: - assert decoded_eq(decoded, obj) - else: - assert decoded == obj + assert decoded == obj class TestBool: @@ -119,16 +111,12 @@ def test_string(self) -> None: class TestPrintableString: def test_ok_printable_string(self) -> None: - def decoded_eq(a: asn1.PrintableString, b: asn1.PrintableString): - return a.as_str() == b.as_str() - assert_roundtrips( [ (asn1.PrintableString(""), b"\x13\x00"), (asn1.PrintableString("hello"), b"\x13\x05hello"), (asn1.PrintableString("Test User 1"), b"\x13\x0bTest User 1"), - ], - decoded_eq, + ] ) def test_invalid_printable_string(self) -> None: From 7887f9bfc2f40bb0b9f0bc7d99adb7b2bef50b9a Mon Sep 17 00:00:00 2001 From: Facundo Tuesca Date: Fri, 26 Sep 2025 01:37:01 +0200 Subject: [PATCH 8/8] Check PrintableString validity during __new__ Signed-off-by: Facundo Tuesca --- src/rust/src/declarative_asn1/types.rs | 11 +++++++++-- tests/hazmat/asn1/test_api.py | 4 ++++ tests/hazmat/asn1/test_serialization.py | 9 --------- 3 files changed, 13 insertions(+), 11 deletions(-) diff --git a/src/rust/src/declarative_asn1/types.rs b/src/rust/src/declarative_asn1/types.rs index b0dae3b6ca8f..9be3e2d8a576 100644 --- a/src/rust/src/declarative_asn1/types.rs +++ b/src/rust/src/declarative_asn1/types.rs @@ -2,6 +2,7 @@ // 2.0, and the BSD License. See the LICENSE file in the root of this repository // for complete details. +use asn1::PrintableString as Asn1PrintableString; use pyo3::types::PyAnyMethods; use pyo3::{IntoPyObject, PyTypeInfo}; @@ -83,8 +84,14 @@ pub struct PrintableString { impl PrintableString { #[new] #[pyo3(signature = (inner,))] - fn new(inner: pyo3::Py) -> Self { - PrintableString { inner } + fn new(py: pyo3::Python<'_>, inner: pyo3::Py) -> pyo3::PyResult { + if Asn1PrintableString::new(&inner.to_cow(py)?).is_none() { + return Err(pyo3::exceptions::PyValueError::new_err(format!( + "invalid PrintableString: {inner}" + ))); + } + + Ok(PrintableString { inner }) } #[pyo3(signature = ())] diff --git a/tests/hazmat/asn1/test_api.py b/tests/hazmat/asn1/test_api.py index 0cb147f22d31..c9e1ed50e9ac 100644 --- a/tests/hazmat/asn1/test_api.py +++ b/tests/hazmat/asn1/test_api.py @@ -17,6 +17,10 @@ def test_repr_printable_string(self) -> None: == f"PrintableString({my_string!r})" ) + def test_invalid_printable_string(self) -> None: + with pytest.raises(ValueError, match="invalid PrintableString: café"): + asn1.PrintableString("café") + class TestSequenceAPI: def test_fail_unsupported_field(self) -> None: diff --git a/tests/hazmat/asn1/test_serialization.py b/tests/hazmat/asn1/test_serialization.py index 5763dd5193ef..b88cc9b5d5bc 100644 --- a/tests/hazmat/asn1/test_serialization.py +++ b/tests/hazmat/asn1/test_serialization.py @@ -6,8 +6,6 @@ import sys import typing -import pytest - import cryptography.hazmat.asn1 as asn1 U = typing.TypeVar("U") @@ -119,13 +117,6 @@ def test_ok_printable_string(self) -> None: ] ) - def test_invalid_printable_string(self) -> None: - with pytest.raises(ValueError, match="allocation error"): - asn1.encode_der(asn1.PrintableString("café")) - - with pytest.raises(ValueError, match="error parsing asn1 value"): - asn1.decode_der(asn1.PrintableString, b"\x0c\x05caf\xc3\xa9") - class TestSequence: def test_ok_sequence_single_field(self) -> None: