From 88243ef9012eb4db166732c4290e54b6d6294a86 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 | 39 ++++++++++++++++++++++++++++++++++----- tests/generic.rs | 16 ++++++++++++++-- 2 files changed, 48 insertions(+), 7 deletions(-) diff --git a/src/csr.rs b/src/csr.rs index 243b174f..017eb756 100644 --- a/src/csr.rs +++ b/src/csr.rs @@ -4,7 +4,9 @@ use crate::{DistinguishedName, SanType}; use pem::Pem; use std::hash::Hash; -use crate::{Certificate, CertificateParams, PublicKeyData, RcgenError, SignatureAlgorithm}; +use crate::{ + Certificate, CertificateParams, CustomExtension, PublicKeyData, RcgenError, SignatureAlgorithm, +}; /// A public key, extracted from a CSR #[derive(Debug, PartialEq, Eq, Hash)] @@ -66,17 +68,44 @@ 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 }, - _ => 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 3c433f41..653aca01 100644 --- a/tests/generic.rs +++ b/tests/generic.rs @@ -331,7 +331,7 @@ mod test_x509_custom_ext { use crate::util; use crate::Certificate; - use rcgen::CustomExtension; + use rcgen::{CertificateSigningRequest, CustomExtension}; use x509_parser::oid_registry::asn1_rs; use x509_parser::prelude::{ FromDer, ParsedCriAttribute, X509Certificate, X509CertificationRequest, @@ -392,8 +392,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); } }