Skip to content

Commit e69b1e0

Browse files
abbraclaude
andauthored
add caching for issuer and subject (#14442)
* x509/certificate: cache issuer getter result Store the Python Name object in a PyOnceLock so that repeated accesses avoid re-running parse_name, which allocates a full Python Name object tree from ASN.1 on every call. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Signed-off-by: Alexander Bokovoy <abokovoy@redhat.com> * x509/certificate: cache subject getter result Store the Python Name object in a PyOnceLock so that repeated accesses avoid re-running parse_name, which allocates a full Python Name object tree from ASN.1 on every call. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Signed-off-by: Alexander Bokovoy <abokovoy@redhat.com> * tests/x509: fix test_country_jurisdiction_country_too_long after subject caching The test assumed cert.subject re-parses the Name on every call, so it checked each too-long-country warning in its own pytest.warns block. After subject caching, parse_name runs only once (on the first access) and emits both COUNTRY_NAME and JURISDICTION_COUNTRY_NAME warnings in a single call. Subsequent accesses return the cached Name object without re-parsing, so the second block saw no warnings. Merge both assertions into a single pytest.warns block, which correctly captures all warnings emitted during the first (and only) parse. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Signed-off-by: Alexander Bokovoy <abokovoy@redhat.com> --------- Signed-off-by: Alexander Bokovoy <abokovoy@redhat.com> Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 1807083 commit e69b1e0

File tree

4 files changed

+30
-6
lines changed

4 files changed

+30
-6
lines changed

src/rust/src/pkcs7.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -768,6 +768,8 @@ where
768768
x509::certificate::Certificate {
769769
raw: raw_cert,
770770
cached_extensions: pyo3::sync::PyOnceLock::new(),
771+
cached_issuer: pyo3::sync::PyOnceLock::new(),
772+
cached_subject: pyo3::sync::PyOnceLock::new(),
771773
},
772774
)?)?;
773775

src/rust/src/x509/certificate.rs

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,8 @@ self_cell::self_cell!(
4141
pub(crate) struct Certificate {
4242
pub(crate) raw: OwnedCertificate,
4343
pub(crate) cached_extensions: pyo3::sync::PyOnceLock<pyo3::Py<pyo3::PyAny>>,
44+
pub(crate) cached_issuer: pyo3::sync::PyOnceLock<pyo3::Py<pyo3::PyAny>>,
45+
pub(crate) cached_subject: pyo3::sync::PyOnceLock<pyo3::Py<pyo3::PyAny>>,
4446
}
4547

4648
#[pyo3::pymethods]
@@ -138,14 +140,28 @@ impl Certificate {
138140

139141
#[getter]
140142
fn issuer<'p>(&self, py: pyo3::Python<'p>) -> pyo3::PyResult<pyo3::Bound<'p, pyo3::PyAny>> {
141-
Ok(x509::parse_name(py, self.raw.borrow_dependent().issuer())
142-
.map_err(|e| e.add_location(asn1::ParseLocation::Field("issuer")))?)
143+
Ok(self
144+
.cached_issuer
145+
.get_or_try_init(py, || {
146+
x509::parse_name(py, self.raw.borrow_dependent().issuer())
147+
.map_err(|e| e.add_location(asn1::ParseLocation::Field("issuer")))
148+
.map(|v| v.unbind())
149+
})?
150+
.bind(py)
151+
.clone())
143152
}
144153

145154
#[getter]
146155
fn subject<'p>(&self, py: pyo3::Python<'p>) -> pyo3::PyResult<pyo3::Bound<'p, pyo3::PyAny>> {
147-
Ok(x509::parse_name(py, self.raw.borrow_dependent().subject())
148-
.map_err(|e| e.add_location(asn1::ParseLocation::Field("subject")))?)
156+
Ok(self
157+
.cached_subject
158+
.get_or_try_init(py, || {
159+
x509::parse_name(py, self.raw.borrow_dependent().subject())
160+
.map_err(|e| e.add_location(asn1::ParseLocation::Field("subject")))
161+
.map(|v| v.unbind())
162+
})?
163+
.bind(py)
164+
.clone())
149165
}
150166

151167
#[getter]
@@ -441,6 +457,8 @@ pub(crate) fn load_der_x509_certificate(
441457
Ok(Certificate {
442458
raw,
443459
cached_extensions: pyo3::sync::PyOnceLock::new(),
460+
cached_issuer: pyo3::sync::PyOnceLock::new(),
461+
cached_subject: pyo3::sync::PyOnceLock::new(),
444462
})
445463
}
446464

src/rust/src/x509/ocsp_resp.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -265,6 +265,8 @@ impl OCSPResponse {
265265
x509::certificate::Certificate {
266266
raw: raw_cert,
267267
cached_extensions: pyo3::sync::PyOnceLock::new(),
268+
cached_issuer: pyo3::sync::PyOnceLock::new(),
269+
cached_subject: pyo3::sync::PyOnceLock::new(),
268270
},
269271
)?)?;
270272
}

tests/x509/test_x509.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1028,15 +1028,17 @@ def test_country_jurisdiction_country_too_long(self, backend):
10281028
os.path.join("x509", "custom", "bad_country.pem"),
10291029
x509.load_pem_x509_certificate,
10301030
)
1031+
# Both warnings are emitted during the first parse_name call (when
1032+
# cert.subject is first accessed); subsequent accesses return the
1033+
# cached Name object without re-parsing, so both checks must live
1034+
# inside a single pytest.warns block.
10311035
with pytest.warns(UserWarning):
10321036
assert (
10331037
cert.subject.get_attributes_for_oid(x509.NameOID.COUNTRY_NAME)[
10341038
0
10351039
].value
10361040
== "too long"
10371041
)
1038-
1039-
with pytest.warns(UserWarning):
10401042
assert (
10411043
cert.subject.get_attributes_for_oid(
10421044
x509.NameOID.JURISDICTION_COUNTRY_NAME

0 commit comments

Comments
 (0)