From 0a488d164bf855884732ec62ffb10ff0c505c1e7 Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Sun, 9 Aug 2026 15:01:53 +0200 Subject: [PATCH 1/4] Deduplicate eKU writing logic --- rcgen/src/certificate.rs | 16 +--------------- 1 file changed, 1 insertion(+), 15 deletions(-) diff --git a/rcgen/src/certificate.rs b/rcgen/src/certificate.rs index ce34495e..3caf6781 100644 --- a/rcgen/src/certificate.rs +++ b/rcgen/src/certificate.rs @@ -507,23 +507,9 @@ impl CertificateParams { ); } - // Write subject_alt_names self.write_subject_alt_names(writer.next()); - - // Write standard key usage self.write_key_usage(writer.next()); - - // Write extended key usage - if !self.extended_key_usages.is_empty() { - write_x509_extension(writer.next(), oid::EXT_KEY_USAGE, false, |writer| { - writer.write_sequence(|writer| { - for usage in self.extended_key_usages.iter() { - let oid = ObjectIdentifier::from_slice(usage.oid()); - writer.next().write_oid(&oid); - } - }); - }); - } + self.write_extended_key_usage(writer.next()); if let Some(name_constraints) = &self.name_constraints { // If both trees are empty, the extension must be omitted. From f0f24c129e722b098408dca279aebce3dcaf8e48 Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Sun, 9 Aug 2026 15:10:05 +0200 Subject: [PATCH 2/4] Deduplicate CA extensions writing logic --- rcgen/src/certificate.rs | 64 ++++++++++------------------------------ 1 file changed, 15 insertions(+), 49 deletions(-) diff --git a/rcgen/src/certificate.rs b/rcgen/src/certificate.rs index 3caf6781..648b27fa 100644 --- a/rcgen/src/certificate.rs +++ b/rcgen/src/certificate.rs @@ -195,15 +195,10 @@ impl CertificateParams { )); writer.next().write_set(|writer| { writer.next().write_sequence(|writer| { - // Write key_usage self.write_key_usage(writer.next()); - // Write subject_alt_names self.write_subject_alt_names(writer.next()); self.write_extended_key_usage(writer.next()); - // Write is_ca - self.write_is_ca(writer.next()); - - // Write custom extensions + self.write_ca_extensions(writer, None); for ext in &self.custom_extensions { write_x509_extension(writer.next(), &ext.oid, ext.critical, |writer| { writer.write_der(ext.content()) @@ -254,15 +249,26 @@ impl CertificateParams { } /// Write a certificate's BasicConstraints as defined in RFC 5280. - fn write_is_ca(&self, writer: DERWriter) { + fn write_ca_extensions(&self, writer: &mut DERWriterSeq, pub_key_spki: Option<&[u8]>) { let is_ca = match &self.is_ca { IsCa::Ca(bc) => Some(bc), IsCa::ExplicitNoCa => None, IsCa::NoCa => return, }; + if let Some(pub_key_spki) = pub_key_spki { + write_x509_extension( + writer.next(), + oid::SUBJECT_KEY_IDENTIFIER, + false, + |writer| { + writer.write_bytes(&self.key_identifier_method.derive(pub_key_spki)); + }, + ); + } + // Write basic_constraints - write_x509_extension(writer, oid::BASIC_CONSTRAINTS, true, |writer| { + write_x509_extension(writer.next(), oid::BASIC_CONSTRAINTS, true, |writer| { writer.write_sequence(|writer| { writer.next().write_bool(is_ca.is_some()); // cA flag if let Some(BasicConstraints::Constrained(path_len_constraint)) = is_ca { @@ -550,48 +556,8 @@ impl CertificateParams { ); } - match self.is_ca { - IsCa::Ca(ref constraint) => { - // Write subject_key_identifier - write_x509_extension( - writer.next(), - oid::SUBJECT_KEY_IDENTIFIER, - false, - |writer| { - writer.write_bytes(&self.key_identifier_method.derive(pub_key_spki)); - }, - ); - // Write basic_constraints - write_x509_extension(writer.next(), oid::BASIC_CONSTRAINTS, true, |writer| { - writer.write_sequence(|writer| { - writer.next().write_bool(true); // cA flag - if let BasicConstraints::Constrained(path_len_constraint) = constraint { - writer.next().write_u8(*path_len_constraint); - } - }); - }); - }, - IsCa::ExplicitNoCa => { - // Write subject_key_identifier - write_x509_extension( - writer.next(), - oid::SUBJECT_KEY_IDENTIFIER, - false, - |writer| { - writer.write_bytes(&self.key_identifier_method.derive(pub_key_spki)); - }, - ); - // Write basic_constraints - write_x509_extension(writer.next(), oid::BASIC_CONSTRAINTS, true, |writer| { - writer.write_sequence(|writer| { - writer.next().write_bool(false); // cA flag - }); - }); - }, - IsCa::NoCa => {}, - } + self.write_ca_extensions(writer, Some(pub_key_spki)); - // Write the custom extensions for ext in &self.custom_extensions { write_x509_extension(writer.next(), &ext.oid, ext.critical, |writer| { writer.write_der(ext.content()) From 0886b4124b4fa9d695600121de047ff4e1ee06f6 Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Sun, 9 Aug 2026 13:42:50 +0200 Subject: [PATCH 3/4] Omit default cA flag value as required by spec --- rcgen/src/certificate.rs | 49 +++++++++++++++++++++++++++++++++++++--- 1 file changed, 46 insertions(+), 3 deletions(-) diff --git a/rcgen/src/certificate.rs b/rcgen/src/certificate.rs index 648b27fa..47016720 100644 --- a/rcgen/src/certificate.rs +++ b/rcgen/src/certificate.rs @@ -270,9 +270,16 @@ impl CertificateParams { // Write basic_constraints write_x509_extension(writer.next(), oid::BASIC_CONSTRAINTS, true, |writer| { writer.write_sequence(|writer| { - writer.next().write_bool(is_ca.is_some()); // cA flag - if let Some(BasicConstraints::Constrained(path_len_constraint)) = is_ca { - writer.next().write_u8(*path_len_constraint); // pathLenConstraint integer + let Some(constraints) = is_ca else { + return; + }; + + writer.next().write_bool(true); // cA flag + match constraints { + BasicConstraints::Unconstrained => {}, + BasicConstraints::Constrained(path_len_constraint) => { + writer.next().write_u8(*path_len_constraint); // pathLenConstraint integer + }, } }); }); @@ -1094,6 +1101,8 @@ mod tests { #[cfg(feature = "x509-parser")] use pki_types::pem::PemObject; + #[cfg(feature = "crypto")] + use x509_parser::oid_registry::OID_X509_EXT_BASIC_CONSTRAINTS; #[cfg(feature = "pem")] use super::*; @@ -1147,6 +1156,40 @@ mod tests { assert!(found); } + #[cfg(feature = "crypto")] + #[test] + fn test_explicit_no_ca() { + let params = CertificateParams { + is_ca: IsCa::ExplicitNoCa, + ..CertificateParams::default() + }; + + // Make the cert + let key_pair = KeyPair::generate().unwrap(); + let cert = params.self_signed(&key_pair).unwrap(); + + // Parse it + let (_rem, cert) = x509_parser::parse_x509_certificate(cert.der()).unwrap(); + + // Check the basic constraints extension + let mut found = false; + for ext in cert.extensions() { + if ext.oid == OID_X509_EXT_BASIC_CONSTRAINTS { + // The cA flag is DEFAULT FALSE, so DER requires it to be omitted: + // the extension value must be an empty SEQUENCE + assert_eq!(ext.value, vec![0x30, 0x00]); + if let x509_parser::extensions::ParsedExtension::BasicConstraints(bc) = + ext.parsed_extension() + { + assert!(!bc.ca); + found = true; + } + } + } + + assert!(found); + } + #[cfg(feature = "crypto")] #[test] fn test_with_key_usages_decipheronly_only() { From 12a686ba04d5d18a2061a7594f8eb54237024ddf Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Sun, 9 Aug 2026 13:43:15 +0200 Subject: [PATCH 4/4] rcgen: bump version to 0.14.9 --- Cargo.lock | 2 +- rcgen/Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 35a0d3c8..ca842743 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -804,7 +804,7 @@ checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" [[package]] name = "rcgen" -version = "0.14.8" +version = "0.14.9" dependencies = [ "aws-lc-rs", "openssl", diff --git a/rcgen/Cargo.toml b/rcgen/Cargo.toml index 108662ee..d46cd8c2 100644 --- a/rcgen/Cargo.toml +++ b/rcgen/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rcgen" -version = "0.14.8" +version = "0.14.9" documentation = "https://docs.rs/rcgen" description.workspace = true repository.workspace = true