Skip to content

Commit

Permalink
x509-cert: rename helpers to get_extension/filter_extensions (#1497)
Browse files Browse the repository at this point in the history
As noted in #1491, the naming of the helpers to get or filter extensions
was confusing.

Fixes #1491.
  • Loading branch information
baloo authored Sep 8, 2024
1 parent 33e4c72 commit 474971e
Show file tree
Hide file tree
Showing 2 changed files with 85 additions and 6 deletions.
54 changes: 48 additions & 6 deletions x509-cert/src/certificate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -165,13 +165,34 @@ pub struct TbsCertificateInner<P: Profile = Rfc5280> {
impl<P: Profile> TbsCertificateInner<P> {
/// 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::<BasicConstraints>()
/// .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<Option<(bool, T)>, <T as Decode<'a>>::Error> {
let mut iter = self.filter::<T>().peekable();
let mut iter = self.filter_extensions::<T>().peekable();
match iter.next() {
None => Ok(None),
Some(item) => match iter.peek() {
Expand All @@ -184,7 +205,28 @@ impl<P: Profile> TbsCertificateInner<P> {
/// 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::<BasicConstraints>();
/// 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<Item = Result<(bool, T), <T as Decode<'a>>::Error>> {
self.extensions
Expand Down
37 changes: 37 additions & 0 deletions x509-cert/src/ext.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,45 @@ pub type Extensions = alloc::vec::Vec<Extension>;

/// 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.
Expand Down

0 comments on commit 474971e

Please sign in to comment.