Skip to content

Commit 1ff6208

Browse files
authored
certificate: add a get_extension helper (#8892)
* certificate: add a `get_extension` helper Signed-off-by: William Woodruff <[email protected]> * certificate: OID by ref Signed-off-by: William Woodruff <[email protected]> * certificate: syntax Signed-off-by: William Woodruff <[email protected]> * x509, src: `check_duplicate_extensions` Signed-off-by: William Woodruff <[email protected]> * src: simplify Signed-off-by: William Woodruff <[email protected]> * src: everyone loves newtypes Signed-off-by: William Woodruff <[email protected]> * rust: refactor-o-rama Signed-off-by: William Woodruff <[email protected]> * src: look upon my works Signed-off-by: William Woodruff <[email protected]> * src: continue blasting the code Signed-off-by: William Woodruff <[email protected]> * src/rust: actually commit my changes Signed-off-by: William Woodruff <[email protected]> * src: clippage Signed-off-by: William Woodruff <[email protected]> * relocate Signed-off-by: William Woodruff <[email protected]> * src: dedupe Signed-off-by: William Woodruff <[email protected]> * src: cleanup Signed-off-by: William Woodruff <[email protected]> * clippage Signed-off-by: William Woodruff <[email protected]> * src: dedupe Signed-off-by: William Woodruff <[email protected]> * common: cleanup Signed-off-by: William Woodruff <[email protected]> * src: unused impls Signed-off-by: William Woodruff <[email protected]> * more deletion Signed-off-by: William Woodruff <[email protected]> * clippage Signed-off-by: William Woodruff <[email protected]> * extensions: add a `get_extension` test Signed-off-by: William Woodruff <[email protected]> * extensions: unused derives Signed-off-by: William Woodruff <[email protected]> * tests/x509: dup ext check for tbs_precertificate_bytes Signed-off-by: William Woodruff <[email protected]> * certificate: remove `extensions()` Signed-off-by: William Woodruff <[email protected]> * extensions: docs Signed-off-by: William Woodruff <[email protected]> * extensions: newtype Signed-off-by: William Woodruff <[email protected]> * rust: better error types, dedupe Signed-off-by: William Woodruff <[email protected]> extensions: unwrap -> expect Signed-off-by: William Woodruff <[email protected]> * Revert "rust: better error types, dedupe" This reverts commit 212b75f. --------- Signed-off-by: William Woodruff <[email protected]>
1 parent c6887af commit 1ff6208

File tree

13 files changed

+193
-60
lines changed

13 files changed

+193
-60
lines changed

src/rust/cryptography-x509/src/certificate.rs

+8-1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
use crate::common;
66
use crate::extensions;
7+
use crate::extensions::Extensions;
78
use crate::name;
89

910
#[derive(asn1::Asn1Read, asn1::Asn1Write, Hash, PartialEq, Clone)]
@@ -31,7 +32,13 @@ pub struct TbsCertificate<'a> {
3132
#[implicit(2)]
3233
pub subject_unique_id: Option<asn1::BitString<'a>>,
3334
#[explicit(3)]
34-
pub extensions: Option<extensions::Extensions<'a>>,
35+
pub raw_extensions: Option<extensions::RawExtensions<'a>>,
36+
}
37+
38+
impl<'a> TbsCertificate<'a> {
39+
pub fn extensions(&'a self) -> Result<Option<Extensions<'a>>, asn1::ObjectIdentifier> {
40+
Extensions::from_raw_extensions(self.raw_extensions.as_ref())
41+
}
3542
}
3643

3744
#[derive(asn1::Asn1Read, asn1::Asn1Write, Hash, PartialEq, Clone)]

src/rust/cryptography-x509/src/crl.rs

+7-3
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,11 @@
22
// 2.0, and the BSD License. See the LICENSE file in the root of this repository
33
// for complete details.
44

5-
use crate::{common, extensions, name};
5+
use crate::{
6+
common,
7+
extensions::{self},
8+
name,
9+
};
610

711
pub type ReasonFlags<'a> =
812
Option<common::Asn1ReadableOrWritable<'a, asn1::BitString<'a>, asn1::OwnedBitString>>;
@@ -31,14 +35,14 @@ pub struct TBSCertList<'a> {
3135
pub next_update: Option<common::Time>,
3236
pub revoked_certificates: RevokedCertificates<'a>,
3337
#[explicit(0)]
34-
pub crl_extensions: Option<extensions::Extensions<'a>>,
38+
pub raw_crl_extensions: Option<extensions::RawExtensions<'a>>,
3539
}
3640

