From bbef96a03fdcdbc6cc8db7ae547ee665a87d0b86 Mon Sep 17 00:00:00 2001 From: Gabgobie <105999094+Gabgobie@users.noreply.github.com> Date: Thu, 26 Feb 2026 23:56:52 +0100 Subject: [PATCH 1/2] Add InhibitAnyPolicy extension as prerequisite to CertificatePolicies InhibitAnyPolicy is used to limit the use of the special policy anypolicy that circumvents validation. Policies must be validated for example when deciding the level of trust to put into a certificate. Common policies include 2.23.140.1.2 which denotes the validation procedure. 2.23.140.1.2.1 for example is "Domain Validation" See also https://oid-base.com/get/2.23.140.1.2.1 or inspect a LetsEncrypt certificate --- rcgen/src/certificate.rs | 97 ++++++++++++++++++++++++++++++++++++++++ rcgen/src/lib.rs | 3 +- rcgen/src/oid.rs | 3 ++ 3 files changed, 102 insertions(+), 1 deletion(-) diff --git a/rcgen/src/certificate.rs b/rcgen/src/certificate.rs index e34abd3f..941c1aea 100644 --- a/rcgen/src/certificate.rs +++ b/rcgen/src/certificate.rs @@ -61,6 +61,7 @@ pub struct CertificateParams { pub distinguished_name: DistinguishedName, pub is_ca: IsCa, pub key_usages: Vec, + pub inhibit_any_policy: Option, pub extended_key_usages: Vec, pub name_constraints: Option, /// An optional list of certificate revocation list (CRL) distribution points as described @@ -93,6 +94,7 @@ impl Default for CertificateParams { distinguished_name, is_ca: IsCa::NoCa, key_usages: Vec::new(), + inhibit_any_policy: None, extended_key_usages: Vec::new(), name_constraints: None, crl_distribution_points: Vec::new(), @@ -181,6 +183,7 @@ impl CertificateParams { distinguished_name: DistinguishedName::from_name(&x509.tbs_certificate.subject)?, not_before: x509.validity().not_before.to_datetime(), not_after: x509.validity().not_after.to_datetime(), + inhibit_any_policy: InhibitAnyPolicy::from_x509(&x509)?, ..Default::default() }) } @@ -351,6 +354,7 @@ impl CertificateParams { distinguished_name, is_ca, key_usages, + inhibit_any_policy, extended_key_usages, name_constraints, crl_distribution_points, @@ -373,6 +377,7 @@ impl CertificateParams { ); if serial_number.is_some() || name_constraints.is_some() + || inhibit_any_policy.is_some() || !crl_distribution_points.is_empty() || *use_authority_key_identifier_extension { @@ -473,6 +478,7 @@ impl CertificateParams { || self.name_constraints.iter().any(|c| !c.is_empty()) || matches!(self.is_ca, IsCa::ExplicitNoCa) || matches!(self.is_ca, IsCa::Ca(_)) + || self.inhibit_any_policy.is_some() || !self.custom_extensions.is_empty(); if !should_write_exts { return Ok(()); @@ -605,6 +611,12 @@ impl CertificateParams { IsCa::NoCa => {}, } + if let Some(inhibit_any_policy) = &self.inhibit_any_policy { + writer.next().write_der(&yasna::construct_der(|writer| { + inhibit_any_policy.encode_der(writer) + })) + } + // Write the custom extensions for ext in &self.custom_extensions { write_x509_extension(writer.next(), &ext.oid, ext.critical, |writer| { @@ -656,6 +668,62 @@ fn write_general_subtrees(writer: DERWriter, tag: u64, general_subtrees: &[Gener }); } +/// Excerpt from [RFC5280 Section 4.2.1.14](https://www.rfc-editor.org/rfc/rfc5280#section-4.2.1.14) +/// +/// > The inhibit anyPolicy extension can be used in certificates issued to +/// > CAs. The inhibit anyPolicy extension indicates that the special +/// > anyPolicy OID, with the value { 2 5 29 32 0 }, is not considered an +/// > explicit match for other certificate policies except when it appears +/// > in an intermediate self-issued CA certificate. The value indicates +/// > the number of additional non-self-issued certificates that may appear +/// > in the path before anyPolicy is no longer permitted. For example, a +/// > value of one indicates that anyPolicy may be processed in +/// > certificates issued by the subject of this certificate, but not in +/// > additional certificates in the path. +/// > +/// > > Conforming CAs MUST mark this extension as critical. +/// > > +/// > > id-ce-inhibitAnyPolicy OBJECT IDENTIFIER ::= { id-ce 54 } +/// > > +/// > > InhibitAnyPolicy ::= SkipCerts +/// > > +/// > > SkipCerts ::= INTEGER (0..MAX) +#[derive(Debug, PartialEq, Eq, Clone)] +pub struct InhibitAnyPolicy { + /// The number of additional non-self-issued certificates that may appear + /// in the path before anyPolicy is no longer permitted. + pub skip_certs: u32, +} + +#[cfg(all(test, feature = "x509-parser"))] +impl InhibitAnyPolicy { + fn from_x509( + x509: &x509_parser::certificate::X509Certificate<'_>, + ) -> Result, Error> { + let inhibit_any_policy = x509 + .inhibit_anypolicy() + .map_err(|_| Error::CouldNotParseCertificate)?; + + let Some(inhibit_any_policy) = inhibit_any_policy else { + return Ok(None); + }; + + Ok(Some(Self { + skip_certs: inhibit_any_policy.value.skip_certs, + })) + } +} + +// impl yasna::DEREncodable for InhibitAnyPolicy { +impl InhibitAnyPolicy { + fn encode_der<'a>(&self, writer: DERWriter<'a>) { + // Conforming CAs MUST mark this extension as critical. + write_x509_extension(writer, oid::INHIBIT_ANY_POLICY, true, |writer| { + writer.write_u32(self.skip_certs) + }); + } +} + /// A PKCS #10 CSR attribute, as defined in [RFC 5280] and constrained /// by [RFC 2986]. /// @@ -1152,6 +1220,35 @@ mod tests { #[cfg(feature = "crypto")] use crate::KeyPair; + #[cfg(feature = "crypto")] + #[test] + fn test_inhibit_any_policy_expected_der() { + const EXPECTED_DER: &[u8] = &[ + 0x30, 0x0D, 0x06, 0x03, 0x55, 0x1D, 0x36, 0x01, 0x01, 0xFF, 0x04, 0x03, 0x02, 0x01, + 0x02, + ]; + let extension_der = + yasna::construct_der(|writer| InhibitAnyPolicy { skip_certs: 2 }.encode_der(writer)); + assert_eq!(EXPECTED_DER, &extension_der) + } + + #[cfg(feature = "crypto")] + #[cfg(feature = "x509-parser")] + #[test] + fn test_inhibit_any_policy_encode_decode() { + let params = CertificateParams { + inhibit_any_policy: Some(InhibitAnyPolicy { skip_certs: 2 }), + ..Default::default() + }; + + let key_pair = KeyPair::generate().unwrap(); + let cert = params.self_signed(&key_pair).unwrap(); + + let parsed = CertificateParams::from_ca_cert_der(cert.der()) + .expect("We should be able to parse the certificate we just created"); + assert_eq!(params.inhibit_any_policy, parsed.inhibit_any_policy,) + } + #[cfg(feature = "crypto")] #[test] fn test_with_key_usages() { diff --git a/rcgen/src/lib.rs b/rcgen/src/lib.rs index 83816182..ce76086d 100644 --- a/rcgen/src/lib.rs +++ b/rcgen/src/lib.rs @@ -43,7 +43,8 @@ use std::ops::Deref; pub use certificate::{ date_time_ymd, Attribute, BasicConstraints, Certificate, CertificateParams, CidrSubnet, - CustomExtension, DnType, ExtendedKeyUsagePurpose, GeneralSubtree, IsCa, NameConstraints, + CustomExtension, DnType, ExtendedKeyUsagePurpose, GeneralSubtree, InhibitAnyPolicy, IsCa, + NameConstraints, }; pub use crl::{ CertificateRevocationList, CertificateRevocationListParams, CrlDistributionPoint, diff --git a/rcgen/src/oid.rs b/rcgen/src/oid.rs index 3b1c0eb9..9f13cdb0 100644 --- a/rcgen/src/oid.rs +++ b/rcgen/src/oid.rs @@ -41,6 +41,9 @@ pub(crate) const RSASSA_PSS: &[u64] = &[1, 2, 840, 113549, 1, 1, 10]; /// id-ce-keyUsage in [RFC 5280](https://tools.ietf.org/html/rfc5280#appendix-A.2) pub(crate) const KEY_USAGE: &[u64] = &[2, 5, 29, 15]; +/// id-ce-inhibitAnyPolicy in [RFC 5280](https://www.rfc-editor.org/rfc/rfc5280#section-4.2.1.14) +pub(crate) const INHIBIT_ANY_POLICY: &[u64] = &[2, 5, 29, 54]; + /// id-ce-subjectAltName in [RFC 5280](https://tools.ietf.org/html/rfc5280#appendix-A.2) pub(crate) const SUBJECT_ALT_NAME: &[u64] = &[2, 5, 29, 17]; From af5b4c0901306abdd3bdbfd4d845e43f57da1fe8 Mon Sep 17 00:00:00 2001 From: Gabgobie <105999094+Gabgobie@users.noreply.github.com> Date: Fri, 27 Feb 2026 03:55:09 +0100 Subject: [PATCH 2/2] Add CertificatePolicies extension --- rcgen/src/certificate.rs | 367 +++++++++++++++++++++++++++++++++++++++ rcgen/src/error.rs | 10 ++ rcgen/src/lib.rs | 7 +- rcgen/src/oid.rs | 3 + 4 files changed, 384 insertions(+), 3 deletions(-) diff --git a/rcgen/src/certificate.rs b/rcgen/src/certificate.rs index 941c1aea..ef05e73a 100644 --- a/rcgen/src/certificate.rs +++ b/rcgen/src/certificate.rs @@ -1,3 +1,4 @@ +use std::collections::HashSet; use std::net::IpAddr; use std::str::FromStr; @@ -61,6 +62,7 @@ pub struct CertificateParams { pub distinguished_name: DistinguishedName, pub is_ca: IsCa, pub key_usages: Vec, + pub certificate_policies: Option, pub inhibit_any_policy: Option, pub extended_key_usages: Vec, pub name_constraints: Option, @@ -94,6 +96,7 @@ impl Default for CertificateParams { distinguished_name, is_ca: IsCa::NoCa, key_usages: Vec::new(), + certificate_policies: None, inhibit_any_policy: None, extended_key_usages: Vec::new(), name_constraints: None, @@ -183,6 +186,7 @@ impl CertificateParams { distinguished_name: DistinguishedName::from_name(&x509.tbs_certificate.subject)?, not_before: x509.validity().not_before.to_datetime(), not_after: x509.validity().not_after.to_datetime(), + certificate_policies: CertificatePolicies::from_x509(&x509)?, inhibit_any_policy: InhibitAnyPolicy::from_x509(&x509)?, ..Default::default() }) @@ -354,6 +358,7 @@ impl CertificateParams { distinguished_name, is_ca, key_usages, + certificate_policies, inhibit_any_policy, extended_key_usages, name_constraints, @@ -377,6 +382,7 @@ impl CertificateParams { ); if serial_number.is_some() || name_constraints.is_some() + || certificate_policies.is_some() || inhibit_any_policy.is_some() || !crl_distribution_points.is_empty() || *use_authority_key_identifier_extension @@ -478,6 +484,7 @@ impl CertificateParams { || self.name_constraints.iter().any(|c| !c.is_empty()) || matches!(self.is_ca, IsCa::ExplicitNoCa) || matches!(self.is_ca, IsCa::Ca(_)) + || self.certificate_policies.is_some() || self.inhibit_any_policy.is_some() || !self.custom_extensions.is_empty(); if !should_write_exts { @@ -611,6 +618,12 @@ impl CertificateParams { IsCa::NoCa => {}, } + if let Some(certificate_policies) = &self.certificate_policies { + writer.next().write_der(&yasna::construct_der(|writer| { + certificate_policies.encode_der(writer); + })) + } + if let Some(inhibit_any_policy) = &self.inhibit_any_policy { writer.next().write_der(&yasna::construct_der(|writer| { inhibit_any_policy.encode_der(writer) @@ -668,6 +681,227 @@ fn write_general_subtrees(writer: DERWriter, tag: u64, general_subtrees: &[Gener }); } +/// The [Certificate Policies extension](https://www.rfc-editor.org/rfc/rfc5280#section-4.2.1.4) +/// +/// This qualifier SHOULD only be present in end entity certificates and CA certificates issued to other organizations +#[derive(Debug, PartialEq, Eq, Clone)] +pub struct CertificatePolicies { + /// Applications with specific policy requirements are expected to have a list of those policies + /// that they will accept and to compare the policy OIDs in the certificate to that list. If this + /// extension is critical, the path validation software MUST be able to interpret this extension + /// (including the optional qualifier), or MUST reject the certificate. + pub critical: bool, + /// A sequence of one or more policy information terms + /// + /// A certificate policy OID MUST NOT appear more than once in a + /// certificate policies extension. + policy_information: Vec, +} + +impl CertificatePolicies { + #[cfg(all(test, feature = "x509-parser"))] + fn from_x509( + x509: &x509_parser::certificate::X509Certificate<'_>, + ) -> Result, Error> { + use x509_parser::extensions::ParsedExtension; + use x509_parser::oid_registry::OID_X509_EXT_CERTIFICATE_POLICIES; + + let ext = x509 + .get_extension_unique(&OID_X509_EXT_CERTIFICATE_POLICIES) + .map_err(|_| Error::CouldNotParseCertificate)?; + + let Some(ext) = ext else { + return Ok(None); + }; + + let ParsedExtension::CertificatePolicies(policies) = ext.parsed_extension() else { + return Err(Error::X509("A CertificatePolicies extension was found by OID but not parsed into the expected type.".to_string())); + }; + + let mut policy_information: Vec = Vec::with_capacity(policies.len()); + for policy in policies.iter().cloned() { + policy_information.push(PolicyInformation::from_x509(policy)?); + } + + Ok(Some(Self { + critical: ext.critical, + policy_information, + })) + } +} + +// impl yasna::DEREncodable for CertificatePolicies { +impl CertificatePolicies { + fn encode_der<'a>(&self, writer: DERWriter<'a>) { + write_x509_extension(writer, oid::CERTIFICATE_POLICIES, self.critical, |writer| { + writer.write_sequence_of(|writer| { + for policy in &self.policy_information { + writer + .next() + .write_der(&yasna::construct_der(|writer| policy.encode_der(writer))) + } + }) + }); + } +} + +impl CertificatePolicies { + /// Create a new [`CertificatePolicies`] extension. + /// Returns [`None`] when `policies` is empty and [`Error::DuplicatePolicyInformation`] when policies are not unique + pub fn new(criticality: bool, policies: Vec) -> Result, Error> { + if policies.is_empty() { + return Ok(None); + } + + let mut unique_ids: HashSet<&[u64]> = HashSet::with_capacity(policies.len()); + for policy in &policies { + if !unique_ids.insert(&policy.policy_identifier) { + return Err(Error::DuplicatePolicyInformation( + policy.policy_identifier.clone(), + )); + } + } + + Ok(Some(Self { + critical: criticality, + policy_information: policies, + })) + } + + /// Returns the contained sequence of one or more policy information terms + pub fn policy_information(&self) -> &[PolicyInformation] { + &self.policy_information + } +} + +/// > A certificate policy OID MUST NOT appear more than once in a +/// > certificate policies extension. +/// > +/// > In an end entity certificate, these policy information terms indicate +/// > the policy under which the certificate has been issued and the +/// > purposes for which the certificate may be used. In a CA certificate, +/// > these policy information terms limit the set of policies for +/// > certification paths that include this certificate. When a CA does +/// > not wish to limit the set of policies for certification paths that +/// > include this certificate, it MAY assert the special policy anyPolicy, +/// > with a value of { 2 5 29 32 0 }. +#[derive(Debug, PartialEq, Eq, Clone)] +pub struct PolicyInformation { + /// > To promote interoperability, this profile RECOMMENDS that policy information terms consist of only an OID. + pub policy_identifier: Vec, + /// Consider only populating [`Self::policy_identifier`] if possible. + /// + /// > To promote interoperability, this profile RECOMMENDS that policy + /// > information terms consist of only an OID. Where an OID alone is + /// > insufficient, this profile strongly recommends that the use of + /// > qualifiers be limited to those identified in this section. When + /// > qualifiers are used with the special policy anyPolicy, they MUST be + /// > limited to the qualifiers identified in this section. Only those + /// > qualifiers returned as a result of path validation are considered. + pub policy_qualifiers: Vec, +} + +// impl yasna::DEREncodable for PolicyInformation { +impl PolicyInformation { + fn encode_der<'a>(&self, writer: DERWriter<'a>) { + writer.write_sequence(|writer| { + writer + .next() + .write_oid(&ObjectIdentifier::from_slice(&self.policy_identifier)); + + if self.policy_qualifiers.is_empty() { + return; + }; + + writer.next().write_sequence_of(|writer| { + for policy_qualifier in &self.policy_qualifiers { + writer.next().write_der(&yasna::construct_der(|writer| { + policy_qualifier.encode_der(writer) + })) + } + }) + }) + } +} + +#[cfg(all(test, feature = "x509-parser"))] +impl PolicyInformation { + fn from_x509(value: x509_parser::extensions::PolicyInformation) -> Result { + let mut policy_identifier = Vec::new(); + + for v in value.policy_id.iter().ok_or(Error::X509(String::from( + "PolicyInformation without a policy_identifier is invalid", + )))? { + policy_identifier.push(v); + } + + let Some(qualifiers) = value.policy_qualifiers else { + return Ok(Self { + policy_identifier, + policy_qualifiers: Vec::new(), + }); + }; + let policy_qualifiers = qualifiers + .into_iter() + .map(PolicyQualifierInfo::from_x509) + .collect::, Error>>()?; + + Ok(Self { + policy_identifier, + policy_qualifiers, + }) + } +} + +#[derive(Debug, PartialEq, Eq, Clone)] +/// RFC5280 strongly recommends that no custom qualifiers are used. +/// +/// ```ASN.1 +/// PolicyQualifierInfo ::= SEQUENCE { +/// policyQualifierId PolicyQualifierId, +/// qualifier ANY DEFINED BY policyQualifierId } +/// ``` +pub struct PolicyQualifierInfo { + /// The OID of your [`PolicyQualifierInfo`] + pub policy_qualifier_id: Vec, + /// The DER encoded qualifier + /// + /// > ANY DEFINED BY policyQualifierId + pub qualifier: Vec, +} + +#[cfg(all(test, feature = "x509-parser"))] +impl PolicyQualifierInfo { + fn from_x509(value: x509_parser::extensions::PolicyQualifierInfo<'_>) -> Result { + let mut oid = Vec::new(); + for arc in value + .policy_qualifier_id + .iter() + .ok_or(Error::X509(String::from( + "PolicyInformation without a policy_identifier is invalid", + )))? { + oid.push(arc); + } + + Ok(Self { + policy_qualifier_id: oid, + qualifier: value.qualifier.to_owned(), + }) + } +} + +// impl yasna::DEREncodable for PolicyQualifierInfo { +impl PolicyQualifierInfo { + fn encode_der<'a>(&self, writer: DERWriter<'a>) { + writer.write_sequence(|writer| { + writer + .next() + .write_oid(&ObjectIdentifier::from_slice(&self.policy_qualifier_id)); + writer.next().write_der(&self.qualifier); + }); + } +} + /// Excerpt from [RFC5280 Section 4.2.1.14](https://www.rfc-editor.org/rfc/rfc5280#section-4.2.1.14) /// /// > The inhibit anyPolicy extension can be used in certificates issued to @@ -1220,6 +1454,139 @@ mod tests { #[cfg(feature = "crypto")] use crate::KeyPair; + #[cfg(feature = "crypto")] + #[cfg(feature = "x509-parser")] + #[test] + fn test_certificate_policies_cert_encode_decode() { + use crate::{ + CertificateParams, CertificatePolicies, PolicyInformation, PolicyQualifierInfo, + }; + + const OID_CPS_URI: &[u64] = &[1, 3, 6, 1, 5, 5, 7, 2, 1]; + + let params = CertificateParams { + certificate_policies: CertificatePolicies::new( + false, + vec![ + // domainValidated + PolicyInformation { + policy_identifier: vec![2, 23, 140, 1, 2, 1], + policy_qualifiers: Vec::new(), + }, + // CpsUri + PolicyInformation { + policy_identifier: OID_CPS_URI.to_vec(), + policy_qualifiers: vec![ + PolicyQualifierInfo { + policy_qualifier_id: OID_CPS_URI.to_vec(), + qualifier: yasna::construct_der(|writer| { + writer.write_ia5_string("https://cps.example.org") + }), + }, + PolicyQualifierInfo { + policy_qualifier_id: OID_CPS_URI.to_vec(), + qualifier: yasna::construct_der(|writer| { + writer.write_ia5_string("https://cps.example.com") + }), + }, + ], + }, + ], + ) + .expect("Constructing a well-formed extension shouldn't fail"), + ..Default::default() + }; + + let key_pair = KeyPair::generate().unwrap(); + let cert = params.self_signed(&key_pair).unwrap(); + + let parsed = CertificateParams::from_ca_cert_der(cert.der()) + .expect("We should be able to parse the certificate we just created"); + assert_eq!(params.certificate_policies, parsed.certificate_policies); + assert_eq!( + params.certificate_policies.unwrap().policy_information(), + parsed.certificate_policies.unwrap().policy_information(), + ); + } + + #[test] + fn test_policy_information_new_oid_only_der() { + use crate::PolicyInformation; + const EXPECTED_DER: &[u8] = &[0x30, 0x05, 0x06, 0x03, 0x01, 0x02, 0x03]; + let policy_information_der = yasna::construct_der(|writer| { + PolicyInformation { + policy_identifier: vec![0, 1, 2, 3], + policy_qualifiers: Vec::new(), + } + .encode_der(writer); + }); + assert_eq!(EXPECTED_DER, &policy_information_der) + } + + #[test] + fn test_policy_information_new_oid_qualifiers_der() { + use crate::{PolicyInformation, PolicyQualifierInfo}; + const EXPECTED_DER: &[u8] = &[ + 0x30, 0x2C, 0x06, 0x03, 0x2A, 0x03, 0x04, 0x30, 0x25, 0x30, 0x12, 0x06, 0x04, 0x2A, + 0x03, 0x04, 0x00, 0x0C, 0x0A, 0x55, 0x54, 0x46, 0x38, 0x53, 0x74, 0x72, 0x69, 0x6E, + 0x67, 0x30, 0x0F, 0x06, 0x04, 0x2A, 0x03, 0x04, 0x01, 0x12, 0x07, 0x31, 0x32, 0x38, + 0x20, 0x32, 0x35, 0x36, + ]; + let policy_information_der = yasna::construct_der(|writer| { + PolicyInformation { + policy_identifier: vec![1, 2, 3, 4], + policy_qualifiers: vec![ + PolicyQualifierInfo { + policy_qualifier_id: vec![1, 2, 3, 4, 0], + qualifier: yasna::construct_der(|writer| { + writer.write_utf8string("UTF8String") + }), + }, + PolicyQualifierInfo { + policy_qualifier_id: vec![1, 2, 3, 4, 1], + qualifier: yasna::construct_der(|writer| { + writer.write_numeric_string("128 256") + }), + }, + ], + } + .encode_der(writer); + }); + assert_eq!(EXPECTED_DER, &policy_information_der) + } + + #[test] + fn test_policy_information_empty() { + use crate::CertificatePolicies; + let none = CertificatePolicies::new(false, vec![]) + .expect("Empty CertificatePolicies don't cause an error but must return None"); + assert!(none.is_none()); + } + + #[test] + fn test_policy_information_non_unique() { + use crate::{CertificatePolicies, PolicyInformation}; + let duplicate_policy_information_error = CertificatePolicies::new( + false, + vec![ + PolicyInformation { + policy_identifier: vec![2, 5, 29, 32, 0], + policy_qualifiers: Vec::new(), + }, + PolicyInformation { + policy_identifier: vec![2, 5, 29, 32, 0], + policy_qualifiers: Vec::new(), + }, + ], + ) + .expect_err("Testing duplicate OID rejection"); + + assert_eq!( + format!("{}", duplicate_policy_information_error), + "Encountered duplicate PolicyInformationOID: 2.5.29.32.0" + ) + } + #[cfg(feature = "crypto")] #[test] fn test_inhibit_any_policy_expected_der() { diff --git a/rcgen/src/error.rs b/rcgen/src/error.rs index e6ae3961..e4d32383 100644 --- a/rcgen/src/error.rs +++ b/rcgen/src/error.rs @@ -51,6 +51,8 @@ pub enum Error { /// X509 parsing error #[cfg(feature = "x509-parser")] X509(String), + /// A certificate policy OID MUST NOT appear more than once in a certificate policies extension. + DuplicatePolicyInformation(Vec), } impl fmt::Display for Error { @@ -101,6 +103,14 @@ impl fmt::Display for Error { MissingSerialNumber => write!(f, "A serial number must be specified")?, #[cfg(feature = "x509-parser")] X509(e) => write!(f, "X.509 parsing error: {e}")?, + DuplicatePolicyInformation(oid) => write!( + f, + "Encountered duplicate PolicyInformationOID: {}", + oid.iter() + .map(|&node| node.to_string()) + .collect::>() + .join("."), + )?, }; Ok(()) } diff --git a/rcgen/src/lib.rs b/rcgen/src/lib.rs index ce76086d..f18f332c 100644 --- a/rcgen/src/lib.rs +++ b/rcgen/src/lib.rs @@ -42,9 +42,10 @@ use std::net::{Ipv4Addr, Ipv6Addr}; use std::ops::Deref; pub use certificate::{ - date_time_ymd, Attribute, BasicConstraints, Certificate, CertificateParams, CidrSubnet, - CustomExtension, DnType, ExtendedKeyUsagePurpose, GeneralSubtree, InhibitAnyPolicy, IsCa, - NameConstraints, + date_time_ymd, Attribute, BasicConstraints, Certificate, CertificateParams, + CertificatePolicies, CidrSubnet, CustomExtension, DnType, ExtendedKeyUsagePurpose, + GeneralSubtree, InhibitAnyPolicy, IsCa, NameConstraints, PolicyInformation, + PolicyQualifierInfo, }; pub use crl::{ CertificateRevocationList, CertificateRevocationListParams, CrlDistributionPoint, diff --git a/rcgen/src/oid.rs b/rcgen/src/oid.rs index 9f13cdb0..1171eca4 100644 --- a/rcgen/src/oid.rs +++ b/rcgen/src/oid.rs @@ -41,6 +41,9 @@ pub(crate) const RSASSA_PSS: &[u64] = &[1, 2, 840, 113549, 1, 1, 10]; /// id-ce-keyUsage in [RFC 5280](https://tools.ietf.org/html/rfc5280#appendix-A.2) pub(crate) const KEY_USAGE: &[u64] = &[2, 5, 29, 15]; +/// id-ce-certificatePolicies in [RFC 5280](https://www.rfc-editor.org/rfc/rfc5280#section-4.2.1.4) +pub(crate) const CERTIFICATE_POLICIES: &[u64] = &[2, 5, 29, 32]; + /// id-ce-inhibitAnyPolicy in [RFC 5280](https://www.rfc-editor.org/rfc/rfc5280#section-4.2.1.14) pub(crate) const INHIBIT_ANY_POLICY: &[u64] = &[2, 5, 29, 54];