diff --git a/src/cryptography/hazmat/backends/openssl/decode_asn1.py b/src/cryptography/hazmat/backends/openssl/decode_asn1.py index e62c25406b39e..6e4dc0e939d19 100644 --- a/src/cryptography/hazmat/backends/openssl/decode_asn1.py +++ b/src/cryptography/hazmat/backends/openssl/decode_asn1.py @@ -8,8 +8,7 @@ import typing from cryptography import x509 -from cryptography.hazmat._der import DERReader, INTEGER, NULL, SEQUENCE -from cryptography.x509.extensions import _TLS_FEATURE_TYPE_TO_ENUM +from cryptography.hazmat.bindings._rust import asn1 from cryptography.x509.name import _ASN1_TYPE_TO_ENUM from cryptography.x509.oid import ( CRLEntryExtensionOID, @@ -211,25 +210,17 @@ def parse(self, x509_obj): # The extension contents are a SEQUENCE OF INTEGERs. data = self._backend._lib.X509_EXTENSION_get_data(ext) data_bytes = _asn1_string_to_bytes(self._backend, data) - features = DERReader(data_bytes).read_single_element(SEQUENCE) - parsed = [] - while not features.is_empty(): - parsed.append(features.read_element(INTEGER).as_integer()) - # Map the features to their enum value. - value = x509.TLSFeature( - [_TLS_FEATURE_TYPE_TO_ENUM[x] for x in parsed] - ) + value = asn1.parse_tls_feature(data_bytes) + extensions.append(x509.Extension(oid, critical, value)) seen_oids.add(oid) continue elif oid == ExtensionOID.PRECERT_POISON: data = self._backend._lib.X509_EXTENSION_get_data(ext) - # The contents of the extension must be an ASN.1 NULL. - reader = DERReader(_asn1_string_to_bytes(self._backend, data)) - reader.read_single_element(NULL).check_empty() - extensions.append( - x509.Extension(oid, critical, x509.PrecertPoison()) - ) + data_bytes = _asn1_string_to_bytes(self._backend, data) + value = asn1.parse_precert_poison(data_bytes) + + extensions.append(x509.Extension(oid, critical, value)) seen_oids.add(oid) continue diff --git a/src/rust/Cargo.lock b/src/rust/Cargo.lock index 83fd7eb8f48bb..150c7ffc16c9a 100644 --- a/src/rust/Cargo.lock +++ b/src/rust/Cargo.lock @@ -1,5 +1,21 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. +version = 3 + +[[package]] +name = "asn1" +version = "0.3.0" +source = "git+https://github.com/alex/rust-asn1#86121b4023376502ca90bc6514c407a2b2cfbf37" +dependencies = [ + "chrono", +] + +[[package]] +name = "autocfg" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" + [[package]] name = "bitflags" version = "1.2.1" @@ -12,10 +28,21 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "chrono" +version = "0.4.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73" +dependencies = [ + "num-integer", + "num-traits", +] + [[package]] name = "cryptography-rust" version = "0.1.0" dependencies = [ + "asn1", "pyo3", ] @@ -109,6 +136,25 @@ dependencies = [ "scopeguard", ] +[[package]] +name = "num-integer" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db" +dependencies = [ + "autocfg", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290" +dependencies = [ + "autocfg", +] + [[package]] name = "parking_lot" version = "0.11.1" diff --git a/src/rust/Cargo.toml b/src/rust/Cargo.toml index bcb9add100205..88fa07ad34885 100644 --- a/src/rust/Cargo.toml +++ b/src/rust/Cargo.toml @@ -7,6 +7,7 @@ publish = false [dependencies] pyo3 = { version = "0.13.1", features = ["extension-module"] } +asn1 = { git = "https://github.com/alex/rust-asn1", default-features = false } [lib] name = "cryptography_rust" diff --git a/src/rust/src/asn1.rs b/src/rust/src/asn1.rs new file mode 100644 index 0000000000000..57fefd9d71b34 --- /dev/null +++ b/src/rust/src/asn1.rs @@ -0,0 +1,75 @@ +// This file is dual licensed under the terms of the Apache License, Version +// 2.0, and the BSD License. See the LICENSE file in the root of this repository +// for complete details. + +use pyo3::conversion::ToPyObject; + +enum PyAsn1Error { + Asn1(asn1::ParseError), + Py(pyo3::PyErr), +} + +impl From for PyAsn1Error { + fn from(e: asn1::ParseError) -> PyAsn1Error { + PyAsn1Error::Asn1(e) + } +} + +impl From for PyAsn1Error { + fn from(e: pyo3::PyErr) -> PyAsn1Error { + PyAsn1Error::Py(e) + } +} + +impl From for pyo3::PyErr { + fn from(e: PyAsn1Error) -> pyo3::PyErr { + match e { + PyAsn1Error::Asn1(asn1_error) => pyo3::exceptions::PyValueError::new_err(format!( + "error parsing asn1 value: {:?}", + asn1_error + )), + PyAsn1Error::Py(py_error) => py_error, + } + } +} + +#[pyo3::prelude::pyfunction] +fn parse_tls_feature(py: pyo3::Python<'_>, data: &[u8]) -> pyo3::PyResult { + let tls_feature_type_to_enum = py + .import("cryptography.x509.extensions")? + .getattr("_TLS_FEATURE_TYPE_TO_ENUM")?; + + let features = asn1::parse::<_, PyAsn1Error, _>(data, |p| { + let features = pyo3::types::PyList::empty(py); + for el in p.read_element::()?.parse::() { + let feature = el?; + let py_feature = tls_feature_type_to_enum.get_item(feature.to_object(py))?; + features.append(py_feature)? + } + Ok(features) + })?; + + let x509_module = py.import("cryptography.x509")?; + x509_module + .call1("TLSFeature", (features,)) + .map(|o| o.to_object(py)) +} + +#[pyo3::prelude::pyfunction] +fn parse_precert_poison(py: pyo3::Python<'_>, data: &[u8]) -> pyo3::PyResult { + asn1::parse::<_, PyAsn1Error, _>(data, |p| { + p.read_element::<()>()?; + Ok(()) + })?; + + let x509_module = py.import("cryptography.x509")?; + x509_module.call0("PrecertPoison").map(|o| o.to_object(py)) +} + +pub(crate) fn create_submodule(py: pyo3::Python) -> pyo3::PyResult<&pyo3::prelude::PyModule> { + let submod = pyo3::prelude::PyModule::new(py, "asn1")?; + submod.add_wrapped(pyo3::wrap_pyfunction!(parse_tls_feature))?; + submod.add_wrapped(pyo3::wrap_pyfunction!(parse_precert_poison))?; + + Ok(submod) +} diff --git a/src/rust/src/lib.rs b/src/rust/src/lib.rs index 3257b35e123f2..a3b3fb7151411 100644 --- a/src/rust/src/lib.rs +++ b/src/rust/src/lib.rs @@ -2,6 +2,8 @@ // 2.0, and the BSD License. See the LICENSE file in the root of this repository // for complete details. +mod asn1; + use std::convert::TryInto; /// Returns the value of the input with the most-significant-bit copied to all @@ -66,10 +68,12 @@ fn check_ansix923_padding(data: &[u8]) -> bool { } #[pyo3::prelude::pymodule] -fn _rust(_py: pyo3::Python<'_>, m: &pyo3::types::PyModule) -> pyo3::PyResult<()> { +fn _rust(py: pyo3::Python<'_>, m: &pyo3::types::PyModule) -> pyo3::PyResult<()> { m.add_function(pyo3::wrap_pyfunction!(check_pkcs7_padding, m)?)?; m.add_function(pyo3::wrap_pyfunction!(check_ansix923_padding, m)?)?; + m.add_submodule(asn1::create_submodule(py)?)?; + Ok(()) }