Skip to content

Commit 41daf2d

Browse files
authored
Migrate PKCS7 backend to Rust (#10228)
* Migrate PKCS7 backend to Rust * Disable PKCS7 functions under BoringSSL * Misc PKCS7 fixes
1 parent d54093e commit 41daf2d

File tree

7 files changed

+117
-72
lines changed

7 files changed

+117
-72
lines changed

src/cryptography/hazmat/backends/openssl/backend.py

+1-56
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
import typing
1111

1212
from cryptography import utils, x509
13-
from cryptography.exceptions import UnsupportedAlgorithm, _Reasons
13+
from cryptography.exceptions import UnsupportedAlgorithm
1414
from cryptography.hazmat.backends.openssl import aead
1515
from cryptography.hazmat.backends.openssl.ciphers import _CipherContext
1616
from cryptography.hazmat.bindings._rust import openssl as rust_openssl
@@ -863,61 +863,6 @@ def poly1305_supported(self) -> bool:
863863
def pkcs7_supported(self) -> bool:
864864
return not self._lib.CRYPTOGRAPHY_IS_BORINGSSL
865865

866-
def load_pem_pkcs7_certificates(
867-
self, data: bytes
868-
) -> list[x509.Certificate]:
869-
utils._check_bytes("data", data)
870-
bio = self._bytes_to_bio(data)
871-
p7 = self._lib.PEM_read_bio_PKCS7(
872-
bio.bio, self._ffi.NULL, self._ffi.NULL, self._ffi.NULL
873-
)
874-
if p7 == self._ffi.NULL:
875-
self._consume_errors()
876-
raise ValueError("Unable to parse PKCS7 data")
877-
878-
p7 = self._ffi.gc(p7, self._lib.PKCS7_free)
879-
return self._load_pkcs7_certificates(p7)
880-
881-
def load_der_pkcs7_certificates(
882-
self, data: bytes
883-
) -> list[x509.Certificate]:
884-
utils._check_bytes("data", data)
885-
bio = self._bytes_to_bio(data)
886-
p7 = self._lib.d2i_PKCS7_bio(bio.bio, self._ffi.NULL)
887-
if p7 == self._ffi.NULL:
888-
self._consume_errors()
889-
raise ValueError("Unable to parse PKCS7 data")
890-
891-
p7 = self._ffi.gc(p7, self._lib.PKCS7_free)
892-
return self._load_pkcs7_certificates(p7)
893-
894-
def _load_pkcs7_certificates(self, p7) -> list[x509.Certificate]:
895-
nid = self._lib.OBJ_obj2nid(p7.type)
896-
self.openssl_assert(nid != self._lib.NID_undef)
897-
if nid != self._lib.NID_pkcs7_signed:
898-
raise UnsupportedAlgorithm(
899-
"Only basic signed structures are currently supported. NID"
900-
f" for this data was {nid}",
901-
_Reasons.UNSUPPORTED_SERIALIZATION,
902-
)
903-
904-
if p7.d.sign == self._ffi.NULL:
905-
raise ValueError(
906-
"The provided PKCS7 has no certificate data, but a cert "
907-
"loading method was called."
908-
)
909-
910-
sk_x509 = p7.d.sign.cert
911-
num = self._lib.sk_X509_num(sk_x509)
912-
certs: list[x509.Certificate] = []
913-
for i in range(num):
914-
x509 = self._lib.sk_X509_value(sk_x509, i)
915-
self.openssl_assert(x509 != self._ffi.NULL)
916-
cert = self._ossl2cert(x509)
917-
certs.append(cert)
918-
919-
return certs
920-
921866

922867
class GetCipherByName:
923868
def __init__(self, fmt: str):

src/cryptography/hazmat/bindings/_rust/pkcs7.pyi

+6
Original file line numberDiff line numberDiff line change
@@ -13,3 +13,9 @@ def sign_and_serialize(
1313
encoding: serialization.Encoding,
1414
options: typing.Iterable[pkcs7.PKCS7Options],
1515
) -> bytes: ...
16+
def load_pem_pkcs7_certificates(
17+
data: bytes,
18+
) -> list[x509.Certificate]: ...
19+
def load_der_pkcs7_certificates(
20+
data: bytes,
21+
) -> list[x509.Certificate]: ...

src/cryptography/hazmat/primitives/serialization/pkcs7.py

+2-12
Original file line numberDiff line numberDiff line change
@@ -17,22 +17,12 @@
1717
from cryptography.hazmat.primitives.asymmetric import ec, padding, rsa
1818
from cryptography.utils import _check_byteslike
1919

20+
load_pem_pkcs7_certificates = rust_pkcs7.load_pem_pkcs7_certificates
2021

21-
def load_pem_pkcs7_certificates(data: bytes) -> list[x509.Certificate]:
22-
from cryptography.hazmat.backends.openssl.backend import backend
23-
24-
return backend.load_pem_pkcs7_certificates(data)
25-
26-
27-
def load_der_pkcs7_certificates(data: bytes) -> list[x509.Certificate]:
28-
from cryptography.hazmat.backends.openssl.backend import backend
29-
30-
return backend.load_der_pkcs7_certificates(data)
31-
22+
load_der_pkcs7_certificates = rust_pkcs7.load_der_pkcs7_certificates
3223

3324
serialize_certificates = rust_pkcs7.serialize_certificates
3425

35-
3626
PKCS7HashTypes = typing.Union[
3727
hashes.SHA224,
3828
hashes.SHA256,

src/rust/cryptography-openssl/Cargo.toml

+1-1
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,6 @@ rust-version = "1.63.0"
99

1010
[dependencies]
1111
openssl = "0.10.63"
12-
ffi = { package = "openssl-sys", version = "0.9.91" }
12+
ffi = { package = "openssl-sys", version = "0.9.99" }
1313
foreign-types = "0.3"
1414
foreign-types-shared = "0.1"

src/rust/src/pkcs7.rs

+93-2
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,17 @@ use std::ops::Deref;
99
use cryptography_x509::csr::Attribute;
1010
use cryptography_x509::{common, oid, pkcs7};
1111
use once_cell::sync::Lazy;
12+
#[cfg(not(CRYPTOGRAPHY_IS_BORINGSSL))]
13+
use openssl::pkcs7::Pkcs7;
14+
#[cfg(not(CRYPTOGRAPHY_IS_BORINGSSL))]
15+
use pyo3::IntoPy;
1216

1317
use crate::asn1::encode_der_data;
1418
use crate::buf::CffiBuf;
15-
use crate::error::CryptographyResult;
16-
use crate::{types, x509};
19+
use crate::error::{CryptographyError, CryptographyResult};
20+
#[cfg(not(CRYPTOGRAPHY_IS_BORINGSSL))]
21+
use crate::x509::certificate::load_der_x509_certificate;
22+
use crate::{exceptions, types, x509};
1723

1824
const PKCS7_CONTENT_TYPE_OID: asn1::ObjectIdentifier = asn1::oid!(1, 2, 840, 113549, 1, 9, 3);
1925
const PKCS7_MESSAGE_DIGEST_OID: asn1::ObjectIdentifier = asn1::oid!(1, 2, 840, 113549, 1, 9, 4);
@@ -290,11 +296,96 @@ fn smime_canonicalize(data: &[u8], text_mode: bool) -> (Cow<'_, [u8]>, Cow<'_, [
290296
}
291297
}
292298

299+
#[cfg(not(CRYPTOGRAPHY_IS_BORINGSSL))]
300+
fn load_pkcs7_certificates(
301+
py: pyo3::Python<'_>,
302+
pkcs7: Pkcs7,
303+
) -> CryptographyResult<&pyo3::types::PyList> {
304+
let nid = pkcs7.type_().map(|t| t.nid());
305+
if nid != Some(openssl::nid::Nid::PKCS7_SIGNED) {
306+
let nid_string = nid.map_or("empty".to_string(), |n| n.as_raw().to_string());
307+
return Err(CryptographyError::from(
308+
exceptions::UnsupportedAlgorithm::new_err((
309+
format!("Only basic signed structures are currently supported. NID for this data was {}", nid_string),
310+
exceptions::Reasons::UNSUPPORTED_SERIALIZATION,
311+
)),
312+
));
313+
}
314+
315+
let signed_certificates = pkcs7.signed().and_then(|x| x.certificates());
316+
match signed_certificates {
317+
None => Err(CryptographyError::from(
318+
pyo3::exceptions::PyValueError::new_err(
319+
"The provided PKCS7 has no certificate data, but a cert loading method was called.",
320+
),
321+
)),
322+
Some(certificates) => {
323+
let result = pyo3::types::PyList::empty(py);
324+
for c in certificates {
325+
let cert_der = pyo3::types::PyBytes::new(py, c.to_der()?.as_slice()).into_py(py);
326+
let cert = load_der_x509_certificate(py, cert_der, None)?;
327+
result.append(cert.into_py(py))?;
328+
}
329+
Ok(result)
330+
}
331+
}
332+
}
333+
334+
#[pyo3::prelude::pyfunction]
335+
fn load_pem_pkcs7_certificates<'p>(
336+
py: pyo3::Python<'p>,
337+
data: &[u8],
338+
) -> CryptographyResult<&'p pyo3::types::PyList> {
339+
cfg_if::cfg_if! {
340+
if #[cfg(not(CRYPTOGRAPHY_IS_BORINGSSL))] {
341+
let pkcs7_decoded = openssl::pkcs7::Pkcs7::from_pem(data).map_err(|_| {
342+
CryptographyError::from(pyo3::exceptions::PyValueError::new_err(
343+
"Unable to parse PKCS7 data",
344+
))
345+
})?;
346+
load_pkcs7_certificates(py, pkcs7_decoded)
347+
} else {
348+
return Err(CryptographyError::from(
349+
exceptions::UnsupportedAlgorithm::new_err((
350+
"PKCS#7 is not supported by this backend.",
351+
exceptions::Reasons::UNSUPPORTED_SERIALIZATION,
352+
)),
353+
));
354+
}
355+
}
356+
}
357+
358+
#[pyo3::prelude::pyfunction]
359+
fn load_der_pkcs7_certificates<'p>(
360+
py: pyo3::Python<'p>,
361+
data: &[u8],
362+
) -> CryptographyResult<&'p pyo3::types::PyList> {
363+
cfg_if::cfg_if! {
364+
if #[cfg(not(CRYPTOGRAPHY_IS_BORINGSSL))] {
365+
let pkcs7_decoded = openssl::pkcs7::Pkcs7::from_der(data).map_err(|_| {
366+
CryptographyError::from(pyo3::exceptions::PyValueError::new_err(
367+
"Unable to parse PKCS7 data",
368+
))
369+
})?;
370+
load_pkcs7_certificates(py, pkcs7_decoded)
371+
} else {
372+
return Err(CryptographyError::from(
373+
exceptions::UnsupportedAlgorithm::new_err((
374+
"PKCS#7 is not supported by this backend.",
375+
exceptions::Reasons::UNSUPPORTED_SERIALIZATION,
376+
)),
377+
));
378+
}
379+
}
380+
}
381+
293382
pub(crate) fn create_submodule(py: pyo3::Python<'_>) -> pyo3::PyResult<&pyo3::prelude::PyModule> {
294383
let submod = pyo3::prelude::PyModule::new(py, "pkcs7")?;
295384

296385
submod.add_function(pyo3::wrap_pyfunction!(serialize_certificates, submod)?)?;
297386
submod.add_function(pyo3::wrap_pyfunction!(sign_and_serialize, submod)?)?;
387+
submod.add_function(pyo3::wrap_pyfunction!(load_pem_pkcs7_certificates, submod)?)?;
388+
submod.add_function(pyo3::wrap_pyfunction!(load_der_pkcs7_certificates, submod)?)?;
298389

299390
Ok(submod)
300391
}

