Skip to content

Commit 23ef49c

Browse files
authored
Added an implementation of pure Rust PKCS#7 loading that we can use on AWS-LC and BoringSSL (#13632)
* Added an implementation of pure Rust PKCS#7 loading that we can use on AWS-LC and BoringSSL * GHA
1 parent 98cb185 commit 23ef49c

File tree

3 files changed

+113
-56
lines changed

3 files changed

+113
-56
lines changed

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

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -294,10 +294,7 @@ def poly1305_supported(self) -> bool:
294294
return not self._fips_enabled
295295

296296
def pkcs7_supported(self) -> bool:
297-
return (
298-
not rust_openssl.CRYPTOGRAPHY_IS_BORINGSSL
299-
and not rust_openssl.CRYPTOGRAPHY_IS_AWSLC
300-
)
297+
return True
301298

302299

303300
backend = Backend()

src/rust/src/pkcs7.rs

Lines changed: 82 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,11 @@
44

55
use std::borrow::Cow;
66
use std::collections::HashMap;
7+
use std::mem;
78
use std::ops::Deref;
89
use std::sync::LazyLock;
910

11+
use cryptography_x509::certificate::Certificate as RawCertificate;
1012
use cryptography_x509::common::{AlgorithmIdentifier, AlgorithmParameters};
1113
use cryptography_x509::csr::Attribute;
1214
use cryptography_x509::pkcs7::PKCS7_DATA_OID;
@@ -23,6 +25,7 @@ use crate::buf::CffiBuf;
2325
use crate::error::{CryptographyError, CryptographyResult};
2426
use crate::padding::PKCS7UnpaddingContext;
2527
use crate::pkcs12::symmetric_encrypt;
28+
use crate::x509::certificate;
2629
#[cfg(not(any(CRYPTOGRAPHY_IS_BORINGSSL, CRYPTOGRAPHY_IS_AWSLC)))]
2730
use crate::x509::certificate::load_der_x509_certificate;
2831
use crate::{exceptions, types, x509};
@@ -739,65 +742,109 @@ fn load_pkcs7_certificates(
739742
}
740743
}
741744

742-
#[pyo3::pyfunction]
743-
fn load_pem_pkcs7_certificates<'p>(
745+
pub fn try_list_of_certificates<'p, F>(
744746
py: pyo3::Python<'p>,
745-
data: &[u8],
746-
) -> CryptographyResult<pyo3::Bound<'p, pyo3::types::PyList>> {
747-
cfg_if::cfg_if! {
748-
if #[cfg(not(any(CRYPTOGRAPHY_IS_BORINGSSL, CRYPTOGRAPHY_IS_AWSLC)))] {
749-
let pem_block = pem::parse(data)?;
750-
if pem_block.tag() != "PKCS7" {
751-
return Err(CryptographyError::from(
752-
pyo3::exceptions::PyValueError::new_err(
753-
"The provided PEM data does not have the PKCS7 tag.",
754-
),
755-
));
756-
}
747+
data: pyo3::Py<pyo3::types::PyBytes>,
748+
f: F,
749+
) -> CryptographyResult<pyo3::Bound<'p, pyo3::types::PyList>>
750+
where
751+
F: for<'a> FnOnce(
752+
&'a pyo3::Py<pyo3::types::PyBytes>,
753+
&mut dyn FnMut(RawCertificate<'a>) -> CryptographyResult<()>,
754+
) -> CryptographyResult<()>,
755+
{
756+
let result = pyo3::types::PyList::empty(py);
757+
let mut cb = |val| {
758+
// SAFETY: based on the type of `F`, we know `val` must be derived from
759+
// data, and we know that `data.clone_ref(py)` makes any pointers into
760+
// the original one also valid.
761+
let raw_cert = certificate::OwnedCertificate::new(data.clone_ref(py), |_| unsafe {
762+
mem::transmute(val)
763+
});
764+
result.append(pyo3::Bound::new(
765+
py,
766+
x509::certificate::Certificate {
767+
raw: raw_cert,
768+
cached_extensions: pyo3::sync::PyOnceLock::new(),
769+
},
770+
)?)?;
757771

758-
load_der_pkcs7_certificates(py, pem_block.contents())
759-
} else {
760-
let _ = py;
761-
let _ = data;
762-
Err(CryptographyError::from(
772+
Ok(())
773+
};
774+
f(&data, &mut cb)?;
775+
776+
Ok(result)
777+
}
778+
779+
fn load_pkcs7_certificates_rust(
780+
py: pyo3::Python<'_>,
781+
data: pyo3::Py<pyo3::types::PyBytes>,
782+
) -> CryptographyResult<pyo3::Bound<'_, pyo3::types::PyList>> {
783+
try_list_of_certificates(py, data, |data, cb| {
784+
let p7 = asn1::parse_single::<pkcs7::ContentInfo<'_>>(data.as_bytes(py))?;
785+
let pkcs7::Content::SignedData(signed_data) = p7.content else {
786+
return Err(CryptographyError::from(
763787
exceptions::UnsupportedAlgorithm::new_err((
764-
"PKCS#7 is not supported by this backend.",
788+
"Only basic signed structures are currently supported.",
765789
exceptions::Reasons::UNSUPPORTED_SERIALIZATION,
766790
)),
767-
))
791+
));
792+
};
793+
let Some(certs) = signed_data.into_inner().certificates else {
794+
return Err(CryptographyError::from(
795+
pyo3::exceptions::PyValueError::new_err(
796+
"The provided PKCS7 has no certificate data, but a cert loading method was called.",
797+
),
798+
));
799+
};
800+
for c in certs.unwrap_read().clone() {
801+
cb(c)?;
768802
}
803+
804+
Ok(())
805+
})
806+
}
807+
808+
#[pyo3::pyfunction]
809+
fn load_pem_pkcs7_certificates(
810+
py: pyo3::Python<'_>,
811+
data: pyo3::Py<pyo3::types::PyBytes>,
812+
) -> CryptographyResult<pyo3::Bound<'_, pyo3::types::PyList>> {
813+
let pem_block = pem::parse(data.as_bytes(py))?;
814+
if pem_block.tag() != "PKCS7" {
815+
return Err(CryptographyError::from(
816+
pyo3::exceptions::PyValueError::new_err(
817+
"The provided PEM data does not have the PKCS7 tag.",
818+
),
819+
));
769820
}
821+
let data = pyo3::types::PyBytes::new(py, pem_block.contents()).unbind();
822+
823+
load_der_pkcs7_certificates(py, data)
770824
}
771825

