diff --git a/src/lib.rs b/src/lib.rs index 7742e1c6..ceaad18d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -911,7 +911,7 @@ impl CertificateParams { // Write extensions // According to the spec in RFC 2986, even if attributes are empty we need the empty attribute tag writer.next().write_tagged(Tag::context(0), |writer| { - if !subject_alt_names.is_empty() { + if !subject_alt_names.is_empty() || !custom_extensions.is_empty() { writer.write_sequence(|writer| { let oid = ObjectIdentifier::from_slice(OID_PKCS_9_AT_EXTENSION_REQUEST); writer.next().write_oid(&oid); @@ -922,17 +922,12 @@ impl CertificateParams { // Write custom extensions for ext in custom_extensions { - writer.next().write_sequence(|writer| { - let oid = ObjectIdentifier::from_slice(&ext.oid); - writer.next().write_oid(&oid); - // If the extension is critical, we should signal this. - // It's false by default so we don't need to write anything - // if the extension is not critical. - if ext.critical { - writer.next().write_bool(true); - } - writer.next().write_bytes(&ext.content); - }); + write_x509_extension( + writer.next(), + &ext.oid, + ext.critical, + |writer| writer.write_der(ext.content()), + ); } }); }); @@ -999,11 +994,7 @@ impl CertificateParams { // Write standard key usage if !self.key_usages.is_empty() { - writer.next().write_sequence(|writer| { - let oid = ObjectIdentifier::from_slice(OID_KEY_USAGE); - writer.next().write_oid(&oid); - writer.next().write_bool(true); - + write_x509_extension(writer.next(), OID_KEY_USAGE, true, |writer| { let mut bits: u16 = 0; for entry in self.key_usages.iter() { @@ -1032,12 +1023,7 @@ impl CertificateParams { // Finally take only the bytes != 0 let bits = &bits[..nb]; - let der = yasna::construct_der(|writer| { - writer.write_bitvec_bytes(&bits, msb as usize) - }); - - // Write them - writer.next().write_bytes(&der); + writer.write_bitvec_bytes(&bits, msb as usize) }); } @@ -1157,16 +1143,8 @@ impl CertificateParams { // Write the custom extensions for ext in &self.custom_extensions { - writer.next().write_sequence(|writer| { - let oid = ObjectIdentifier::from_slice(&ext.oid); - writer.next().write_oid(&oid); - // If the extension is critical, we should signal this. - // It's false by default so we don't need to write anything - // if the extension is not critical. - if ext.critical { - writer.next().write_bool(true); - } - writer.next().write_bytes(&ext.content); + write_x509_extension(writer.next(), &ext.oid, ext.critical, |writer| { + writer.write_der(ext.content()) }); } }); diff --git a/tests/generic.rs b/tests/generic.rs index 647eb7df..2b158c7c 100644 --- a/tests/generic.rs +++ b/tests/generic.rs @@ -84,6 +84,77 @@ mod test_convert_x509_subject_alternative_name { } } +#[cfg(feature = "x509-parser")] +mod test_x509_custom_ext { + use crate::util; + use crate::Certificate; + + use rcgen::CustomExtension; + use x509_parser::oid_registry::asn1_rs; + use x509_parser::prelude::{ + FromDer, ParsedCriAttribute, X509Certificate, X509CertificationRequest, + }; + + #[test] + fn custom_ext() { + // Create an imaginary critical custom extension for testing. + let test_oid = asn1_rs::Oid::from(&[2, 5, 29, 999999]).unwrap(); + 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(), + ); + custom_ext.set_criticality(true); + + // Generate a certificate with the custom extension, parse it with x509-parser. + let mut params = util::default_params(); + params.custom_extensions = vec![custom_ext]; + // Ensure the custom exts. being omitted into a CSR doesn't require SAN ext being present. + // See https://github.com/rustls/rcgen/issues/122 + params.subject_alt_names = Vec::default(); + let test_cert = Certificate::from_params(params).unwrap(); + let test_cert_der = test_cert.serialize_der().unwrap(); + let (_, x509_test_cert) = X509Certificate::from_der(&test_cert_der).unwrap(); + + // We should be able to find the extension by OID, with expected criticality and value. + let favorite_drink_ext = x509_test_cert + .get_extension_unique(&test_oid) + .expect("invalid extensions") + .expect("missing custom extension"); + assert_eq!(favorite_drink_ext.critical, true); + assert_eq!(favorite_drink_ext.value, test_ext); + + // Generate a CSR with the custom extension, parse it with x509-parser. + let test_cert_csr_der = test_cert.serialize_request_der().unwrap(); + let (_, x509_csr) = X509CertificationRequest::from_der(&test_cert_csr_der).unwrap(); + + // We should find that the CSR contains requested extensions. + // Note: we can't use `x509_csr.requested_extensions()` here because it maps the raw extension + // request extensions to their parsed form, and of course x509-parser doesn't parse our custom extension. + let exts = x509_csr + .certification_request_info + .iter_attributes() + .find_map(|attr| { + if let ParsedCriAttribute::ExtensionRequest(requested) = &attr.parsed_attribute() { + Some(requested.extensions.iter().collect::>()) + } else { + None + } + }) + .expect("missing requested extensions"); + + // We should find the custom extension with expected criticality and value. + let custom_ext = exts + .iter() + .find(|ext| ext.oid == test_oid) + .expect("missing requested custom extension"); + assert_eq!(custom_ext.critical, true); + assert_eq!(custom_ext.value, test_ext); + } +} + #[cfg(feature = "x509-parser")] mod test_x509_parser_crl { use crate::util;