3741
#[derive(asn1::Asn1Read, asn1::Asn1Write, PartialEq, Hash, Clone)]
3842
pub struct RevokedCertificate<'a> {
3943
pub user_certificate: asn1::BigUint<'a>,
4044
pub revocation_date: common::Time,
41-
pub crl_entry_extensions: Option<extensions::Extensions<'a>>,
45+
pub raw_crl_entry_extensions: Option<extensions::RawExtensions<'a>>,
4246
}
4347

4448
#[derive(asn1::Asn1Read, asn1::Asn1Write)]

src/rust/cryptography-x509/src/csr.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ pub struct CertificationRequestInfo<'a> {
2626
impl CertificationRequestInfo<'_> {
2727
pub fn get_extension_attribute(
2828
&self,
29-
) -> Result<Option<extensions::Extensions<'_>>, asn1::ParseError> {
29+
) -> Result<Option<extensions::RawExtensions<'_>>, asn1::ParseError> {
3030
for attribute in self.attributes.unwrap_read().clone() {
3131
if attribute.type_id == oid::EXTENSION_REQUEST
3232
|| attribute.type_id == oid::MS_EXTENSION_REQUEST

src/rust/cryptography-x509/src/extensions.rs

+82-1
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,62 @@
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::collections::HashSet;
6+
57
use crate::common;
68
use crate::crl;
79
use crate::name;
810

9-
pub type Extensions<'a> = common::Asn1ReadableOrWritable<
11+
pub type RawExtensions<'a> = common::Asn1ReadableOrWritable<
1012
'a,
1113
asn1::SequenceOf<'a, Extension<'a>>,
1214
asn1::SequenceOfWriter<'a, Extension<'a>, Vec<Extension<'a>>>,
1315
>;
1416

17+
/// An invariant-enforcing wrapper for `RawExtensions`.
18+
///
19+
/// In particular, an `Extensions` cannot be constructed from a `RawExtensions`
20+
/// that contains duplicated extensions (by OID).
21+
pub struct Extensions<'a>(RawExtensions<'a>);
22+
23+
impl<'a> Extensions<'a> {
24+
/// Create an `Extensions` from the given `RawExtensions`.
25+
///
26+
/// Returns an `Err` variant containing the first duplicated extension's
27+
/// OID, if there are any duplicates.
28+
pub fn from_raw_extensions(
29+
raw: Option<&RawExtensions<'a>>,
30+
) -> Result<Option<Self>, asn1::ObjectIdentifier> {
31+
match raw {
32+
Some(raw_exts) => {
33+
let mut seen_oids = HashSet::new();
34+
35+
for ext in raw_exts.unwrap_read().clone() {
36+
if !seen_oids.insert(ext.extn_id.clone()) {
37+
return Err(ext.extn_id);
38+
}
39+
}
40+
41+
Ok(Some(Self(raw_exts.clone())))
42+
}
43+
None => Ok(None),
44+
}
45+
}
46+
47+
/// Retrieves the extension identified by the given OID,
48+
/// or None if the extension is not present (or no extensions are present).
49+
pub fn get_extension(&self, oid: &asn1::ObjectIdentifier) -> Option<Extension> {
50+
let mut extensions = self.0.unwrap_read().clone();
51+
52+
extensions.find(|ext| &ext.extn_id == oid)
53+
}
54+
55+
/// Returns a reference to the underlying extensions.
56+
pub fn as_raw(&self) -> &RawExtensions<'_> {
57+
&self.0
58+
}
59+
}
60+
1561
#[derive(asn1::Asn1Read, asn1::Asn1Write, PartialEq, Eq, Hash, Clone)]
1662
pub struct Extension<'a> {
1763
pub extn_id: asn1::ObjectIdentifier,
@@ -174,3 +220,38 @@ pub struct BasicConstraints {
174220
pub ca: bool,
175221
pub path_length: Option<u64>,
176222
}
223+
224+
#[cfg(test)]
225+
mod tests {
226+
use asn1::SequenceOfWriter;
227+
228+
use crate::oid::{AUTHORITY_KEY_IDENTIFIER_OID, BASIC_CONSTRAINTS_OID};
229+
230+
use super::{BasicConstraints, Extension, Extensions};
231+
232+
#[test]
233+
fn test_get_extension() {
234+
let extension_value = BasicConstraints {
235+
ca: true,
236+
path_length: Some(3),
237+
};
238+
let extension = Extension {
239+
extn_id: BASIC_CONSTRAINTS_OID,
240+
critical: true,
241+
extn_value: &asn1::write_single(&extension_value).unwrap(),
242+
};
243+
let extensions = SequenceOfWriter::new(vec![extension]);
244+
245+
let der = asn1::write_single(&extensions).unwrap();
246+
247+
let extensions: Extensions =
248+
Extensions::from_raw_extensions(Some(&asn1::parse_single(&der).unwrap()))
249+
.unwrap()
250+
.unwrap();
251+
252+
assert!(&extensions.get_extension(&BASIC_CONSTRAINTS_OID).is_some());
253+
assert!(&extensions
254+
.get_extension(&AUTHORITY_KEY_IDENTIFIER_OID)
255+
.is_none());
256+
}
257+
}