772826
#[pyo3::pyfunction]
773-
fn load_der_pkcs7_certificates<'p>(
774-
py: pyo3::Python<'p>,
775-
data: &[u8],
776-
) -> CryptographyResult<pyo3::Bound<'p, pyo3::types::PyList>> {
827+
fn load_der_pkcs7_certificates(
828+
py: pyo3::Python<'_>,
829+
data: pyo3::Py<pyo3::types::PyBytes>,
830+
) -> CryptographyResult<pyo3::Bound<'_, pyo3::types::PyList>> {
777831
cfg_if::cfg_if! {
778832
if #[cfg(not(any(CRYPTOGRAPHY_IS_BORINGSSL, CRYPTOGRAPHY_IS_AWSLC)))] {
779-
let pkcs7_decoded = openssl::pkcs7::Pkcs7::from_der(data).map_err(|_| {
833+
let pkcs7_decoded = openssl::pkcs7::Pkcs7::from_der(data.as_bytes(py)).map_err(|_| {
780834
CryptographyError::from(pyo3::exceptions::PyValueError::new_err(
781835
"Unable to parse PKCS7 data",
782836
))
783837
})?;
784838
let result = load_pkcs7_certificates(py, pkcs7_decoded)?;
785-
if asn1::parse_single::<pkcs7::ContentInfo<'_>>(data).is_err() {
839+
if load_pkcs7_certificates_rust(py, data).is_err() {
786840
let warning_cls = pyo3::exceptions::PyUserWarning::type_object(py);
787841
let message = c"PKCS#7 certificates could not be parsed as DER, falling back to parsing as BER. Please file an issue at https://github.com/pyca/cryptography/issues explaining how your PKCS#7 certificates were created. In the future, this may become an exception.";
788842
pyo3::PyErr::warn(py, &warning_cls, message, 1)?;
789843
}
790844

791845
Ok(result)
792846
} else {
793-
let _ = py;
794-
let _ = data;
795-
Err(CryptographyError::from(
796-
exceptions::UnsupportedAlgorithm::new_err((
797-
"PKCS#7 is not supported by this backend.",
798-
exceptions::Reasons::UNSUPPORTED_SERIALIZATION,
799-
)),
800-
))
847+
load_pkcs7_certificates_rust(py, data)
801848
}
802849
}
803850
}

