Skip to content

Commit b4e43fd

Browse files
committed
Add from_{der,pem}_custom_validator methods
* Allow user to parse otherwise unsupported extensions. * Retain the custom extensions when parsing. * Update to latest branch commit * Ran rustfmt * Fix clippy * Add UnsupportedExtension wrapper type
1 parent eb57cb0 commit b4e43fd

File tree

3 files changed

+86
-8
lines changed

3 files changed

+86
-8
lines changed

rcgen/Cargo.toml

+3-2
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,8 @@ ring = { workspace = true, optional = true }
2828
pem = { workspace = true, optional = true }
2929
pki-types = { workspace = true }
3030
time = { version = "0.3.6", default-features = false }
31-
x509-parser = { workspace = true, features = ["verify"], optional = true }
31+
#x509-parser = { workspace = true, features = ["verify"], optional = true }
32+
x509-parser = { features = ["verify"], optional = true, git = "https://github.com/jean-airoldie/x509-parser", branch = "custom-extension" }
3233
zeroize = { version = "1.2", optional = true }
3334

3435
[features]
@@ -51,7 +52,7 @@ allowed_external_types = [
5152
[dev-dependencies]
5253
openssl = "0.10"
5354
pki-types = { package = "rustls-pki-types", version = "1" }
54-
x509-parser = { workspace = true, features = ["verify"] }
55+
x509-parser = { features = ["verify"], git = "https://github.com/jean-airoldie/x509-parser", branch = "custom-extension" }
5556
rustls-webpki = { version = "0.103", features = ["ring", "std"] }
5657
botan = { version = "0.11", features = ["vendored"] }
5758
ring = { workspace = true }

rcgen/src/csr.rs

+67-2
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ use crate::{
1010
Certificate, CertificateParams, Error, Issuer, PublicKeyData, SignatureAlgorithm, SigningKey,
1111
};
1212
#[cfg(feature = "x509-parser")]
13-
use crate::{DistinguishedName, SanType};
13+
use crate::{CustomExtension, DistinguishedName, SanType};
1414

1515
/// A public key, extracted from a CSR
1616
#[derive(Debug, PartialEq, Eq, Hash)]
@@ -65,6 +65,18 @@ impl From<CertificateSigningRequest> for CertificateSigningRequestDer<'static> {
6565
}
6666
}
6767

