From 474971e52e40726c60b8e82ab026b464e511db5d Mon Sep 17 00:00:00 2001 From: Arthur Gautier Date: Sun, 8 Sep 2024 10:37:29 -0700 Subject: [PATCH] x509-cert: rename helpers to `get_extension`/`filter_extensions` (#1497) As noted in #1491, the naming of the helpers to get or filter extensions was confusing. Fixes #1491. --- x509-cert/src/certificate.rs | 54 ++++++++++++++++++++++++++++++++---- x509-cert/src/ext.rs | 37 ++++++++++++++++++++++++ 2 files changed, 85 insertions(+), 6 deletions(-) diff --git a/x509-cert/src/certificate.rs b/x509-cert/src/certificate.rs index 364ba0f5..d435789a 100644 --- a/x509-cert/src/certificate.rs +++ b/x509-cert/src/certificate.rs @@ -165,13 +165,34 @@ pub struct TbsCertificateInner { impl TbsCertificateInner

{ /// Decodes a single extension /// - /// Returns an error if multiple of these extensions is present. Returns - /// `Ok(None)` if the extension is not present. Returns a decoding error - /// if decoding failed. Otherwise returns the extension. - pub fn get<'a, T: Decode<'a> + AssociatedOid>( + /// Returns `Ok(None)` if the extension is not present. + /// + /// Otherwise returns the extension, and indicates if the extension was marked critical in the + /// boolean. + /// + /// ``` + /// # #[cfg(feature = "pem")] + /// # fn pemonly() { + /// # const CERT_PEM: &str = include_str!("../tests/examples/amazon.pem"); + /// use x509_cert::{der::DecodePem, ext::pkix::BasicConstraints, Certificate}; + /// let certificate = Certificate::from_pem(CERT_PEM.as_bytes()).expect("parse certificate"); + /// + /// let (critical, constraints) = certificate.tbs_certificate.get_extension::() + /// .expect("Failed to parse extension") + /// .expect("Basic constraints expected"); + /// # let _ = constraints; + /// # } + /// ``` + /// + /// # Errors + /// + /// Returns an error if multiple of these extensions are present. + /// + /// Returns a decoding error if decoding failed. + pub fn get_extension<'a, T: Decode<'a> + AssociatedOid>( &'a self, ) -> Result, >::Error> { - let mut iter = self.filter::().peekable(); + let mut iter = self.filter_extensions::().peekable(); match iter.next() { None => Ok(None), Some(item) => match iter.peek() { @@ -184,7 +205,28 @@ impl TbsCertificateInner

{ /// Filters extensions by an associated OID /// /// Returns a filtered iterator over all the extensions with the OID. - pub fn filter<'a, T: Decode<'a> + AssociatedOid>( + /// + /// ``` + /// # #[cfg(feature = "pem")] + /// # fn pemonly() { + /// # const CERT_PEM: &str = include_str!("../tests/examples/amazon.pem"); + /// use x509_cert::{der::DecodePem, ext::pkix::BasicConstraints, Certificate}; + /// let certificate = Certificate::from_pem(CERT_PEM.as_bytes()).expect("parse certificate"); + /// + /// let mut extensions_found = certificate.tbs_certificate.filter_extensions::(); + /// while let Some(Ok((critical, extension))) = extensions_found.next() { + /// println!("Found (critical={critical}): {extension:?}"); + /// } + /// # } + /// ``` + /// + /// # Safety + /// + /// According to [RFC 5290 section 4.2], extensions should not appear more than once. + /// A better alternative is to use [`TbsCertificateInner::get_extension`] instead. + /// + /// [RFC 5290 section 4.2]: https://www.rfc-editor.org/rfc/rfc5280#section-4.2 + pub fn filter_extensions<'a, T: Decode<'a> + AssociatedOid>( &'a self, ) -> impl 'a + Iterator>::Error>> { self.extensions diff --git a/x509-cert/src/ext.rs b/x509-cert/src/ext.rs index acdcc341..ef308458 100644 --- a/x509-cert/src/ext.rs +++ b/x509-cert/src/ext.rs @@ -46,8 +46,45 @@ pub type Extensions = alloc::vec::Vec; /// Trait to be implemented by extensions to allow them to be formatted as x509 v3 extensions by /// builder. +/// +/// # Examples +/// +/// ``` +/// use const_oid::{AssociatedOid, ObjectIdentifier}; +/// use x509_cert::{der::Sequence, ext, name}; +/// +/// /// This extension indicates the age of the captain at the time of signature +/// #[derive(Clone, Debug, Eq, PartialEq, Sequence)] +/// pub struct CaptainAge { +/// pub age: u32, +/// } +/// +/// impl AssociatedOid for CaptainAge { +/// # // https://datatracker.ietf.org/doc/html/rfc5612 +/// # // 32473 is the private OID reserved for documentation. +/// const OID: ObjectIdentifier = ObjectIdentifier::new_unwrap("1.3.6.1.4.1.32473.1"); +/// } +/// +/// impl ext::AsExtension for CaptainAge { +/// fn critical(&self, _subject: &name::Name, _extensions: &[ext::Extension]) -> bool { +/// false +/// } +/// } +/// ``` pub trait AsExtension: AssociatedOid + der::Encode { /// Should the extension be marked critical + /// + /// This affects the behavior of a validator when using the generated certificate. + /// See [RFC 5280 Section 4.2]: + /// ```text + /// A certificate-using system MUST reject the certificate if it encounters + /// a critical extension it does not recognize or a critical extension + /// that contains information that it cannot process. A non-critical + /// extension MAY be ignored if it is not recognized, but MUST be + /// processed if it is recognized. + /// ``` + /// + /// [RFC 5280 Section 4.2]: https://www.rfc-editor.org/rfc/rfc5280#section-4.2 fn critical(&self, subject: &crate::name::Name, extensions: &[Extension]) -> bool; /// Returns the Extension with the content encoded.