Skip to content

Commit 4f1849e

Browse files
committed
Refactor CRL to use PyBytes ownership instead of Arc
Simplifies the CRL implementation by removing Arc indirection - OwnedRevokedCertificate and OwnedCRLIteratorData now own Py<PyBytes> directly. Adds sound abstraction functions for lifetime rebinding following the existing try_map_arc_data_mut_crl_iterator pattern.
1 parent 8e5f124 commit 4f1849e

File tree

1 file changed

+130
-54
lines changed

1 file changed

+130
-54
lines changed

src/rust/src/x509/crl.rs

Lines changed: 130 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,6 @@
22
// 2.0, and the BSD License. See the LICENSE file in the root of this repository
33
// for complete details.
44

5-
use std::sync::Arc;
6-
75
use cryptography_x509::certificate::SerialNumber;
86
use cryptography_x509::common::{self, Asn1Read};
97
use cryptography_x509::crl::{
@@ -46,7 +44,7 @@ pub(crate) fn load_der_x509_crl(
4644
}
4745

4846
Ok(CertificateRevocationList {
49-
owned: Arc::new(owned),
47+
owned,
5048
revoked_certs: pyo3::sync::PyOnceLock::new(),
5149
cached_extensions: pyo3::sync::PyOnceLock::new(),
5250
})
@@ -83,7 +81,7 @@ self_cell::self_cell!(
8381

8482
#[pyo3::pyclass(frozen, module = "cryptography.hazmat.bindings._rust.x509")]
8583
pub(crate) struct CertificateRevocationList {
86-
owned: Arc<OwnedCertificateRevocationList>,
84+
owned: OwnedCertificateRevocationList,
8785

8886
revoked_certs: pyo3::sync::PyOnceLock<Vec<OwnedRevokedCertificate>>,
8987
cached_extensions: pyo3::sync::PyOnceLock<pyo3::Py<pyo3::PyAny>>,
@@ -96,7 +94,7 @@ impl CertificateRevocationList {
9694

9795
fn revoked_cert(&self, py: pyo3::Python<'_>, idx: usize) -> RevokedCertificate {
9896
RevokedCertificate {
99-
owned: self.revoked_certs.get(py).unwrap()[idx].clone(),
97+
owned: self.revoked_certs.get(py).unwrap()[idx].clone_with_py(py),
10098
cached_extensions: pyo3::sync::PyOnceLock::new(),
10199
}
102100
}
@@ -121,18 +119,15 @@ impl CertificateRevocationList {
121119
self.len()
122120
}
123121

124-
fn __iter__(&self) -> CRLIterator {
122+
fn __iter__(slf: pyo3::PyRef<'_, Self>) -> CRLIterator {
123+
let py = slf.py();
125124
CRLIterator {
126-
contents: OwnedCRLIteratorData::try_new(Arc::clone(&self.owned), |v| {
127-
Ok::<_, ()>(
128-
v.borrow_dependent()
129-
.tbs_cert_list
130-
.revoked_certificates
131-
.as_ref()
132-
.map(|v| v.unwrap_read().clone()),
133-
)
134-
})
135-
.unwrap(),
125+
contents: map_crl_to_iterator_data(&slf.owned, py, |crl| {
126+
crl.tbs_cert_list
127+
.revoked_certificates
128+
.as_ref()
129+
.map(|v| v.unwrap_read().clone())
130+
}),
136131
}
137132
}
138133

@@ -143,9 +138,24 @@ impl CertificateRevocationList {
143138
) -> pyo3::PyResult<pyo3::Bound<'p, pyo3::PyAny>> {
144139
self.revoked_certs.get_or_init(py, || {
145140
let mut revoked_certs = vec![];
146-
let mut it = self.__iter__();
147-
while let Some(c) = it.__next__() {
148-
revoked_certs.push(c.owned);
141+
let mut it_data = map_crl_to_iterator_data(&self.owned, py, |crl| {
142+
crl.tbs_cert_list
143+
.revoked_certificates
144+
.as_ref()
145+
.map(|v| v.unwrap_read().clone())
146+
});
147+
loop {
148+
let revoked = try_map_arc_data_mut_crl_iterator(py, &mut it_data, |v| match v {
149+
Some(v) => match v.next() {
150+
Some(revoked) => Ok(revoked),
151+
None => Err(()),
152+
},
153+
None => Err(()),
154+
});
155+
match revoked {
156+
Ok(owned) => revoked_certs.push(owned),
157+
Err(()) => break,
158+
}
149159
}
150160
revoked_certs
151161
});
@@ -379,27 +389,22 @@ impl CertificateRevocationList {
379389
serial: pyo3::Bound<'_, pyo3::types::PyInt>,
380390
) -> pyo3::PyResult<Option<RevokedCertificate>> {
381391
let serial_bytes = py_uint_to_big_endian_bytes(py, serial)?;
382-
let owned = OwnedRevokedCertificate::try_new(Arc::clone(&self.owned), |v| {
383-
let certs = match &v.borrow_dependent().tbs_cert_list.revoked_certificates {
384-
Some(certs) => certs.unwrap_read().clone(),
385-
None => return Err(()),
386-
};
392+
393+
// Use try_map_crl_to_revoked_cert to soundly extract the certificate
394+
let owned = try_map_crl_to_revoked_cert(&self.owned, py, |crl| {
395+
let certs = crl.tbs_cert_list.revoked_certificates.as_ref()?;
387396

388397
// TODO: linear scan. Make a hash or bisect!
389-
for cert in certs {
390-
if serial_bytes == cert.user_certificate.as_bytes() {
391-
return Ok(cert);
392-
}
393-
}
394-
Err(())
398+
certs
399+
.unwrap_read()
400+
.clone()
401+
.find(|cert| serial_bytes == cert.user_certificate.as_bytes())
395402
});
396-
match owned {
397-
Ok(o) => Ok(Some(RevokedCertificate {
398-
owned: o,
399-
cached_extensions: pyo3::sync::PyOnceLock::new(),
400-
})),
401-
Err(()) => Ok(None),
402-
}
403+
404+
Ok(owned.map(|o| RevokedCertificate {
405+
owned: o,
406+
cached_extensions: pyo3::sync::PyOnceLock::new(),
407+
}))
403408
}
404409

405410
fn is_signature_valid<'p>(
@@ -431,7 +436,7 @@ impl CertificateRevocationList {
431436
type RawCRLIterator<'a> = Option<asn1::SequenceOf<'a, crl::RevokedCertificate<'a>>>;
432437
self_cell::self_cell!(
433438
struct OwnedCRLIteratorData {
434-
owner: Arc<OwnedCertificateRevocationList>,
439+
owner: pyo3::Py<pyo3::types::PyBytes>,
435440

436441
#[covariant]
437442
dependent: RawCRLIterator,
@@ -443,22 +448,96 @@ struct CRLIterator {
443448
contents: OwnedCRLIteratorData,
444449
}
445450

451+
// Open-coded implementation of the API discussed in
452+
// https://github.com/joshua-maros/ouroboros/issues/38
453+
fn map_crl_to_iterator_data<F>(
454+
source: &OwnedCertificateRevocationList,
455+
py: pyo3::Python<'_>,
456+
f: F,
457+
) -> OwnedCRLIteratorData
458+
where
459+
F: for<'a> FnOnce(
460+
&'a RawCertificateRevocationList<'a>,
461+
) -> Option<asn1::SequenceOf<'a, crl::RevokedCertificate<'a>>>,
462+
{
463+
OwnedCRLIteratorData::new(source.borrow_owner().clone_ref(py), |_| {
464+
// SAFETY: This is safe because cloning the PyBytes Py<> ensures the data is
465+
// alive, but Rust doesn't understand the lifetime relationship it
466+
// produces.
467+
f(unsafe {
468+
std::mem::transmute::<
469+
&RawCertificateRevocationList<'_>,
470+
&RawCertificateRevocationList<'_>,
471+
>(source.borrow_dependent())
472+
})
473+
})
474+
}
475+
476+
// Open-coded implementation of the API discussed in
477+
// https://github.com/joshua-maros/ouroboros/issues/38
478+
fn try_map_crl_to_revoked_cert<F>(
479+
source: &OwnedCertificateRevocationList,
480+
py: pyo3::Python<'_>,
481+
f: F,
482+
) -> Option<OwnedRevokedCertificate>
483+
where
484+
F: for<'a> FnOnce(&'a RawCertificateRevocationList<'a>) -> Option<RawRevokedCertificate<'a>>,
485+
{
486+
OwnedRevokedCertificate::try_new(source.borrow_owner().clone_ref(py), |_| {
487+
// SAFETY: This is safe because cloning the PyBytes Py<> ensures the data is
488+
// alive, but Rust doesn't understand the lifetime relationship it
489+
// produces.
490+
match f(unsafe {
491+
std::mem::transmute::<
492+
&RawCertificateRevocationList<'_>,
493+
&RawCertificateRevocationList<'_>,
494+
>(source.borrow_dependent())
495+
}) {
496+
Some(cert) => Ok(cert),
497+
None => Err(()),
498+
}
499+
})
500+
.ok()
501+
}
502+
503+
// Open-coded implementation of the API discussed in
504+
// https://github.com/joshua-maros/ouroboros/issues/38
505+
fn map_revoked_cert<F>(
506+
source: &OwnedRevokedCertificate,
507+
py: pyo3::Python<'_>,
508+
f: F,
509+
) -> OwnedRevokedCertificate
510+
where
511+
F: for<'a> FnOnce(&'a crl::RevokedCertificate<'a>) -> crl::RevokedCertificate<'a>,
512+
{
513+
OwnedRevokedCertificate::new(source.borrow_owner().clone_ref(py), |_| {
514+
// SAFETY: This is safe because cloning the PyBytes Py<> ensures the data is
515+
// alive, but Rust doesn't understand the lifetime relationship it
516+
// produces.
517+
f(unsafe {
518+
std::mem::transmute::<&crl::RevokedCertificate<'_>, &crl::RevokedCertificate<'_>>(
519+
source.borrow_dependent(),
520+
)
521+
})
522+
})
523+
}
524+
446525
// Open-coded implementation of the API discussed in
447526
// https://github.com/joshua-maros/ouroboros/issues/38
448527
fn try_map_arc_data_mut_crl_iterator<E>(
528+
py: pyo3::Python<'_>,
449529
it: &mut OwnedCRLIteratorData,
450530
f: impl for<'this> FnOnce(
451-
&'this OwnedCertificateRevocationList,
452531
&mut Option<asn1::SequenceOf<'this, crl::RevokedCertificate<'this>>>,
453532
) -> Result<crl::RevokedCertificate<'this>, E>,
454533
) -> Result<OwnedRevokedCertificate, E> {
455-
OwnedRevokedCertificate::try_new(Arc::clone(it.borrow_owner()), |inner_it| {
534+
OwnedRevokedCertificate::try_new(it.borrow_owner().clone_ref(py), |_pybytes| {
456535
it.with_dependent_mut(|_, value| {
457-
// SAFETY: This is safe because `Arc::clone` ensures the data is
536+
// SAFETY: This is safe because cloning the PyBytes Py<> ensures the data is
458537
// alive, but Rust doesn't understand the lifetime relationship it
459538
// produces. Open-coded implementation of the API discussed in
460539
// https://github.com/joshua-maros/ouroboros/issues/38
461-
f(inner_it, unsafe {
540+
f(unsafe {
462541
std::mem::transmute::<
463542
&mut Option<asn1::SequenceOf<'_, crl::RevokedCertificate<'_>>>,
464543
&mut Option<asn1::SequenceOf<'_, crl::RevokedCertificate<'_>>>,
@@ -481,8 +560,11 @@ impl CRLIterator {
481560
slf
482561
}
483562

484-
fn __next__(&mut self) -> Option<RevokedCertificate> {
485-
let revoked = try_map_arc_data_mut_crl_iterator(&mut self.contents, |_data, v| match v {
563+
fn __next__(
564+
&mut self,
565+
py: pyo3::Python<'_>,
566+
) -> Option<RevokedCertificate> {
567+
let revoked = try_map_arc_data_mut_crl_iterator(py, &mut self.contents, |v| match v {
486568
Some(v) => match v.next() {
487569
Some(revoked) => Ok(revoked),
488570
None => Err(()),
@@ -499,21 +581,15 @@ impl CRLIterator {
499581

500582
self_cell::self_cell!(
501583
struct OwnedRevokedCertificate {
502-
owner: Arc<OwnedCertificateRevocationList>,
584+
owner: pyo3::Py<pyo3::types::PyBytes>,
503585
#[covariant]
504586
dependent: RawRevokedCertificate,
505587
}
506588
);
507589

508-
impl Clone for OwnedRevokedCertificate {
509-
fn clone(&self) -> OwnedRevokedCertificate {
510-
// SAFETY: This is safe because `Arc::clone` ensures the data is
511-
// alive, but Rust doesn't understand the lifetime relationship it
512-
// produces. Open-coded implementation of the API discussed in
513-
// https://github.com/joshua-maros/ouroboros/issues/38
514-
OwnedRevokedCertificate::new(Arc::clone(self.borrow_owner()), |_| unsafe {
515-
std::mem::transmute(self.borrow_dependent().clone())
516-
})
590+
impl OwnedRevokedCertificate {
591+
fn clone_with_py(&self, py: pyo3::Python<'_>) -> OwnedRevokedCertificate {
592+
map_revoked_cert(self, py, |cert| cert.clone())
517593
}
518594
}
519595

0 commit comments

Comments
 (0)