src/rust/cryptography-x509/src/ocsp_req.rs

+7-3
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,11 @@
22
// 2.0, and the BSD License. See the LICENSE file in the root of this repository
33
// for complete details.
44

5-
use crate::{common, extensions, name};
5+
use crate::{
6+
common,
7+
extensions::{self},
8+
name,
9+
};
610

711
#[derive(asn1::Asn1Read, asn1::Asn1Write)]
812
pub struct TBSRequest<'a> {
@@ -17,14 +21,14 @@ pub struct TBSRequest<'a> {
1721
asn1::SequenceOfWriter<'a, Request<'a>>,
1822
>,
1923
#[explicit(2)]
20-
pub request_extensions: Option<extensions::Extensions<'a>>,
24+
pub raw_request_extensions: Option<extensions::RawExtensions<'a>>,
2125
}
2226

2327
#[derive(asn1::Asn1Read, asn1::Asn1Write)]
2428
pub struct Request<'a> {
2529
pub req_cert: CertID<'a>,
2630
#[explicit(0)]
27-
pub single_request_extensions: Option<extensions::Extensions<'a>>,
31+
pub single_request_extensions: Option<extensions::RawExtensions<'a>>,
2832
}
2933

3034
#[derive(asn1::Asn1Read, asn1::Asn1Write)]

src/rust/cryptography-x509/src/ocsp_resp.rs

+7-3
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,11 @@
22
// 2.0, and the BSD License. See the LICENSE file in the root of this repository
33
// for complete details.
44

5-
use crate::{certificate, common, crl, extensions, name, ocsp_req};
5+
use crate::{
6+
certificate, common, crl,
7+
extensions::{self},
8+
name, ocsp_req,
9+
};
610

711
#[derive(asn1::Asn1Read, asn1::Asn1Write)]
812
pub struct OCSPResponse<'a> {
@@ -47,7 +51,7 @@ pub struct ResponseData<'a> {
4751
asn1::SequenceOfWriter<'a, SingleResponse<'a>, Vec<SingleResponse<'a>>>,
4852
>,
4953
#[explicit(1)]
50-
pub response_extensions: Option<extensions::Extensions<'a>>,
54+
pub raw_response_extensions: Option<extensions::RawExtensions<'a>>,
5155
}
5256

5357
#[derive(asn1::Asn1Read, asn1::Asn1Write)]
@@ -66,7 +70,7 @@ pub struct SingleResponse<'a> {
6670
#[explicit(0)]
6771
pub next_update: Option<asn1::GeneralizedTime>,
6872
#[explicit(1)]
69-
pub single_extensions: Option<extensions::Extensions<'a>>,
73+
pub raw_single_extensions: Option<extensions::RawExtensions<'a>>,
7074
}
7175

