From e8348abc3666e4fde02d67ccd887b878343a2cfb Mon Sep 17 00:00:00 2001 From: Daniel McCarney Date: Sun, 10 Sep 2023 21:22:21 -0400 Subject: [PATCH] csr: support populating custom extensions from CSR. This commit updates the CSR parsing to populate unknown requested extensions as custom extensions in the built CSR `CertificateParams.custom_extensions`. --- src/csr.rs | 38 +++++++++++++++++++++++++++++++++----- tests/generic.rs | 16 ++++++++++++++-- 2 files changed, 47 insertions(+), 7 deletions(-) diff --git a/src/csr.rs b/src/csr.rs index 62e2d633..0fc36d21 100644 --- a/src/csr.rs +++ b/src/csr.rs @@ -1,5 +1,5 @@ #[cfg(feature = "x509-parser")] -use crate::{DistinguishedName, SanType}; +use crate::{CustomExtension, DistinguishedName, SanType}; #[cfg(feature = "pem")] use pem::Pem; use std::hash::Hash; @@ -67,20 +67,48 @@ impl CertificateSigningRequest { params.distinguished_name = DistinguishedName::from_name(&info.subject)?; let raw = info.subject_pki.subject_public_key.data.to_vec(); - if let Some(extensions) = csr.requested_extensions() { - for ext in extensions { - match ext { + // Pull out the extension requests attributes from the CSR. + // Note: we avoid using csr.requested_extensions() here because it maps to the parsed + // extension value and we want the raw extension value to handle unknown extensions + // ourselves. + let requested_exts = csr + .certification_request_info + .iter_attributes() + .filter_map(|attr| { + if let x509_parser::prelude::ParsedCriAttribute::ExtensionRequest(requested) = + &attr.parsed_attribute() + { + Some(requested.extensions.iter().collect::>()) + } else { + None + } + }) + .flatten() + .collect::>(); + + if !requested_exts.is_empty() { + for ext in requested_exts { + let supported = match ext.parsed_extension() { x509_parser::extensions::ParsedExtension::SubjectAlternativeName(san) => { for name in &san.general_names { params .subject_alt_names .push(SanType::try_from_general(name)?); } + true }, x509_parser::extensions::ParsedExtension::SubjectKeyIdentifier(ski) => { params.key_identifier = ski.0.to_vec(); + true }, - _ => return Err(RcgenError::UnsupportedExtension), + _ => false, + }; + if !supported { + params.custom_extensions.push(CustomExtension { + oid: ext.oid.iter().unwrap().collect(), + critical: ext.critical, + content: ext.value.to_vec(), + }) } } } diff --git a/tests/generic.rs b/tests/generic.rs index 0ea21bc6..8b7f9bdd 100644 --- a/tests/generic.rs +++ b/tests/generic.rs @@ -335,7 +335,7 @@ mod test_csr_exts { mod test_x509_custom_ext { use crate::util; - use rcgen::{Certificate, CustomExtension}; + use rcgen::{Certificate, CertificateSigningRequest, CustomExtension}; use x509_parser::oid_registry::asn1_rs; use x509_parser::prelude::{ FromDer, ParsedCriAttribute, X509Certificate, X509CertificationRequest, @@ -396,8 +396,20 @@ mod test_x509_custom_ext { .iter() .find(|ext| ext.oid == test_oid) .expect("missing requested custom extension"); - assert_eq!(custom_ext.critical, true); + assert!(custom_ext.critical); assert_eq!(custom_ext.value, test_ext); + + // We should be able to create an rcgen CSR from the serialized CSR. + let rcgen_csr = CertificateSigningRequest::from_der(&test_cert_csr_der).unwrap(); + // The custom extensions should be present in the CSR. + let custom_ext = rcgen_csr + .params + .custom_extensions + .iter() + .find(|ext| Iterator::eq(ext.oid_components(), test_oid.iter().unwrap())) + .expect("missing requested custom extension"); + assert!(custom_ext.criticality()); + assert_eq!(custom_ext.content(), test_ext); } }