src/rust/src/x509/certificate.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -378,7 +378,7 @@ fn load_pem_x509_certificates(
378378
}
379379

380380
#[pyo3::prelude::pyfunction]
381-
fn load_der_x509_certificate(
381+
pub(crate) fn load_der_x509_certificate(
382382
py: pyo3::Python<'_>,
383383
data: pyo3::Py<pyo3::types::PyBytes>,
384384
backend: Option<&pyo3::PyAny>,

tests/hazmat/primitives/test_pkcs7.py

+13
Original file line numberDiff line numberDiff line change
@@ -922,3 +922,16 @@ def test_invalid_types(self):
922922
certs,
923923
"not an encoding", # type: ignore[arg-type]
924924
)
925+
926+
927+
@pytest.mark.supported(
928+
only_if=lambda backend: not backend.pkcs7_supported(),
929+
skip_message="Requires OpenSSL without PKCS7 support (BoringSSL)",
930+
)
931+
class TestPKCS7Unsupported:
932+
def test_pkcs7_functions_unsupported(self):
933+
with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_SERIALIZATION):
934+
pkcs7.load_der_pkcs7_certificates(b"nonsense")
935+
936+
with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_SERIALIZATION):
937+
pkcs7.load_pem_pkcs7_certificates(b"nonsense")

0 commit comments

Comments
 (0)