tests/hazmat/primitives/test_pkcs7.py

Lines changed: 30 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,12 @@
1313

1414
from cryptography import exceptions, x509
1515
from cryptography.exceptions import _Reasons
16-
from cryptography.hazmat.bindings._rust import test_support
16+
from cryptography.hazmat.bindings._rust import (
17+
openssl as rust_openssl,
18+
)
19+
from cryptography.hazmat.bindings._rust import (
20+
test_support,
21+
)
1722
from cryptography.hazmat.primitives import hashes, serialization
1823
from cryptography.hazmat.primitives.asymmetric import ed25519, padding, rsa
1924
from cryptography.hazmat.primitives.ciphers import algorithms
@@ -77,8 +82,16 @@ def test_load_pkcs7_pem(self, backend):
7782
],
7883
)
7984
def test_load_pkcs7_der(self, filepath, backend):
85+
loading_fails = False
8086
if filepath.endswith("p7b"):
81-
ctx: typing.Any = pytest.warns(UserWarning)
87+
if (
88+
rust_openssl.CRYPTOGRAPHY_IS_AWSLC
89+
or rust_openssl.CRYPTOGRAPHY_IS_BORINGSSL
90+
):
91+
ctx: typing.Any = pytest.raises(ValueError)
92+
loading_fails = True
93+
else:
94+
ctx = pytest.warns(UserWarning)
8295
else:
8396
ctx = contextlib.nullcontext()
8497

@@ -90,6 +103,10 @@ def test_load_pkcs7_der(self, filepath, backend):
90103
),
91104
mode="rb",
92105
)
106+
107+
if loading_fails:
108+
return
109+
93110
assert len(certs) == 2
94111
assert certs[0].subject.get_attributes_for_oid(
95112
x509.oid.NameOID.COMMON_NAME
@@ -118,7 +135,13 @@ def test_load_pkcs7_unsupported_type(self, backend):
118135

119136
def test_load_pkcs7_empty_certificates(self):
120137
der = b"\x30\x0b\x06\x09\x2a\x86\x48\x86\xf7\x0d\x01\x07\x02"
138+
with pytest.raises(ValueError):
139+
pkcs7.load_der_pkcs7_certificates(der)
121140

141+
der = (
142+
b"0#\x06\t*\x86H\x86\xf7\r\x01\x07\x02\xa0\x160\x14\x02\x01\x011"
143+
b"\x000\x0b\x06\t*\x86H\x86\xf7\r\x01\x07\x011\x00"
144+
)
122145
with pytest.raises(ValueError):
123146
pkcs7.load_der_pkcs7_certificates(der)
124147

@@ -140,8 +163,11 @@ def _load_cert_key():
140163

141164

142165
@pytest.mark.supported(
143-
only_if=lambda backend: backend.pkcs7_supported(),
144-
skip_message="Requires OpenSSL with PKCS7 support",
166+
only_if=lambda backend: not (
167+
rust_openssl.CRYPTOGRAPHY_IS_AWSLC
168+
or rust_openssl.CRYPTOGRAPHY_IS_BORINGSSL
169+
),
170+
skip_message="Requires OpenSSL with PKCS7 verification test support",
145171
)
146172
class TestPKCS7SignatureBuilder:
147173
def test_invalid_data(self, backend):
@@ -1483,19 +1509,6 @@ def test_invalid_types(self):
14831509
)
14841510

14851511

1486-
@pytest.mark.supported(
1487-
only_if=lambda backend: not backend.pkcs7_supported(),
1488-
skip_message="Requires OpenSSL without PKCS7 support (BoringSSL)",
1489-
)
1490-
class TestPKCS7Unsupported:
1491-
def test_pkcs7_functions_unsupported(self):
1492-
with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_SERIALIZATION):
1493-
pkcs7.load_der_pkcs7_certificates(b"nonsense")
1494-
1495-
with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_SERIALIZATION):
1496-
pkcs7.load_pem_pkcs7_certificates(b"nonsense")
1497-
1498-
14991512
@pytest.mark.supported(
15001513
only_if=lambda backend: backend.pkcs7_supported()
15011514
and not backend.rsa_encryption_supported(padding.PKCS1v15()),

0 commit comments

Comments
 (0)