From 3dba58343f0aae96d25734c6c933162ddb2110d4 Mon Sep 17 00:00:00 2001 From: Facundo Tuesca Date: Mon, 22 Jan 2024 12:36:03 +0100 Subject: [PATCH] Migrate PKCS7 backend to Rust --- .../hazmat/backends/openssl/backend.py | 55 ---------------- .../hazmat/bindings/_rust/pkcs7.pyi | 6 ++ .../hazmat/primitives/serialization/pkcs7.py | 10 +-- src/rust/cryptography-openssl/Cargo.toml | 2 +- src/rust/src/pkcs7.rs | 62 ++++++++++++++++++- src/rust/src/x509/certificate.rs | 2 +- 6 files changed, 70 insertions(+), 67 deletions(-) diff --git a/src/cryptography/hazmat/backends/openssl/backend.py b/src/cryptography/hazmat/backends/openssl/backend.py index 1cb68c33ac742..b836743f85d9c 100644 --- a/src/cryptography/hazmat/backends/openssl/backend.py +++ b/src/cryptography/hazmat/backends/openssl/backend.py @@ -863,61 +863,6 @@ def poly1305_supported(self) -> bool: def pkcs7_supported(self) -> bool: return not self._lib.CRYPTOGRAPHY_IS_BORINGSSL - def load_pem_pkcs7_certificates( - self, data: bytes - ) -> list[x509.Certificate]: - utils._check_bytes("data", data) - bio = self._bytes_to_bio(data) - p7 = self._lib.PEM_read_bio_PKCS7( - bio.bio, self._ffi.NULL, self._ffi.NULL, self._ffi.NULL - ) - if p7 == self._ffi.NULL: - self._consume_errors() - raise ValueError("Unable to parse PKCS7 data") - - p7 = self._ffi.gc(p7, self._lib.PKCS7_free) - return self._load_pkcs7_certificates(p7) - - def load_der_pkcs7_certificates( - self, data: bytes - ) -> list[x509.Certificate]: - utils._check_bytes("data", data) - bio = self._bytes_to_bio(data) - p7 = self._lib.d2i_PKCS7_bio(bio.bio, self._ffi.NULL) - if p7 == self._ffi.NULL: - self._consume_errors() - raise ValueError("Unable to parse PKCS7 data") - - p7 = self._ffi.gc(p7, self._lib.PKCS7_free) - return self._load_pkcs7_certificates(p7) - - def _load_pkcs7_certificates(self, p7) -> list[x509.Certificate]: - nid = self._lib.OBJ_obj2nid(p7.type) - self.openssl_assert(nid != self._lib.NID_undef) - if nid != self._lib.NID_pkcs7_signed: - raise UnsupportedAlgorithm( - "Only basic signed structures are currently supported. NID" - f" for this data was {nid}", - _Reasons.UNSUPPORTED_SERIALIZATION, - ) - - if p7.d.sign == self._ffi.NULL: - raise ValueError( - "The provided PKCS7 has no certificate data, but a cert " - "loading method was called." - ) - - sk_x509 = p7.d.sign.cert - num = self._lib.sk_X509_num(sk_x509) - certs: list[x509.Certificate] = [] - for i in range(num): - x509 = self._lib.sk_X509_value(sk_x509, i) - self.openssl_assert(x509 != self._ffi.NULL) - cert = self._ossl2cert(x509) - certs.append(cert) - - return certs - class GetCipherByName: def __init__(self, fmt: str): diff --git a/src/cryptography/hazmat/bindings/_rust/pkcs7.pyi b/src/cryptography/hazmat/bindings/_rust/pkcs7.pyi index 32c21c4c54397..883dd54389dd2 100644 --- a/src/cryptography/hazmat/bindings/_rust/pkcs7.pyi +++ b/src/cryptography/hazmat/bindings/_rust/pkcs7.pyi @@ -13,3 +13,9 @@ def sign_and_serialize( encoding: serialization.Encoding, options: typing.Iterable[pkcs7.PKCS7Options], ) -> bytes: ... +def load_pem_pkcs7_certificates( + data: bytes, +) -> list[x509.Certificate]: ... +def load_der_pkcs7_certificates( + data: bytes, +) -> list[x509.Certificate]: ... \ No newline at end of file diff --git a/src/cryptography/hazmat/primitives/serialization/pkcs7.py b/src/cryptography/hazmat/primitives/serialization/pkcs7.py index cd6c904df0eaf..20289f4e183fb 100644 --- a/src/cryptography/hazmat/primitives/serialization/pkcs7.py +++ b/src/cryptography/hazmat/primitives/serialization/pkcs7.py @@ -18,16 +18,10 @@ from cryptography.utils import _check_byteslike -def load_pem_pkcs7_certificates(data: bytes) -> list[x509.Certificate]: - from cryptography.hazmat.backends.openssl.backend import backend +load_pem_pkcs7_certificates = rust_pkcs7.load_pem_pkcs7_certificates - return backend.load_pem_pkcs7_certificates(data) - -def load_der_pkcs7_certificates(data: bytes) -> list[x509.Certificate]: - from cryptography.hazmat.backends.openssl.backend import backend - - return backend.load_der_pkcs7_certificates(data) +load_der_pkcs7_certificates = rust_pkcs7.load_der_pkcs7_certificates serialize_certificates = rust_pkcs7.serialize_certificates diff --git a/src/rust/cryptography-openssl/Cargo.toml b/src/rust/cryptography-openssl/Cargo.toml index 9de75a80c88fa..3a35c9fcaa2d9 100644 --- a/src/rust/cryptography-openssl/Cargo.toml +++ b/src/rust/cryptography-openssl/Cargo.toml @@ -9,6 +9,6 @@ rust-version = "1.63.0" [dependencies] openssl = "0.10.63" -ffi = { package = "openssl-sys", version = "0.9.91" } +ffi = { package = "openssl-sys", version = "0.9.99" } foreign-types = "0.3" foreign-types-shared = "0.1" diff --git a/src/rust/src/pkcs7.rs b/src/rust/src/pkcs7.rs index b7f6af216e495..33785f9fe7db3 100644 --- a/src/rust/src/pkcs7.rs +++ b/src/rust/src/pkcs7.rs @@ -9,11 +9,13 @@ use std::ops::Deref; use cryptography_x509::csr::Attribute; use cryptography_x509::{common, oid, pkcs7}; use once_cell::sync::Lazy; +use openssl::pkcs7::Pkcs7; use crate::asn1::encode_der_data; use crate::buf::CffiBuf; -use crate::error::CryptographyResult; -use crate::{types, x509}; +use crate::error::{CryptographyError, CryptographyResult}; +use crate::x509::certificate::load_pem_x509_certificate; +use crate::{exceptions, types, x509}; const PKCS7_CONTENT_TYPE_OID: asn1::ObjectIdentifier = asn1::oid!(1, 2, 840, 113549, 1, 9, 3); const PKCS7_MESSAGE_DIGEST_OID: asn1::ObjectIdentifier = asn1::oid!(1, 2, 840, 113549, 1, 9, 4); @@ -290,11 +292,67 @@ fn smime_canonicalize(data: &[u8], text_mode: bool) -> (Cow<'_, [u8]>, Cow<'_, [ } } +fn load_pkcs7_certificates( + py: pyo3::Python<'_>, + pkcs7: Pkcs7, +) -> CryptographyResult> { + let nid = pkcs7.type_().map_or(openssl::nid::Nid::UNDEF, |x| x.nid()); + assert_ne!(nid, openssl::nid::Nid::UNDEF); + if nid != openssl::nid::Nid::PKCS7_SIGNED { + return Err(CryptographyError::from( + exceptions::UnsupportedAlgorithm::new_err(( + format!("Only basic signed structures are currently supported. NID for this data was {}", nid.as_raw()), + exceptions::Reasons::UNSUPPORTED_SERIALIZATION, + )), + )); + } + let signed_certificates = pkcs7.signed().and_then(|x| x.certificates()); + match signed_certificates { + None => Err(CryptographyError::from( + pyo3::exceptions::PyValueError::new_err( + "The provided PKCS7 has no certificate data, but a cert loading method was called.", + ), + )), + Some(c) => c + .iter() + .map(|c| load_pem_x509_certificate(py, c.to_pem()?.as_slice(), None)) + .collect(), + } +} + +#[pyo3::prelude::pyfunction] +fn load_pem_pkcs7_certificates( + py: pyo3::Python<'_>, + data: &[u8], +) -> CryptographyResult> { + let pkcs7_decoded = openssl::pkcs7::Pkcs7::from_pem(data).map_err(|_| { + CryptographyError::from(pyo3::exceptions::PyValueError::new_err( + "Unable to parse PKCS7 data", + )) + })?; + load_pkcs7_certificates(py, pkcs7_decoded) +} + +#[pyo3::prelude::pyfunction] +fn load_der_pkcs7_certificates( + py: pyo3::Python<'_>, + data: &[u8], +) -> CryptographyResult> { + let pkcs7_decoded = openssl::pkcs7::Pkcs7::from_der(data).map_err(|_| { + CryptographyError::from(pyo3::exceptions::PyValueError::new_err( + "Unable to parse PKCS7 data", + )) + })?; + load_pkcs7_certificates(py, pkcs7_decoded) +} + pub(crate) fn create_submodule(py: pyo3::Python<'_>) -> pyo3::PyResult<&pyo3::prelude::PyModule> { let submod = pyo3::prelude::PyModule::new(py, "pkcs7")?; submod.add_function(pyo3::wrap_pyfunction!(serialize_certificates, submod)?)?; submod.add_function(pyo3::wrap_pyfunction!(sign_and_serialize, submod)?)?; + submod.add_function(pyo3::wrap_pyfunction!(load_pem_pkcs7_certificates, submod)?)?; + submod.add_function(pyo3::wrap_pyfunction!(load_der_pkcs7_certificates, submod)?)?; Ok(submod) } diff --git a/src/rust/src/x509/certificate.rs b/src/rust/src/x509/certificate.rs index bc40fc846ef49..58c1267d3b36a 100644 --- a/src/rust/src/x509/certificate.rs +++ b/src/rust/src/x509/certificate.rs @@ -332,7 +332,7 @@ fn cert_version(py: pyo3::Python<'_>, version: u8) -> Result<&pyo3::PyAny, Crypt } #[pyo3::prelude::pyfunction] -fn load_pem_x509_certificate( +pub(crate) fn load_pem_x509_certificate( py: pyo3::Python<'_>, data: &[u8], backend: Option<&pyo3::PyAny>,