diff --git a/src/rust/src/pkcs7.rs b/src/rust/src/pkcs7.rs index 06814f777d50..05f3bec6565a 100644 --- a/src/rust/src/pkcs7.rs +++ b/src/rust/src/pkcs7.rs @@ -768,6 +768,8 @@ where x509::certificate::Certificate { raw: raw_cert, cached_extensions: pyo3::sync::PyOnceLock::new(), + cached_issuer: pyo3::sync::PyOnceLock::new(), + cached_subject: pyo3::sync::PyOnceLock::new(), }, )?)?; diff --git a/src/rust/src/x509/certificate.rs b/src/rust/src/x509/certificate.rs index d0bb651f053e..15a33beb01e1 100644 --- a/src/rust/src/x509/certificate.rs +++ b/src/rust/src/x509/certificate.rs @@ -41,6 +41,8 @@ self_cell::self_cell!( pub(crate) struct Certificate { pub(crate) raw: OwnedCertificate, pub(crate) cached_extensions: pyo3::sync::PyOnceLock>, + pub(crate) cached_issuer: pyo3::sync::PyOnceLock>, + pub(crate) cached_subject: pyo3::sync::PyOnceLock>, } #[pyo3::pymethods] @@ -138,14 +140,28 @@ impl Certificate { #[getter] fn issuer<'p>(&self, py: pyo3::Python<'p>) -> pyo3::PyResult> { - Ok(x509::parse_name(py, self.raw.borrow_dependent().issuer()) - .map_err(|e| e.add_location(asn1::ParseLocation::Field("issuer")))?) + Ok(self + .cached_issuer + .get_or_try_init(py, || { + x509::parse_name(py, self.raw.borrow_dependent().issuer()) + .map_err(|e| e.add_location(asn1::ParseLocation::Field("issuer"))) + .map(|v| v.unbind()) + })? + .bind(py) + .clone()) } #[getter] fn subject<'p>(&self, py: pyo3::Python<'p>) -> pyo3::PyResult> { - Ok(x509::parse_name(py, self.raw.borrow_dependent().subject()) - .map_err(|e| e.add_location(asn1::ParseLocation::Field("subject")))?) + Ok(self + .cached_subject + .get_or_try_init(py, || { + x509::parse_name(py, self.raw.borrow_dependent().subject()) + .map_err(|e| e.add_location(asn1::ParseLocation::Field("subject"))) + .map(|v| v.unbind()) + })? + .bind(py) + .clone()) } #[getter] @@ -441,6 +457,8 @@ pub(crate) fn load_der_x509_certificate( Ok(Certificate { raw, cached_extensions: pyo3::sync::PyOnceLock::new(), + cached_issuer: pyo3::sync::PyOnceLock::new(), + cached_subject: pyo3::sync::PyOnceLock::new(), }) } diff --git a/src/rust/src/x509/ocsp_resp.rs b/src/rust/src/x509/ocsp_resp.rs index cab6320fc922..6acb0970f85b 100644 --- a/src/rust/src/x509/ocsp_resp.rs +++ b/src/rust/src/x509/ocsp_resp.rs @@ -265,6 +265,8 @@ impl OCSPResponse { x509::certificate::Certificate { raw: raw_cert, cached_extensions: pyo3::sync::PyOnceLock::new(), + cached_issuer: pyo3::sync::PyOnceLock::new(), + cached_subject: pyo3::sync::PyOnceLock::new(), }, )?)?; } diff --git a/tests/x509/test_x509.py b/tests/x509/test_x509.py index 3be4a3162171..5d69e5358eae 100644 --- a/tests/x509/test_x509.py +++ b/tests/x509/test_x509.py @@ -1028,6 +1028,10 @@ def test_country_jurisdiction_country_too_long(self, backend): os.path.join("x509", "custom", "bad_country.pem"), x509.load_pem_x509_certificate, ) + # Both warnings are emitted during the first parse_name call (when + # cert.subject is first accessed); subsequent accesses return the + # cached Name object without re-parsing, so both checks must live + # inside a single pytest.warns block. with pytest.warns(UserWarning): assert ( cert.subject.get_attributes_for_oid(x509.NameOID.COUNTRY_NAME)[ @@ -1035,8 +1039,6 @@ def test_country_jurisdiction_country_too_long(self, backend): ].value == "too long" ) - - with pytest.warns(UserWarning): assert ( cert.subject.get_attributes_for_oid( x509.NameOID.JURISDICTION_COUNTRY_NAME