From 1a681412bceea3a5b4a5dc3d639b4ce62838e698 Mon Sep 17 00:00:00 2001 From: Gabgobie <105999094+Gabgobie@users.noreply.github.com> Date: Wed, 31 Dec 2025 00:55:04 +0100 Subject: [PATCH 1/9] Add id-ce-certificatePolicies OID and contributor question --- rcgen/src/oid.rs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/rcgen/src/oid.rs b/rcgen/src/oid.rs index 3b1c0eb9..3c3e49a4 100644 --- a/rcgen/src/oid.rs +++ b/rcgen/src/oid.rs @@ -41,6 +41,16 @@ 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]; +// Contributor Question: +// Many docstrings refer to appendix A of the RFC instead of the section +// of the respective OID. Why is that? Should I change the link accordingly? +// +// Note: Every other RFC5280 rfc-editor.org link is referring to a nonexisting +// appendix-A section. + +/// 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-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 59afa8aa7e76cc9fc8297168c61dba8d7d4a875a Mon Sep 17 00:00:00 2001 From: Gabgobie <105999094+Gabgobie@users.noreply.github.com> Date: Wed, 14 Jan 2026 18:43:34 +0100 Subject: [PATCH 2/9] Add id-ce-inhibitAnyPolicy --- rcgen/src/oid.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/rcgen/src/oid.rs b/rcgen/src/oid.rs index 3c3e49a4..7a3f63c1 100644 --- a/rcgen/src/oid.rs +++ b/rcgen/src/oid.rs @@ -51,6 +51,9 @@ 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]; + /// 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 a4f243437345fd6cc2df441a563c098bc4bcfcc9 Mon Sep 17 00:00:00 2001 From: Gabgobie <105999094+Gabgobie@users.noreply.github.com> Date: Sat, 17 Jan 2026 08:38:52 +0100 Subject: [PATCH 3/9] Works --- rcgen/examples/certificate_policies.rs | 53 +++ rcgen/src/certificate.rs | 585 ++++++++++++++++++++++++- rcgen/src/error.rs | 5 + rcgen/src/lib.rs | 5 +- 4 files changed, 645 insertions(+), 3 deletions(-) create mode 100644 rcgen/examples/certificate_policies.rs diff --git a/rcgen/examples/certificate_policies.rs b/rcgen/examples/certificate_policies.rs new file mode 100644 index 00000000..3c1eb6f9 --- /dev/null +++ b/rcgen/examples/certificate_policies.rs @@ -0,0 +1,53 @@ +// This example is a copy from `simple.rs` with the addition of the CertificatePolicies extension + +use std::fs; + +use rcgen::{ + date_time_ymd, string::Ia5String, CertificateParams, CertificatePolicies, DistinguishedName, + DnType, InhibitAnyPolicy, KeyPair, PolicyInformation, SanType, UserNotice, +}; + +fn main() -> Result<(), Box> { + let mut params: CertificateParams = Default::default(); + params.not_before = date_time_ymd(1975, 1, 1); + params.not_after = date_time_ymd(4096, 1, 1); + params.distinguished_name = DistinguishedName::new(); + params + .distinguished_name + .push(DnType::OrganizationName, "Crab widgits SE"); + params + .distinguished_name + .push(DnType::CommonName, "Master Cert"); + params.subject_alt_names = vec![ + SanType::DnsName("crabs.crabs".try_into()?), + SanType::DnsName("localhost".try_into()?), + ]; + params.certificate_policies = Some( + CertificatePolicies::new(false, PolicyInformation::any_policy()) + .add_policy_unchecked(PolicyInformation::domain_validated()) + .add_policy_unchecked(PolicyInformation::cps_uri(vec![ + Ia5String::try_from("https://cps.example.com")?, + Ia5String::try_from("https://cps.example.org")?, + ])) + .add_policy_unchecked(PolicyInformation::user_notice( + &UserNotice::new_explicit_text("Test".into()), + )), + ); + + params.inhibit_any_policy = Some(InhibitAnyPolicy::new(2)); + + let key_pair = KeyPair::generate()?; + let cert = params.self_signed(&key_pair)?; + + let pem_serialized = cert.pem(); + let pem = pem::parse(&pem_serialized)?; + let der_serialized = pem.contents(); + println!("{pem_serialized}"); + println!("{}", key_pair.serialize_pem()); + fs::create_dir_all("certs/")?; + fs::write("certs/cert.pem", pem_serialized.as_bytes())?; + fs::write("certs/cert.der", der_serialized)?; + fs::write("certs/key.pem", key_pair.serialize_pem().as_bytes())?; + fs::write("certs/key.der", key_pair.serialize_der())?; + Ok(()) +} diff --git a/rcgen/src/certificate.rs b/rcgen/src/certificate.rs index 6a238862..d6dc3936 100644 --- a/rcgen/src/certificate.rs +++ b/rcgen/src/certificate.rs @@ -16,7 +16,7 @@ use crate::ring_like::digest; #[cfg(feature = "pem")] use crate::ENCODE_CONFIG; use crate::{ - oid, write_distinguished_name, write_dt_utc_or_generalized, + oid, string, write_distinguished_name, write_dt_utc_or_generalized, write_x509_authority_key_identifier, write_x509_extension, DistinguishedName, Error, Issuer, KeyIdMethod, KeyUsagePurpose, SanType, SerialNumber, SigningKey, }; @@ -61,6 +61,19 @@ pub struct CertificateParams { pub distinguished_name: DistinguishedName, pub is_ca: IsCa, pub key_usages: Vec, + /// "\[If the optional extension is present, t\]he certificate policies extension contains a sequence of one or more + /// policy information terms, each of which consists of an object + /// identifier (OID) and optional qualifiers. + /// + /// 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."[^1] + /// + /// [^1]: + pub certificate_policies: Option, + 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 +106,8 @@ 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, crl_distribution_points: Vec::new(), @@ -181,6 +196,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)?, ..Default::default() }) } @@ -330,6 +346,8 @@ impl CertificateParams { distinguished_name, is_ca, key_usages, + certificate_policies, + inhibit_any_policy, extended_key_usages, name_constraints, crl_distribution_points, @@ -353,6 +371,8 @@ impl CertificateParams { if serial_number.is_some() || *is_ca != IsCa::NoCa || name_constraints.is_some() + || certificate_policies.is_some() // I think this extension must be set by the CA and not the requesting party + || inhibit_any_policy.is_some() // Same as policies above || !crl_distribution_points.is_empty() || *use_authority_key_identifier_extension { @@ -584,6 +604,27 @@ impl CertificateParams { IsCa::NoCa => {}, } + if let Some(certificate_policies) = &self.certificate_policies { + write_x509_extension( + writer.next(), + oid::CERTIFICATE_POLICIES, + certificate_policies.critical, + |writer| { + writer.write_sequence_of(|writer| { + for policy in &certificate_policies.policy_information { + writer.next().write_der(&yasna::encode_der(policy)) + } + }) + }, + ); + } + + if let Some(inhibit_any_policy) = &self.inhibit_any_policy { + write_x509_extension(writer.next(), oid::INHIBIT_ANY_POLICY, true, |writer| { + writer.write_i64(inhibit_any_policy.skip_certs as i64) + }); + } + // Write the custom extensions for ext in &self.custom_extensions { write_x509_extension(writer.next(), &ext.oid, ext.critical, |writer| { @@ -635,6 +676,548 @@ 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. + 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, + // Contributor note: + // Would it be better/feasible to use a HashSet where the hash is determined by + // the OID to ensure RFC compliance here? Is ensuring valid extensions part of + // this crate's responsibilities or is it the consumers responsibility? +} + +// Contributer note: Strongly inspired by NameConstraints impl +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 { + // Contributor note: + // Since we use get_extension_unique with the ext_certificate_policies and CertificatePolicies + // is implemented, this should be `unreachable!()`. I am unsure about the error to return here. + // Returning None would probably be worse since the extension is known to be present. + // The available parser errors don't seem to be applicable here. + // What would have to happen for this branch to be chosen? + return Err(Error::UnsupportedExtension); + }; + + let mut policy_information: Vec = Vec::with_capacity(policies.len()); + for policy in policies.iter().cloned() { + policy_information.push(policy.try_into()?) + } + + Ok(Some(Self { + critical: ext.critical, + policy_information, + })) + } +} + +impl yasna::DEREncodable for 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::encode_der(policy)) + } + }) + }); + } +} + +impl CertificatePolicies { + /// Create a new [`CertificatePolicies`] extension + /// + /// Encorces validity by requiring you to add the first policy. + /// Add more policies by using the [`Self::add_policy`] method + /// or multiple at once by using the [`Self::add_policies`] method. + pub fn new(criticality: bool, policy: PolicyInformation) -> Self { + Self { + critical: criticality, + policy_information: vec![policy], + } + } + + /// Add policy and check if it is already present. + /// Returns [`Error::Other`] if the + /// PolicyInformation OID is already present + pub fn add_policy(self, policy: PolicyInformation) -> Result { + let mut registered_policies = self.policy_information; + for policy_information in ®istered_policies { + if policy_information.policy_identifier == policy.policy_identifier { + return Err(Error::Other); // PolicyInformation must be unique + } + } + registered_policies.push(policy); + Ok(Self { + critical: self.critical, + policy_information: registered_policies, + }) + } + + /// Add once policy at a time + /// Does not validate if the PolicyInformation OID is unique. + pub fn add_policy_unchecked(self, policy: PolicyInformation) -> Self { + let mut registered_policies = self.policy_information.clone(); + registered_policies.push(policy); + Self { + critical: self.critical, + policy_information: registered_policies, + } + } + + /// Add multiple policies at once + /// Does not validate if the PolicyInformation OID is unique. + pub fn add_policies_unchecked(self, policies: &[PolicyInformation]) -> Self { + let mut registered_policies = self.policy_information.clone(); + registered_policies.extend_from_slice(policies); + Self { + critical: self.critical, + policy_information: registered_policies, + } + } +} + +/// > 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 { + policy_identifier: Vec, + policy_qualifiers: Option>, +} + +impl yasna::DEREncodable for PolicyInformation { + fn encode_der<'a>(&self, writer: DERWriter<'a>) { + writer.write_sequence(|writer| { + writer + .next() + .write_oid(&ObjectIdentifier::from_slice(&self.policy_identifier)); + if let Some(policy_qualifiers) = &self.policy_qualifiers { + writer.next().write_sequence_of(|writer| { + for policy_qualifier in policy_qualifiers { + writer + .next() + .write_der(&yasna::encode_der(policy_qualifier)); + } + }) + } + }) + } +} + +impl PolicyInformation { + /// > To promote interoperability, this profile RECOMMENDS that policy information terms consist of only an OID. + pub fn new_oid_only(oid: Vec) -> Self { + Self { + policy_identifier: oid, + policy_qualifiers: None, + } + } + + /// Consider using [`Self::new_oid_only`] instead 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 fn new_oid_qualifiers(oid: Vec, qualifiers: Vec) -> Self { + Self { + policy_identifier: oid, + policy_qualifiers: Some(qualifiers), + } + } + + /// 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 }. + pub fn any_policy() -> Self { + Self::new_oid_only(vec![2, 5, 29, 32, 0]) + } + + /// Certificate issued in compliance with the Extended Validation Guidelines + pub fn extended_validation() -> Self { + Self::new_oid_only(vec![2, 23, 140, 1, 1]) + } + + /// Certificate issued in compliance with the TLS Baseline Requirements – No entity identity asserted + pub fn domain_validated() -> Self { + Self::new_oid_only(vec![2, 23, 140, 1, 2, 1]) + } + + /// Certificate issued in compliance with the TLS Baseline Requirements – Organization identity asserted + pub fn organization_validated() -> Self { + Self::new_oid_only(vec![2, 23, 140, 1, 2, 2]) + } + + /// Certificate issued in compliance with the TLS Baseline Requirements – Individual identity asserted + pub fn individual_validated() -> Self { + Self::new_oid_only(vec![2, 23, 140, 1, 2, 3]) + } + + /// > The CPS Pointer qualifier contains a pointer to a Certification + /// > Practice Statement (CPS) published by the CA. The pointer is in the + /// > form of a URI. Processing requirements for this qualifier are a + /// > local matter. No action is mandated by this specification regardless + /// > of the criticality value asserted for the extension. + pub fn cps_uri(cps_uris: Vec) -> Self { + Self::new_oid_qualifiers( + // Didn't find this one in RFC5280 and took it from PKIOverheid (Dutch Government PKI) using Firefox certificate inspection + // Is it plausible, that this is matching the PolicyQualifierInfo OID? + vec![1, 3, 6, 1, 5, 5, 7, 2, 1], + cps_uris + .iter() + .map(PolicyQualifierInfo::new_cps_uri) + .collect(), + ) + } + + /// User notice is intended for display to a relying party when a + /// certificate is used. Only user notices returned as a result of path + /// validation are intended for display to the user. + pub fn user_notice(user_notice: &UserNotice) -> Self { + Self::new_oid_qualifiers( + // Didn't find this one in RFC5280 and took it from https://github.com/rustls/rcgen/issues/370#issuecomment-3183832371 -> Firefox + // Is it plausible, that this is matching the PolicyQualifierInfo OID? + vec![1, 3, 6, 1, 5, 5, 7, 2, 2], + vec![PolicyQualifierInfo::new_user_notice(user_notice)], + ) + } +} + +#[cfg(all(test, feature = "x509-parser"))] +impl TryFrom> for PolicyInformation { + type Error = Error; + fn try_from(value: x509_parser::extensions::PolicyInformation) -> Result { + let mut policy_identifier = Vec::new(); + + // Contributor question: What error should be returned here? Is this something that can happen? + for v in value.policy_id.iter().ok_or(Error::X509(String::from( + "PolicyInformation without a policy_identifier is invalid", + )))? { + policy_identifier.push(v); + } + + // // This is an alternative way to convert the [`Oid<'_>`] but I think it may yield incorrect results because an arc can be larger than [`u8`] + // for arc in value.policy_id.as_bytes() { + // policy_identifier.push(arc.to_owned() as u64) + // } + + let Some(qualifiers) = value.policy_qualifiers else { + return Ok(Self { + policy_identifier, + policy_qualifiers: None, + }); + }; + + let mut policy_qualifiers = Vec::new(); + for qualifier in qualifiers { + policy_qualifiers.push(qualifier.into()) + } + + Ok(Self { + policy_identifier, + policy_qualifiers: Some(policy_qualifiers), + }) + } +} + +#[derive(Debug, PartialEq, Eq, Clone)] +pub struct PolicyQualifierInfo { + policy_qualifier_id: Vec, + /// The DER encoded qualifier + /// + /// > ANY DEFINED BY policyQualifierId + qualifier: Vec, +} + +#[cfg(all(test, feature = "x509-parser"))] +impl From> for PolicyQualifierInfo { + fn from(value: x509_parser::extensions::PolicyQualifierInfo<'_>) -> Self { + let mut oid = Vec::new(); + for arc in value.policy_qualifier_id.as_bytes() { + oid.push(arc.to_owned() as u64) + } + + Self { + policy_qualifier_id: oid, + qualifier: value.qualifier.to_owned(), + } + } +} + +impl PolicyQualifierInfo { + // id-qt OBJECT IDENTIFIER ::= { id-pkix 2 } + // id-qt-cps OBJECT IDENTIFIER ::= { id-qt 1 } + fn new_cps_uri(cps_uri: &string::Ia5String) -> Self { + Self { + policy_qualifier_id: vec![1, 3, 6, 1, 5, 5, 7, 2, 1], + qualifier: yasna::construct_der(|writer| writer.write_ia5_string(cps_uri.as_str())), + } + } + + // id-qt OBJECT IDENTIFIER ::= { id-pkix 2 } + // id-qt-unotice OBJECT IDENTIFIER ::= { id-qt 2 } + fn new_user_notice(user_notice: &UserNotice) -> Self { + Self { + policy_qualifier_id: vec![1, 3, 6, 1, 5, 5, 7, 2, 2], + qualifier: yasna::encode_der(user_notice), + } + } +} + +impl yasna::DEREncodable for 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); + }); + } +} + +/// > The user notice has two optional fields: the noticeRef field and the +/// > explicitText field. Conforming CAs SHOULD NOT use the noticeRef +/// > option. +/// +/// > If both the noticeRef and explicitText options are included in the +/// > one qualifier and if the application software can locate the notice +/// > text indicated by the noticeRef option, then that text SHOULD be +/// > displayed; otherwise, the explicitText string SHOULD be displayed. +#[derive(Debug, PartialEq, Eq, Clone)] +pub struct UserNotice { + /// > The noticeRef field, if used, names an organization and + /// > identifies, by number, a particular textual statement prepared by + /// > that organization. For example, it might identify the + /// > organization "CertsRUs" and notice number 1. In a typical + /// > implementation, the application software will have a notice file + /// > containing the current set of notices for CertsRUs; the + /// > application will extract the notice text from the file and display + /// > it. Messages MAY be multilingual, allowing the software to select + /// > the particular language message for its own environment. + notice_ref: Option, + /// > An explicitText field includes the textual statement directly in + /// > the certificate. The explicitText field is a string with a + /// > maximum size of 200 characters. Conforming CAs SHOULD use the + /// > UTF8String encoding for explicitText, but MAY use IA5String. + /// > Conforming CAs MUST NOT encode explicitText as VisibleString or + /// > BMPString. The explicitText string SHOULD NOT include any control + /// > characters (e.g., U+0000 to U+001F and U+007F to U+009F). When + /// > the UTF8String encoding is used, all character sequences SHOULD be + /// > normalized according to Unicode normalization form C (NFC)[^1] \[[NFC](https://www.rfc-editor.org/rfc/rfc5280#ref-NFC)\]. + /// + /// [^1]: + explicit_text: Option, +} + +impl UserNotice { + /// Creates a new [`UserNotice`] with only the + /// [`UserNotice::explicit_text`] field populated + /// + /// Recommended + pub fn new_explicit_text(msg: DisplayText) -> Self { + Self { + notice_ref: None, + explicit_text: Some(msg), + } + } + + /// Creates a new [`UserNotice`] with all fields populated + /// + /// Consider using [`Self::new_explicit_text`] instead + /// Conforming CAs SHOULD NOT use the noticeRef option. + pub fn new_full(organization: DisplayText, notice_numbers: Vec, msg: String) -> Self { + Self { + notice_ref: Some(NoticeReference { + organization, + notice_numbers, + }), + explicit_text: Some(DisplayText::Utf8String(msg)), // MSG is defined as DisplayText but RECOMMENDED to be UTF8String. Should we expose all options including those that are discouraged? + } + } + + /// Creates a new [`UserNotice`] with all fields populated + /// + /// Consider using [`Self::new_explicit_text`] instead + /// Conforming CAs SHOULD NOT use the noticeRef option. + pub fn new_notice_reference(organization: String, notice_numbers: Vec) -> Self { + Self { + notice_ref: Some(NoticeReference { + organization: DisplayText::Utf8String(organization), + notice_numbers, + }), + explicit_text: None, + } + } +} + +impl yasna::DEREncodable for UserNotice { + fn encode_der<'a>(&self, writer: DERWriter<'a>) { + use yasna::encode_der; + writer.write_sequence(|writer| { + if let Some(notice_ref) = &self.notice_ref { + writer.next().write_der(&encode_der(notice_ref)); + } + if let Some(explicit_text) = &self.explicit_text { + writer.next().write_der(&encode_der(explicit_text)); + } + }) + } +} + +/// Consider using [`UserNotice::explicit_text`] instead. +/// +/// > Conforming CAs SHOULD NOT use the noticeRef option. +#[derive(Debug, PartialEq, Eq, Clone)] +struct NoticeReference { + organization: DisplayText, + notice_numbers: Vec, +} + +impl yasna::DEREncodable for NoticeReference { + fn encode_der<'a>(&self, writer: DERWriter<'a>) { + use yasna::encode_der; + writer.write_sequence(|writer| { + writer.next().write_der(&encode_der(&self.organization)); + writer.next().write_der(&self.notice_numbers); + }) + } +} + +/// ```ASN.1 +/// DisplayText ::= CHOICE { +/// ia5String IA5String (SIZE (1..200)), +/// visibleString VisibleString (SIZE (1..200)), +/// bmpString BMPString (SIZE (1..200)), +/// utf8String UTF8String (SIZE (1..200)) } +/// ``` +/// +/// Note: While the explicitText has a maximum size of 200 +/// characters, some non-conforming CAs exceed this limit. +/// Therefore, certificate users SHOULD gracefully handle +/// explicitText with more than 200 characters. +#[derive(Debug, PartialEq, Eq, Clone)] +pub enum DisplayText { + Ia5String(string::Ia5String), + Utf8String(String), + // Contributor question: + // Should we make non-conformant options available at all? + + // VisibleString(string::VisibleString), // Not implemented yet. Could be imported/inspired from x509_parser + // BmpString(string::BmpString), +} + +impl From<&str> for DisplayText { + fn from(value: &str) -> Self { + Self::Utf8String(value.to_string()) + } +} + +impl From for DisplayText { + fn from(value: String) -> Self { + Self::Utf8String(value) + } +} + +impl yasna::DEREncodable for DisplayText { + fn encode_der<'a>(&self, writer: DERWriter<'a>) { + match self { + DisplayText::Ia5String(string) => writer.write_ia5_string(string.as_str()), + DisplayText::Utf8String(string) => writer.write_utf8_string(string.as_str()), + // DisplayText::BmpString(string) => { + // // [`writer.write_bmp_string`] expects [`&str`]. + // // Would I use write_bytes for this? + // let bytes = string.as_bytes(); + // writer.write_bitvec_bytes(bytes, bytes.len()) + // }, + } + } +} + +/// 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 { + skip_certs: u32, +} + +impl InhibitAnyPolicy { + /// Builds a new InhibitAnyPolicy Extension. + /// + /// > 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. + pub fn new(skip_certs: u32) -> Self { + Self { skip_certs } + } +} + +impl yasna::DEREncodable for 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_i64(self.skip_certs as i64) + }); + } +} + /// A PKCS #10 CSR attribute, as defined in [RFC 5280] and constrained /// by [RFC 2986]. /// diff --git a/rcgen/src/error.rs b/rcgen/src/error.rs index e6ae3961..9881d5d3 100644 --- a/rcgen/src/error.rs +++ b/rcgen/src/error.rs @@ -51,6 +51,10 @@ pub enum Error { /// X509 parsing error #[cfg(feature = "x509-parser")] X509(String), + /// A placeholder error until I can get some feedback on the right error to use + /// + /// Currently used for invalid builder operations + Other, } impl fmt::Display for Error { @@ -101,6 +105,7 @@ 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}")?, + Other => write!(f, "Placeholder error until I get some feedback")?, }; Ok(()) } diff --git a/rcgen/src/lib.rs b/rcgen/src/lib.rs index 83816182..d4dfac04 100644 --- a/rcgen/src/lib.rs +++ b/rcgen/src/lib.rs @@ -42,8 +42,9 @@ use std::net::{Ipv4Addr, Ipv6Addr}; use std::ops::Deref; pub use certificate::{ - date_time_ymd, Attribute, BasicConstraints, Certificate, CertificateParams, CidrSubnet, - CustomExtension, DnType, ExtendedKeyUsagePurpose, GeneralSubtree, IsCa, NameConstraints, + date_time_ymd, Attribute, BasicConstraints, Certificate, CertificateParams, + CertificatePolicies, CidrSubnet, CustomExtension, DnType, ExtendedKeyUsagePurpose, + GeneralSubtree, InhibitAnyPolicy, IsCa, NameConstraints, PolicyInformation, UserNotice, }; pub use crl::{ CertificateRevocationList, CertificateRevocationListParams, CrlDistributionPoint, From 3233728dcddaa633be0f98e999f5657f1d3e62f2 Mon Sep 17 00:00:00 2001 From: Gabgobie <105999094+Gabgobie@users.noreply.github.com> Date: Sat, 17 Jan 2026 09:44:43 +0100 Subject: [PATCH 4/9] Fix bad noticeNumbers DER; Hide external types in public API --- rcgen/examples/certificate_policies.rs | 8 +- rcgen/src/certificate.rs | 128 +++++++++++++++++-------- 2 files changed, 95 insertions(+), 41 deletions(-) diff --git a/rcgen/examples/certificate_policies.rs b/rcgen/examples/certificate_policies.rs index 3c1eb6f9..e86a7a2a 100644 --- a/rcgen/examples/certificate_policies.rs +++ b/rcgen/examples/certificate_policies.rs @@ -29,9 +29,11 @@ fn main() -> Result<(), Box> { Ia5String::try_from("https://cps.example.com")?, Ia5String::try_from("https://cps.example.org")?, ])) - .add_policy_unchecked(PolicyInformation::user_notice( - &UserNotice::new_explicit_text("Test".into()), - )), + .add_policy_unchecked(PolicyInformation::user_notice(&UserNotice::new_full( + "Example Org".into(), + vec![0, 1, 2, 3], + "Test message".into(), + ))), ); params.inhibit_any_policy = Some(InhibitAnyPolicy::new(2)); diff --git a/rcgen/src/certificate.rs b/rcgen/src/certificate.rs index d6dc3936..70f6448e 100644 --- a/rcgen/src/certificate.rs +++ b/rcgen/src/certificate.rs @@ -605,24 +605,35 @@ impl CertificateParams { } if let Some(certificate_policies) = &self.certificate_policies { - write_x509_extension( - writer.next(), - oid::CERTIFICATE_POLICIES, - certificate_policies.critical, - |writer| { - writer.write_sequence_of(|writer| { - for policy in &certificate_policies.policy_information { - writer.next().write_der(&yasna::encode_der(policy)) - } - }) - }, - ); + // write_x509_extension( + // writer.next(), + // oid::CERTIFICATE_POLICIES, + // certificate_policies.critical, + // |writer| { + // writer.write_sequence_of(|writer| { + // for policy in &certificate_policies.policy_information { + // // writer.next().write_der(&yasna::encode_der(policy)) + + // // Unwrapped equivalent to the above trait use + // writer.next().write_der(&yasna::construct_der(|writer| { + // policy.encode_der(writer) + // })) + // } + // }) + // }, + // ); + writer.next().write_der(&yasna::construct_der(|writer| { + certificate_policies.encode_der(writer); + })) } if let Some(inhibit_any_policy) = &self.inhibit_any_policy { - write_x509_extension(writer.next(), oid::INHIBIT_ANY_POLICY, true, |writer| { - writer.write_i64(inhibit_any_policy.skip_certs as i64) - }); + // write_x509_extension(writer.next(), oid::INHIBIT_ANY_POLICY, true, |writer| { + // writer.write_i64(inhibit_any_policy.skip_certs as i64) + // }); + writer.next().write_der(&yasna::construct_der(|writer| { + inhibit_any_policy.encode_der(writer) + })) } // Write the custom extensions @@ -736,12 +747,18 @@ impl CertificatePolicies { } } -impl yasna::DEREncodable for CertificatePolicies { +// 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::encode_der(policy)) + // writer.next().write_der(&yasna::encode_der(policy)) + + // Unwrapped equivalent to the above trait use + writer + .next() + .write_der(&yasna::construct_der(|writer| policy.encode_der(writer))) } }) }); @@ -818,7 +835,8 @@ pub struct PolicyInformation { policy_qualifiers: Option>, } -impl yasna::DEREncodable for PolicyInformation { +// impl yasna::DEREncodable for PolicyInformation { +impl PolicyInformation { fn encode_der<'a>(&self, writer: DERWriter<'a>) { writer.write_sequence(|writer| { writer @@ -829,7 +847,11 @@ impl yasna::DEREncodable for PolicyInformation { for policy_qualifier in policy_qualifiers { writer .next() - .write_der(&yasna::encode_der(policy_qualifier)); + // .write_der(&yasna::encode_der(policy_qualifier)); + // Unwrapped equivalent to the above trait use + .write_der(&yasna::construct_der(|writer| { + policy_qualifier.encode_der(writer) + })) } }) } @@ -944,11 +966,11 @@ impl TryFrom> for PolicyInformati policy_qualifiers: None, }); }; - - let mut policy_qualifiers = Vec::new(); - for qualifier in qualifiers { - policy_qualifiers.push(qualifier.into()) - } + // let policy_qualifiers = qualifiers.into_iter().map(PolicyQualifierInfo::from).collect(); + let policy_qualifiers = qualifiers + .into_iter() + .map(PolicyQualifierInfo::from_x509) + .collect(); Ok(Self { policy_identifier, @@ -967,8 +989,10 @@ pub struct PolicyQualifierInfo { } #[cfg(all(test, feature = "x509-parser"))] -impl From> for PolicyQualifierInfo { - fn from(value: x509_parser::extensions::PolicyQualifierInfo<'_>) -> Self { +// impl From> for PolicyQualifierInfo { +// fn from(value: x509_parser::extensions::PolicyQualifierInfo<'_>) -> Self { +impl PolicyQualifierInfo { + fn from_x509(value: x509_parser::extensions::PolicyQualifierInfo<'_>) -> Self { let mut oid = Vec::new(); for arc in value.policy_qualifier_id.as_bytes() { oid.push(arc.to_owned() as u64) @@ -996,12 +1020,18 @@ impl PolicyQualifierInfo { fn new_user_notice(user_notice: &UserNotice) -> Self { Self { policy_qualifier_id: vec![1, 3, 6, 1, 5, 5, 7, 2, 2], - qualifier: yasna::encode_der(user_notice), + // qualifier: yasna::encode_der(user_notice), + + // Unwrapped equivalent to the above trait use + qualifier: yasna::construct_der(|writer| { + user_notice.encode_der(writer); + }), } } } -impl yasna::DEREncodable for PolicyQualifierInfo { +// impl yasna::DEREncodable for PolicyQualifierInfo { +impl PolicyQualifierInfo { fn encode_der<'a>(&self, writer: DERWriter<'a>) { writer.write_sequence(|writer| { writer @@ -1062,7 +1092,7 @@ impl UserNotice { /// /// Consider using [`Self::new_explicit_text`] instead /// Conforming CAs SHOULD NOT use the noticeRef option. - pub fn new_full(organization: DisplayText, notice_numbers: Vec, msg: String) -> Self { + pub fn new_full(organization: DisplayText, notice_numbers: NoticeNumbers, msg: String) -> Self { Self { notice_ref: Some(NoticeReference { organization, @@ -1076,7 +1106,7 @@ impl UserNotice { /// /// Consider using [`Self::new_explicit_text`] instead /// Conforming CAs SHOULD NOT use the noticeRef option. - pub fn new_notice_reference(organization: String, notice_numbers: Vec) -> Self { + pub fn new_notice_reference(organization: String, notice_numbers: NoticeNumbers) -> Self { Self { notice_ref: Some(NoticeReference { organization: DisplayText::Utf8String(organization), @@ -1087,7 +1117,8 @@ impl UserNotice { } } -impl yasna::DEREncodable for UserNotice { +// impl yasna::DEREncodable for UserNotice { +impl UserNotice { fn encode_der<'a>(&self, writer: DERWriter<'a>) { use yasna::encode_der; writer.write_sequence(|writer| { @@ -1095,27 +1126,46 @@ impl yasna::DEREncodable for UserNotice { writer.next().write_der(&encode_der(notice_ref)); } if let Some(explicit_text) = &self.explicit_text { - writer.next().write_der(&encode_der(explicit_text)); + // writer.next().write_der(&encode_der(explicit_text)); + + // Unwrapped equivalent to the above trait use + writer.next().write_der(&yasna::construct_der(|writer| { + explicit_text.encode_der(writer); + })) } }) } } +/// ```ASN.1 +/// noticeNumbers SEQUENCE OF INTEGER +/// ``` +type NoticeNumbers = Vec; // Does INTEGER translate to i64? OpenSSL successfully decodes it + /// Consider using [`UserNotice::explicit_text`] instead. /// /// > Conforming CAs SHOULD NOT use the noticeRef option. #[derive(Debug, PartialEq, Eq, Clone)] struct NoticeReference { organization: DisplayText, - notice_numbers: Vec, + notice_numbers: NoticeNumbers, } +// NoticeRef is private. Should I still remove the Trait impl? impl yasna::DEREncodable for NoticeReference { fn encode_der<'a>(&self, writer: DERWriter<'a>) { - use yasna::encode_der; writer.write_sequence(|writer| { - writer.next().write_der(&encode_der(&self.organization)); - writer.next().write_der(&self.notice_numbers); + // writer.next().write_der(&encode_der(&self.organization)); + writer.next().write_der(&yasna::construct_der(|writer| { + self.organization.encode_der(writer); + })); + // This is incorrect + // writer.next().write_der(&self.notice_numbers); + writer.next().write_sequence_of(|writer| { + for val in &self.notice_numbers { + writer.next().write_i64(*val); + } + }); }) } } @@ -1155,7 +1205,8 @@ impl From for DisplayText { } } -impl yasna::DEREncodable for DisplayText { +// impl yasna::DEREncodable for DisplayText { +impl DisplayText { fn encode_der<'a>(&self, writer: DERWriter<'a>) { match self { DisplayText::Ia5String(string) => writer.write_ia5_string(string.as_str()), @@ -1209,7 +1260,8 @@ impl InhibitAnyPolicy { } } -impl yasna::DEREncodable for InhibitAnyPolicy { +// 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| { From 2107885e18416cda6465c3749b5c501c9f3d1b0a Mon Sep 17 00:00:00 2001 From: Gabgobie <105999094+Gabgobie@users.noreply.github.com> Date: Sat, 17 Jan 2026 09:48:38 +0100 Subject: [PATCH 5/9] Fix check: build-windows --- rcgen/Cargo.toml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/rcgen/Cargo.toml b/rcgen/Cargo.toml index 331abacc..21eac214 100644 --- a/rcgen/Cargo.toml +++ b/rcgen/Cargo.toml @@ -31,6 +31,10 @@ zeroize = { workspace = true, optional = true } [target."cfg(unix)".dev-dependencies] openssl = { workspace = true } +[[example]] +name = "certificate_policies" +required-features = ["pem"] + [[example]] name = "rsa-irc-openssl" required-features = ["pem"] From eecdd83ca93747c7fbbc53c3241489ddbf2a7276 Mon Sep 17 00:00:00 2001 From: Gabgobie <105999094+Gabgobie@users.noreply.github.com> Date: Sat, 17 Jan 2026 09:50:44 +0100 Subject: [PATCH 6/9] Apply unstable formatting cargo fmt --all -- --config-path .rustfmt.unstable.toml --- rcgen/examples/certificate_policies.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/rcgen/examples/certificate_policies.rs b/rcgen/examples/certificate_policies.rs index e86a7a2a..c634ecad 100644 --- a/rcgen/examples/certificate_policies.rs +++ b/rcgen/examples/certificate_policies.rs @@ -2,9 +2,10 @@ use std::fs; +use rcgen::string::Ia5String; use rcgen::{ - date_time_ymd, string::Ia5String, CertificateParams, CertificatePolicies, DistinguishedName, - DnType, InhibitAnyPolicy, KeyPair, PolicyInformation, SanType, UserNotice, + date_time_ymd, CertificateParams, CertificatePolicies, DistinguishedName, DnType, + InhibitAnyPolicy, KeyPair, PolicyInformation, SanType, UserNotice, }; fn main() -> Result<(), Box> { From 38df12a3da0783dd7e49a0148ce017ddaba35604 Mon Sep 17 00:00:00 2001 From: Gabgobie <105999094+Gabgobie@users.noreply.github.com> Date: Sat, 17 Jan 2026 09:59:50 +0100 Subject: [PATCH 7/9] Add checked add_policies method --- rcgen/src/certificate.rs | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/rcgen/src/certificate.rs b/rcgen/src/certificate.rs index 70f6448e..45e76dd1 100644 --- a/rcgen/src/certificate.rs +++ b/rcgen/src/certificate.rs @@ -795,6 +795,24 @@ impl CertificatePolicies { }) } + /// Add multiple policies at once + /// Returns [`Error::Other`] if any of the + /// PolicyInformation OIDs are already present + pub fn add_policies(self, policies: &[PolicyInformation]) -> Result { + let mut registered_policies = self.policy_information.clone(); + registered_policies.extend_from_slice(policies); + let mut oids = std::collections::HashSet::<&[u64]>::new(); + for policy in ®istered_policies { + if !oids.insert(&policy.policy_identifier) { + return Err(Error::Other); + } + } + Ok(Self { + critical: self.critical, + policy_information: registered_policies, + }) + } + /// Add once policy at a time /// Does not validate if the PolicyInformation OID is unique. pub fn add_policy_unchecked(self, policy: PolicyInformation) -> Self { From 64ee5b10b64d197879568c26a688c6e04bb585e3 Mon Sep 17 00:00:00 2001 From: Gabgobie <105999094+Gabgobie@users.noreply.github.com> Date: Sat, 17 Jan 2026 10:04:18 +0100 Subject: [PATCH 8/9] Fix doc complaints cargo doc --features ring,pem,x509-parser --document-private-items --- rcgen/src/certificate.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rcgen/src/certificate.rs b/rcgen/src/certificate.rs index 45e76dd1..119c915f 100644 --- a/rcgen/src/certificate.rs +++ b/rcgen/src/certificate.rs @@ -1096,7 +1096,7 @@ pub struct UserNotice { impl UserNotice { /// Creates a new [`UserNotice`] with only the - /// [`UserNotice::explicit_text`] field populated + /// explicit_text field populated /// /// Recommended pub fn new_explicit_text(msg: DisplayText) -> Self { @@ -1239,7 +1239,7 @@ impl DisplayText { } } -/// https://www.rfc-editor.org/rfc/rfc5280#section-4.2.1.14 +/// 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 From 391d263be27f52e0cce4ae315e1be4376673d080 Mon Sep 17 00:00:00 2001 From: Gabgobie <105999094+Gabgobie@users.noreply.github.com> Date: Sat, 17 Jan 2026 18:16:15 +0100 Subject: [PATCH 9/9] Remove some redundant comments, add missing from_x509 implementation for InhibitAnyPolicy --- rcgen/src/certificate.rs | 49 ++++++++++++++++++++++++++++------------ 1 file changed, 35 insertions(+), 14 deletions(-) diff --git a/rcgen/src/certificate.rs b/rcgen/src/certificate.rs index 119c915f..e092e8ca 100644 --- a/rcgen/src/certificate.rs +++ b/rcgen/src/certificate.rs @@ -197,6 +197,7 @@ impl CertificateParams { 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() }) } @@ -702,13 +703,8 @@ pub struct CertificatePolicies { /// A certificate policy OID MUST NOT appear more than once in a /// certificate policies extension. policy_information: Vec, - // Contributor note: - // Would it be better/feasible to use a HashSet where the hash is determined by - // the OID to ensure RFC compliance here? Is ensuring valid extensions part of - // this crate's responsibilities or is it the consumers responsibility? } -// Contributer note: Strongly inspired by NameConstraints impl impl CertificatePolicies { #[cfg(all(test, feature = "x509-parser"))] fn from_x509( @@ -737,7 +733,8 @@ impl CertificatePolicies { let mut policy_information: Vec = Vec::with_capacity(policies.len()); for policy in policies.iter().cloned() { - policy_information.push(policy.try_into()?) + // policy_information.push(policy.try_into()?) + policy_information.push(PolicyInformation::from_x509(policy)?); } Ok(Some(Self { @@ -961,9 +958,11 @@ impl PolicyInformation { } #[cfg(all(test, feature = "x509-parser"))] -impl TryFrom> for PolicyInformation { - type Error = Error; - fn try_from(value: x509_parser::extensions::PolicyInformation) -> Result { +// impl TryFrom> for PolicyInformation { +// type Error = Error; +// fn try_from(value: x509_parser::extensions::PolicyInformation) -> Result { +impl PolicyInformation { + fn from_x509(value: x509_parser::extensions::PolicyInformation) -> Result { let mut policy_identifier = Vec::new(); // Contributor question: What error should be returned here? Is this something that can happen? @@ -973,11 +972,6 @@ impl TryFrom> for PolicyInformati policy_identifier.push(v); } - // // This is an alternative way to convert the [`Oid<'_>`] but I think it may yield incorrect results because an arc can be larger than [`u8`] - // for arc in value.policy_id.as_bytes() { - // policy_identifier.push(arc.to_owned() as u64) - // } - let Some(qualifiers) = value.policy_qualifiers else { return Ok(Self { policy_identifier, @@ -1278,6 +1272,33 @@ impl InhibitAnyPolicy { } } +// #[cfg(feature = "x509-parser")] +// impl From for InhibitAnyPolicy { +// fn from(value: x509_parser::extensions::InhibitAnyPolicy) -> Self { +// Self { +// skip_certs: value.skip_certs +// } +// } +// } +#[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>) {