7276
#[derive(asn1::Asn1Read, asn1::Asn1Write)]

src/rust/src/x509/certificate.rs

+18-10
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,13 @@ use crate::error::{CryptographyError, CryptographyResult};
99
use crate::x509::{extensions, sct, sign};
1010
use crate::{exceptions, x509};
1111
use cryptography_x509::common::Asn1ReadableOrWritable;
12+
use cryptography_x509::extensions::Extension;
1213
use cryptography_x509::extensions::{
1314
AuthorityKeyIdentifier, BasicConstraints, DisplayText, DistributionPoint,
1415
DistributionPointName, MSCertificateTemplate, NameConstraints, PolicyConstraints,
15-
PolicyInformation, PolicyQualifierInfo, Qualifier, SequenceOfAccessDescriptions,
16+
PolicyInformation, PolicyQualifierInfo, Qualifier, RawExtensions, SequenceOfAccessDescriptions,
1617
SequenceOfSubtrees, UserNotice,
1718
};
18-
use cryptography_x509::extensions::{Extension, Extensions};
1919
use cryptography_x509::{common, name, oid};
2020
use once_cell::sync::Lazy;
2121
use pyo3::{IntoPy, ToPyObject};
@@ -193,9 +193,9 @@ impl Certificate {
193193
let val = self.raw.borrow_value();
194194
let mut tbs_precert = val.tbs_cert.clone();
195195
// Remove the SCT list extension
196-
match tbs_precert.extensions {
197-
Some(extensions) => {
198-
let readable_extensions = extensions.unwrap_read().clone();
196+
match val.tbs_cert.extensions() {
197+
Ok(Some(extensions)) => {
198+
let readable_extensions = extensions.as_raw().unwrap_read().clone();
199199
let ext_count = readable_extensions.len();
200200
let filtered_extensions: Vec<Extension<'_>> = readable_extensions
201201
.filter(|x| x.extn_id != oid::PRECERT_SIGNED_CERTIFICATE_TIMESTAMPS_OID)
@@ -207,18 +207,26 @@ impl Certificate {
207207
),
208208
));
209209
}
210-
let filtered_extensions: Extensions<'_> = Asn1ReadableOrWritable::new_write(
210+
let filtered_extensions: RawExtensions<'_> = Asn1ReadableOrWritable::new_write(
211211
asn1::SequenceOfWriter::new(filtered_extensions),
212212
);
213-
tbs_precert.extensions = Some(filtered_extensions);
213+
tbs_precert.raw_extensions = Some(filtered_extensions);
214214
let result = asn1::write_single(&tbs_precert)?;
215215
Ok(pyo3::types::PyBytes::new(py, &result))
216216
}
217-
None => Err(CryptographyError::from(
217+
Ok(None) => Err(CryptographyError::from(
218218
pyo3::exceptions::PyValueError::new_err(
219219
"Could not find any extensions in TBS certificate",
220220
),
221221
)),
222+
Err(oid) => {
223+
let oid_obj = oid_to_py_oid(py, &oid)?;
224+
Err(exceptions::DuplicateExtension::new_err((
225+
format!("Duplicate {} extension found", oid),
226+
oid_obj.into_py(py),
227+
))
228+
.into())
229+
}
222230
}
223231
}
224232