68+
/// A unsupported extension.
69+
#[derive(Debug, Clone, Eq, PartialEq)]
70+
#[cfg(feature = "x509-parser")]
71+
pub struct UnsupportedExtension<'a> {
72+
/// The Object ID of the extension.
73+
pub oid: Vec<u64>,
74+
/// The unparsed value.
75+
pub value: &'a [u8],
76+
/// Whether the extension is critical.
77+
pub critical: bool,
78+
}
79+
6880
/// Parameters for a certificate signing request
6981
#[derive(Debug)]
7082
pub struct CertificateSigningRequestParams {
@@ -84,6 +96,23 @@ impl CertificateSigningRequestParams {
8496
Self::from_der(&csr.contents().into())
8597
}
8698

99+
/// Parse a certificate signing request from the ASCII PEM format
100+
/// using the provided validator function to handle unknown extension
101+
/// types.
102+
///
103+
/// The validator function must return an error if the attribute OID or value
104+
/// is incorrect.
105+
///
106+
/// See [`from_der`](Self::from_der) for more details.
107+
#[cfg(all(feature = "pem", feature = "x509-parser"))]
108+
pub fn from_pem_custom_validator<F>(pem_str: &str, valid_fn: F) -> Result<Self, Error>
109+
where
110+
F: FnMut(&UnsupportedExtension<'_>) -> Result<(), Error>,
111+
{
112+
let csr = pem::parse(pem_str).or(Err(Error::CouldNotParseCertificationRequest))?;
113+
Self::from_der_custom_validator(&csr.contents().into(), valid_fn)
114+
}
115+
87116
/// Parse a certificate signing request from DER-encoded bytes
88117
///
89118
/// Currently, this only supports the `Subject Alternative Name` extension.
@@ -96,6 +125,24 @@ impl CertificateSigningRequestParams {
96125
/// [`rustls_pemfile::csr()`]: https://docs.rs/rustls-pemfile/latest/rustls_pemfile/fn.csr.html
97126
#[cfg(feature = "x509-parser")]
98127
pub fn from_der(csr: &CertificateSigningRequestDer<'_>) -> Result<Self, Error> {
128+
Self::from_der_custom_validator(csr, |_| Ok(()))
129+
}
130+
131+
/// Parse a certificate signing request from DER-encoded bytes using the provided
132+
/// validator function to handle unknown extension types.
133+
///
134+
/// The validator function must return an error if the attribute OID or value
135+
/// is incorrect.
136+
///
137+
/// See [`from_der`](Self::from_der) for more details.
138+
#[cfg(feature = "x509-parser")]
139+
pub fn from_der_custom_validator<F>(
140+
csr: &CertificateSigningRequestDer<'_>,
141+
mut valid_fn: F,
142+
) -> Result<Self, Error>
143+
where
144+
F: FnMut(&UnsupportedExtension<'_>) -> Result<(), Error>,
145+
{
99146
use crate::KeyUsagePurpose;
100147
use x509_parser::prelude::FromDer;
101148

@@ -171,7 +218,25 @@ impl CertificateSigningRequestParams {
171218
return Err(Error::UnsupportedExtension);
172219
}
173220
},
174-
_ => return Err(Error::UnsupportedExtension),
221+
x509_parser::extensions::ParsedExtension::UnsupportedExtension(val) => {
222+
let oid: Vec<u64> = match val.oid.iter() {
223+
Some(iter) => iter.collect(),
224+
None => return Err(Error::UnsupportedExtension),
225+
};
226+
let ext = UnsupportedExtension {
227+
oid,
228+
value: val.value,
229+
critical: val.critical,
230+
};
231+
valid_fn(&ext)?;
232+
let mut ext =
233+
CustomExtension::from_oid_content(&ext.oid, val.value.to_vec());
234+
ext.set_criticality(val.critical);
235+
params.custom_extensions.push(ext);
236+
},
237+
_ => {
238+
return Err(Error::UnsupportedExtension);
239+
},
175240
}
176241
}
177242
}

rcgen/tests/generic.rs

+16-4
Original file line numberDiff line numberDiff line change
@@ -80,13 +80,11 @@ mod test_x509_custom_ext {
8080
fn custom_ext() {
8181
// Create an imaginary critical custom extension for testing.
8282
let test_oid = asn1_rs::Oid::from(&[2, 5, 29, 999999]).unwrap();
83+
let oid_vec = test_oid.iter().unwrap().collect::<Vec<u64>>();
8384
let test_ext = yasna::construct_der(|writer| {
8485
writer.write_utf8_string("🦀 greetz to ferris 🦀");
8586
});
86-
let mut custom_ext = CustomExtension::from_oid_content(
87-
test_oid.iter().unwrap().collect::<Vec<u64>>().as_slice(),
88-
test_ext.clone(),
89-
);
87+
let mut custom_ext = CustomExtension::from_oid_content(&oid_vec, test_ext.clone());
9088
custom_ext.set_criticality(true);
9189

9290
// Generate a certificate with the custom extension, parse it with x509-parser.
@@ -132,6 +130,20 @@ mod test_x509_custom_ext {
132130
.expect("missing requested custom extension");
133131
assert!(custom_ext.critical);
134132
assert_eq!(custom_ext.value, test_ext);
133+
134+
let csr_params = rcgen::CertificateSigningRequestParams::from_der_custom_validator(
135+
test_cert_csr.der(),
136+
|ext| {
137+
if &ext.oid != &oid_vec || ext.value != test_ext || !ext.critical {
138+
Err(rcgen::Error::UnsupportedExtension)
139+
} else {
140+
Ok(())
141+
}
142+
},
143+
)
144+
.unwrap();
145+
146+
assert_eq!(csr_params.params, params);
135147
}
136148
}
137149

0 commit comments

Comments
 (0)