-
Notifications
You must be signed in to change notification settings - Fork 1.7k
asn1: Add support for PrintableString
#13496
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 1 commit
8c1812c
14a19a7
71c0586
bf37c10
73b8541
3b22b14
3e968d7
7887f9b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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::types::PyString>, | ||
| } | ||
|
|
||
| #[pyo3::pymethods] | ||
| impl PrintableString { | ||
| #[new] | ||
| #[pyo3(signature = (inner,))] | ||
| fn new(inner: pyo3::Py<pyo3::types::PyString>) -> Self { | ||
|
||
| PrintableString { inner } | ||
| } | ||
|
|
||
| #[pyo3(signature = ())] | ||
| pub fn as_str(&self, py: pyo3::Python<'_>) -> pyo3::PyResult<pyo3::Py<pyo3::types::PyString>> { | ||
| Ok(self.inner.clone_ref(py)) | ||
| } | ||
alex marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| } | ||
|
|
||
| /// 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}; | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -6,6 +6,8 @@ | |
| import sys | ||
| import typing | ||
|
|
||
| import pytest | ||
|
|
||
| import cryptography.hazmat.asn1 as asn1 | ||
|
|
||
| U = typing.TypeVar("U") | ||
|
|
@@ -34,16 +36,26 @@ 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) | ||
| assert encoded == obj_bytes | ||
|
|
||
| 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 | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We shouldn't need a pybacked string,
val.get().inner.bind(py)should be all we need.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fixed