@@ -360,7 +368,7 @@ impl Certificate {
360368
x509::parse_and_cache_extensions(
361369
py,
362370
&mut self.cached_extensions,
363-
&self.raw.borrow_value().tbs_cert.extensions,
371+
&self.raw.borrow_value().tbs_cert.raw_extensions,
364372
|oid, ext_data| match *oid {
365373
oid::PRECERT_POISON_OID => {
366374
asn1::parse_single::<()>(ext_data)?;
@@ -1035,7 +1043,7 @@ fn create_x509_certificate(
10351043
spki: asn1::parse_single(spki_bytes)?,
10361044
issuer_unique_id: None,
10371045
subject_unique_id: None,
1038-
extensions: x509::common::encode_extensions(
1046+
raw_extensions: x509::common::encode_extensions(
10391047
py,
10401048
builder.getattr(pyo3::intern!(py, "_extensions"))?,
10411049
extensions::encode_extension,

src/rust/src/x509/common.rs

+16-15
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,10 @@ use crate::asn1::{oid_to_py_oid, py_oid_to_oid};
66
use crate::error::{CryptographyError, CryptographyResult};
77
use crate::{exceptions, x509};
88
use cryptography_x509::common::{Asn1ReadableOrWritable, AttributeTypeValue, RawTlv};
9-
use cryptography_x509::extensions::{AccessDescription, Extension, Extensions};
9+
use cryptography_x509::extensions::{AccessDescription, Extension, Extensions, RawExtensions};
1010
use cryptography_x509::name::{GeneralName, Name, OtherName, UnvalidatedIA5String};
1111
use pyo3::types::IntoPyDict;
1212
use pyo3::{IntoPy, ToPyObject};
13-
use std::collections::HashSet;
1413

1514
/// Parse all sections in a PEM file and return the first matching section.
1615
/// If no matching sections are found, return an error.
@@ -391,27 +390,30 @@ pub(crate) fn parse_and_cache_extensions<
391390
>(
392391
py: pyo3::Python<'p>,
393392
cached_extensions: &mut Option<pyo3::PyObject>,
394-
raw_exts: &Option<Extensions<'_>>,
393+
raw_extensions: &Option<RawExtensions<'_>>,
395394
parse_ext: F,
396395
) -> pyo3::PyResult<pyo3::PyObject> {
397396
if let Some(cached) = cached_extensions {
398397
return Ok(cached.clone_ref(py));
399398
}
400399

400+
let extensions = match Extensions::from_raw_extensions(raw_extensions.as_ref()) {
401+
Ok(extensions) => extensions,
402+
Err(oid) => {
403+
let oid_obj = oid_to_py_oid(py, &oid)?;
404+
return Err(exceptions::DuplicateExtension::new_err((
405+
format!("Duplicate {} extension found", oid),
406+
oid_obj.into_py(py),
407+
)));
408+
}
409+
};
410+
401411
let x509_module = py.import(pyo3::intern!(py, "cryptography.x509"))?;
402412
let exts = pyo3::types::PyList::empty(py);
403-
let mut seen_oids = HashSet::new();
404-
if let Some(raw_exts) = raw_exts {
405-
for raw_ext in raw_exts.unwrap_read().clone() {
413+
if let Some(extensions) = extensions {
414+
for raw_ext in extensions.as_raw().unwrap_read().clone() {
406415
let oid_obj = oid_to_py_oid(py, &raw_ext.extn_id)?;
407416

408-
if seen_oids.contains(&raw_ext.extn_id) {
409-
return Err(exceptions::DuplicateExtension::new_err((
410-
format!("Duplicate {} extension found", raw_ext.extn_id),
411-
oid_obj.into_py(py),
412-
)));
413-
}
414-
415417
let extn_value = match parse_ext(&raw_ext.extn_id, raw_ext.extn_value)? {
416418
Some(e) => e,
417419
None => x509_module.call_method1(
@@ -424,7 +426,6 @@ pub(crate) fn parse_and_cache_extensions<
424426
(oid_obj, raw_ext.critical, extn_value),
425427
)?;
426428
exts.append(ext_obj)?;
427-
seen_oids.insert(raw_ext.extn_id);
428429
}
429430
}
430431
let extensions = x509_module
@@ -445,7 +446,7 @@ pub(crate) fn encode_extensions<
445446
py: pyo3::Python<'p>,
446447
py_exts: &'p pyo3::PyAny,
447448
encode_ext: F,
448-
) -> pyo3::PyResult<Option<Extensions<'p>>> {
449+
) -> pyo3::PyResult<Option<RawExtensions<'p>>> {
449450
let unrecognized_extension_type: &pyo3::types::PyType = py
450451
.import(pyo3::intern!(py, "cryptography.x509"))?
451452
.getattr(pyo3::intern!(py, "UnrecognizedExtension"))?

0 commit comments

Comments
 (0)