From 7240eac3dd9cbd41fcade256e982110bc39fa5a9 Mon Sep 17 00:00:00 2001 From: jean-airoldie <25088801+jean-airoldie@users.noreply.github.com> Date: Sun, 4 May 2025 10:05:57 -0400 Subject: [PATCH] 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 --- rcgen/Cargo.toml | 5 +-- rcgen/src/csr.rs | 69 ++++++++++++++++++++++++++++++++++++++++-- rcgen/tests/generic.rs | 20 +++++++++--- 3 files changed, 86 insertions(+), 8 deletions(-) diff --git a/rcgen/Cargo.toml b/rcgen/Cargo.toml index cfddea99..8dc076ad 100644 --- a/rcgen/Cargo.toml +++ b/rcgen/Cargo.toml @@ -28,7 +28,8 @@ ring = { workspace = true, optional = true } pem = { workspace = true, optional = true } pki-types = { workspace = true } time = { version = "0.3.6", default-features = false } -x509-parser = { workspace = true, features = ["verify"], optional = true } +#x509-parser = { workspace = true, features = ["verify"], optional = true } +x509-parser = { features = ["verify"], optional = true, git = "https://github.com/jean-airoldie/x509-parser", branch = "custom-extension" } zeroize = { version = "1.2", optional = true } [features] @@ -51,7 +52,7 @@ allowed_external_types = [ [dev-dependencies] openssl = "0.10" pki-types = { package = "rustls-pki-types", version = "1" } -x509-parser = { workspace = true, features = ["verify"] } +x509-parser = { features = ["verify"], git = "https://github.com/jean-airoldie/x509-parser", branch = "custom-extension" } rustls-webpki = { version = "0.103", features = ["ring", "std"] } botan = { version = "0.11", features = ["vendored"] } ring = { workspace = true } diff --git a/rcgen/src/csr.rs b/rcgen/src/csr.rs index a6f0d816..192f3fd1 100644 --- a/rcgen/src/csr.rs +++ b/rcgen/src/csr.rs @@ -10,7 +10,7 @@ use crate::{ Certificate, CertificateParams, Error, Issuer, PublicKeyData, SignatureAlgorithm, SigningKey, }; #[cfg(feature = "x509-parser")] -use crate::{DistinguishedName, SanType}; +use crate::{CustomExtension, DistinguishedName, SanType}; /// A public key, extracted from a CSR #[derive(Debug, PartialEq, Eq, Hash)] @@ -65,6 +65,18 @@ impl From for CertificateSigningRequestDer<'static> { } } +/// A unsupported extension. +#[derive(Debug, Clone, Eq, PartialEq)] +#[cfg(feature = "x509-parser")] +pub struct UnsupportedExtension<'a> { + /// The Object ID of the extension. + pub oid: Vec, + /// The unparsed value. + pub value: &'a [u8], + /// Whether the extension is critical. + pub critical: bool, +} + /// Parameters for a certificate signing request #[derive(Debug)] pub struct CertificateSigningRequestParams { @@ -84,6 +96,23 @@ impl CertificateSigningRequestParams { Self::from_der(&csr.contents().into()) } + /// Parse a certificate signing request from the ASCII PEM format + /// using the provided validator function to handle unknown extension + /// types. + /// + /// The validator function must return an error if the attribute OID or value + /// is incorrect. + /// + /// See [`from_der`](Self::from_der) for more details. + #[cfg(all(feature = "pem", feature = "x509-parser"))] + pub fn from_pem_custom_validator(pem_str: &str, valid_fn: F) -> Result + where + F: FnMut(&UnsupportedExtension<'_>) -> Result<(), Error>, + { + let csr = pem::parse(pem_str).or(Err(Error::CouldNotParseCertificationRequest))?; + Self::from_der_custom_validator(&csr.contents().into(), valid_fn) + } + /// Parse a certificate signing request from DER-encoded bytes /// /// Currently, this only supports the `Subject Alternative Name` extension. @@ -96,6 +125,24 @@ impl CertificateSigningRequestParams { /// [`rustls_pemfile::csr()`]: https://docs.rs/rustls-pemfile/latest/rustls_pemfile/fn.csr.html #[cfg(feature = "x509-parser")] pub fn from_der(csr: &CertificateSigningRequestDer<'_>) -> Result { + Self::from_der_custom_validator(csr, |_| Ok(())) + } + + /// Parse a certificate signing request from DER-encoded bytes using the provided + /// validator function to handle unknown extension types. + /// + /// The validator function must return an error if the attribute OID or value + /// is incorrect. + /// + /// See [`from_der`](Self::from_der) for more details. + #[cfg(feature = "x509-parser")] + pub fn from_der_custom_validator( + csr: &CertificateSigningRequestDer<'_>, + mut valid_fn: F, + ) -> Result + where + F: FnMut(&UnsupportedExtension<'_>) -> Result<(), Error>, + { use crate::KeyUsagePurpose; use x509_parser::prelude::FromDer; @@ -171,7 +218,25 @@ impl CertificateSigningRequestParams { return Err(Error::UnsupportedExtension); } }, - _ => return Err(Error::UnsupportedExtension), + x509_parser::extensions::ParsedExtension::UnsupportedExtension(val) => { + let oid: Vec = match val.oid.iter() { + Some(iter) => iter.collect(), + None => return Err(Error::UnsupportedExtension), + }; + let ext = UnsupportedExtension { + oid, + value: val.value, + critical: val.critical, + }; + valid_fn(&ext)?; + let mut ext = + CustomExtension::from_oid_content(&ext.oid, val.value.to_vec()); + ext.set_criticality(val.critical); + params.custom_extensions.push(ext); + }, + _ => { + return Err(Error::UnsupportedExtension); + }, } } } diff --git a/rcgen/tests/generic.rs b/rcgen/tests/generic.rs index ed1a8abc..9f14c9a4 100644 --- a/rcgen/tests/generic.rs +++ b/rcgen/tests/generic.rs @@ -80,13 +80,11 @@ mod test_x509_custom_ext { fn custom_ext() { // Create an imaginary critical custom extension for testing. let test_oid = asn1_rs::Oid::from(&[2, 5, 29, 999999]).unwrap(); + let oid_vec = test_oid.iter().unwrap().collect::>(); let test_ext = yasna::construct_der(|writer| { writer.write_utf8_string("🦀 greetz to ferris 🦀"); }); - let mut custom_ext = CustomExtension::from_oid_content( - test_oid.iter().unwrap().collect::>().as_slice(), - test_ext.clone(), - ); + let mut custom_ext = CustomExtension::from_oid_content(&oid_vec, test_ext.clone()); custom_ext.set_criticality(true); // Generate a certificate with the custom extension, parse it with x509-parser. @@ -132,6 +130,20 @@ mod test_x509_custom_ext { .expect("missing requested custom extension"); assert!(custom_ext.critical); assert_eq!(custom_ext.value, test_ext); + + let csr_params = rcgen::CertificateSigningRequestParams::from_der_custom_validator( + test_cert_csr.der(), + |ext| { + if ext.oid != oid_vec || ext.value != test_ext || !ext.critical { + Err(rcgen::Error::UnsupportedExtension) + } else { + Ok(()) + } + }, + ) + .unwrap(); + + assert_eq!(csr_params.params, params); } }