From 43dfaefd8620e8e672f1ff09c97972ca3151bfe0 Mon Sep 17 00:00:00 2001 From: Nathaniel McCallum Date: Thu, 11 Dec 2025 09:33:40 -0600 Subject: [PATCH] refactor(ext)!: improve extension API flexibility and ergonomics Redesigned the extension trait interface to support more flexible ownership patterns and use cases. The trait is renamed from AsExtension to ToExtension and now consumes values by default, with implementations for references and tuples like (OID, bool, &T) to allow direct control over criticality and OID. This makes the API more ergonomic for common scenarios while enabling advanced use cases that weren't previously possible. BREAKING CHANGE: The AsExtension trait has been renamed to ToExtension, and to_extension now takes self by value. Users must update trait bounds from AsExtension to ToExtension and may need to adjust how extensions are passed to add_extension methods. --- x509-cert/src/builder.rs | 12 ++--- x509-cert/src/builder/profile/cabf.rs | 2 +- x509-cert/src/builder/profile/cabf/tls.rs | 2 +- x509-cert/src/builder/profile/devid.rs | 2 +- x509-cert/src/ext.rs | 66 +++++++++++++++++------ x509-cert/src/request/builder.rs | 12 ++--- x509-cert/tests/builder_crl.rs | 2 +- x509-ocsp/src/basic.rs | 4 +- x509-ocsp/src/builder/request.rs | 8 +-- x509-ocsp/src/builder/response.rs | 6 +-- x509-ocsp/src/request.rs | 4 +- x509-ocsp/tests/builder.rs | 12 ++--- 12 files changed, 84 insertions(+), 48 deletions(-) diff --git a/x509-cert/src/builder.rs b/x509-cert/src/builder.rs index 29037748d..811d0c5c4 100644 --- a/x509-cert/src/builder.rs +++ b/x509-cert/src/builder.rs @@ -15,7 +15,7 @@ use crate::{ certificate::{self, Certificate, TbsCertificate, Version}, crl::{CertificateList, RevokedCert, TbsCertList}, ext::{ - AsExtension, Extensions, + Extensions, ToExtension, pkix::{AuthorityKeyIdentifier, CrlNumber, SubjectKeyIdentifier}, }, serial_number::SerialNumber, @@ -216,12 +216,12 @@ where /// Add an extension to this certificate /// - /// Extensions need to implement [`AsExtension`], examples may be found in - /// in [`AsExtension` documentation](../ext/trait.AsExtension.html#examples) or - /// [the implementors](../ext/trait.AsExtension.html#implementors). - pub fn add_extension( + /// Extensions need to implement [`ToExtension`], examples may be found in + /// in [`ToExtension` documentation](../ext/trait.ToExtension.html#examples) or + /// [the implementors](../ext/trait.ToExtension.html#implementors). + pub fn add_extension( &mut self, - extension: &E, + extension: E, ) -> core::result::Result<(), E::Error> { let ext = extension.to_extension(&self.tbs.subject, &self.extensions)?; self.extensions.push(ext); diff --git a/x509-cert/src/builder/profile/cabf.rs b/x509-cert/src/builder/profile/cabf.rs index d69b0c2b1..2b6050376 100644 --- a/x509-cert/src/builder/profile/cabf.rs +++ b/x509-cert/src/builder/profile/cabf.rs @@ -8,7 +8,7 @@ use crate::{ builder::{BuilderProfile, Error, Result}, certificate::TbsCertificate, ext::{ - AsExtension, Extension, + Extension, ToExtension, pkix::{ AuthorityKeyIdentifier, BasicConstraints, KeyUsage, KeyUsages, SubjectKeyIdentifier, }, diff --git a/x509-cert/src/builder/profile/cabf/tls.rs b/x509-cert/src/builder/profile/cabf/tls.rs index 4b28d5544..9cf64a283 100644 --- a/x509-cert/src/builder/profile/cabf/tls.rs +++ b/x509-cert/src/builder/profile/cabf/tls.rs @@ -16,7 +16,7 @@ use crate::{ builder::{BuilderProfile, Result}, certificate::TbsCertificate, ext::{ - AsExtension, Extension, + Extension, ToExtension, pkix::{ AuthorityKeyIdentifier, BasicConstraints, ExtendedKeyUsage, KeyUsage, KeyUsages, SubjectKeyIdentifier, name::GeneralNames, diff --git a/x509-cert/src/builder/profile/devid.rs b/x509-cert/src/builder/profile/devid.rs index 14582cb6b..0d7e884a5 100644 --- a/x509-cert/src/builder/profile/devid.rs +++ b/x509-cert/src/builder/profile/devid.rs @@ -19,7 +19,7 @@ use crate::{ builder::{BuilderProfile, Result}, certificate::TbsCertificate, ext::{ - AsExtension, Extension, + Extension, ToExtension, pkix::{ AuthorityKeyIdentifier, KeyUsage, KeyUsages, SubjectAltName, name::{GeneralName, GeneralNames, HardwareModuleName, OtherName}, diff --git a/x509-cert/src/ext.rs b/x509-cert/src/ext.rs index 511659304..da92b237d 100644 --- a/x509-cert/src/ext.rs +++ b/x509-cert/src/ext.rs @@ -35,6 +35,18 @@ pub struct Extension { pub extn_value: OctetString, } +impl ToExtension for Extension { + type Error = der::Error; + + fn to_extension( + self, + _subject: &crate::name::Name, + _extensions: &[Extension], + ) -> Result { + Ok(self) + } +} + /// Extensions as defined in [RFC 5280 Section 4.1.2.9]. /// /// ```text @@ -91,44 +103,68 @@ pub trait Criticality { /// } /// } /// ``` -pub trait AsExtension { +pub trait ToExtension { /// The error type returned when encoding the extension. type Error; /// Returns the Extension with the content encoded. fn to_extension( - &self, + self, subject: &crate::name::Name, extensions: &[Extension], ) -> Result; } -impl AsExtension for T { +impl ToExtension for &T { type Error = der::Error; fn to_extension( - &self, + self, subject: &crate::name::Name, extensions: &[Extension], ) -> Result { - Ok(Extension { - extn_id: ::OID, - critical: self.criticality(subject, extensions), - extn_value: OctetString::new(self.to_der()?)?, - }) + let criticality = self.criticality(subject, extensions); + (criticality, self).to_extension(subject, extensions) + } +} + +impl ToExtension for (ObjectIdentifier, &T) { + type Error = der::Error; + + fn to_extension( + self, + subject: &crate::name::Name, + extensions: &[Extension], + ) -> Result { + let criticality = self.1.criticality(subject, extensions); + (self.0, criticality, self.1).to_extension(subject, extensions) } } -impl AsExtension for (bool, T) { - type Error = T::Error; +impl ToExtension for (bool, &T) { + type Error = der::Error; fn to_extension( - &self, + self, subject: &crate::name::Name, extensions: &[Extension], ) -> Result { - let mut extension = self.1.to_extension(subject, extensions)?; - extension.critical = self.0; - Ok(extension) + (T::OID, self.0, self.1).to_extension(subject, extensions) + } +} + +impl ToExtension for (ObjectIdentifier, bool, &T) { + type Error = der::Error; + + fn to_extension( + self, + _subject: &crate::name::Name, + _extensions: &[Extension], + ) -> Result { + Ok(Extension { + extn_id: self.0, + critical: self.1, + extn_value: OctetString::new(self.2.to_der()?)?, + }) } } diff --git a/x509-cert/src/request/builder.rs b/x509-cert/src/request/builder.rs index db26fda57..84ae5c96c 100644 --- a/x509-cert/src/request/builder.rs +++ b/x509-cert/src/request/builder.rs @@ -8,7 +8,7 @@ use spki::{ use crate::{ builder::{Builder, Error, NULL_OID, Result}, - ext::AsExtension, + ext::ToExtension, name::Name, request::{CertReq, CertReqInfo, ExtensionReq, attributes::AsAttribute}, }; @@ -77,12 +77,12 @@ impl RequestBuilder { /// Add an extension to this certificate request /// - /// Extensions need to implement [`AsExtension`], examples may be found in - /// in [`AsExtension` documentation](../ext/trait.AsExtension.html#examples) or - /// [the implementors](../ext/trait.AsExtension.html#implementors). - pub fn add_extension( + /// Extensions need to implement [`ToExtension`], examples may be found in + /// in [`ToExtension` documentation](../ext/trait.ToExtension.html#examples) or + /// [the implementors](../ext/trait.ToExtension.html#implementors). + pub fn add_extension( &mut self, - extension: &E, + extension: E, ) -> core::result::Result<(), E::Error> { let ext = extension.to_extension(&self.info.subject, &self.extension_req.0)?; diff --git a/x509-cert/tests/builder_crl.rs b/x509-cert/tests/builder_crl.rs index 2a8f81726..41f8ecd41 100644 --- a/x509-cert/tests/builder_crl.rs +++ b/x509-cert/tests/builder_crl.rs @@ -17,7 +17,7 @@ use x509_cert::{ certificate::Rfc5280, crl::RevokedCert, ext::{ - AsExtension, + ToExtension, pkix::{CrlNumber, CrlReason, name::GeneralName}, }, name::Name, diff --git a/x509-ocsp/src/basic.rs b/x509-ocsp/src/basic.rs index e4dfb72b2..3c3cf2d43 100644 --- a/x509-ocsp/src/basic.rs +++ b/x509-ocsp/src/basic.rs @@ -130,7 +130,7 @@ mod builder { use const_oid::AssociatedOid; use digest::Digest; use x509_cert::{ - Certificate, crl::CertificateList, ext::AsExtension, name::Name, + Certificate, crl::CertificateList, ext::ToExtension, name::Name, serial_number::SerialNumber, }; @@ -171,7 +171,7 @@ mod builder { /// extension encoding fails. /// /// [RFC 6960 Section 4.4]: https://datatracker.ietf.org/doc/html/rfc6960#section-4.4 - pub fn with_extension(mut self, ext: E) -> Result { + pub fn with_extension(mut self, ext: E) -> Result { let ext = ext.to_extension(&Name::default(), &[])?; match self.single_extensions { Some(ref mut exts) => exts.push(ext), diff --git a/x509-ocsp/src/builder/request.rs b/x509-ocsp/src/builder/request.rs index 1b9bc54f7..45cb587fd 100644 --- a/x509-ocsp/src/builder/request.rs +++ b/x509-ocsp/src/builder/request.rs @@ -9,7 +9,7 @@ use spki::{DynSignatureAlgorithmIdentifier, SignatureBitStringEncoding}; use x509_cert::{ Certificate, certificate::Rfc5280, - ext::{AsExtension, pkix::name::GeneralName}, + ext::{ToExtension, pkix::name::GeneralName}, name::Name, }; @@ -45,7 +45,7 @@ use x509_cert::{ /// .with_request(Request::from_issuer::(&issuer, SerialNumber::from(2usize)).unwrap()) /// .with_request(Request::from_issuer::(&issuer, SerialNumber::from(3usize)).unwrap()) /// .with_request(Request::from_issuer::(&issuer, SerialNumber::from(4usize)).unwrap()) -/// .with_extension(Nonce::generate(&mut rng, 32).unwrap()) +/// .with_extension(&Nonce::generate(&mut rng, 32).unwrap()) /// .unwrap() /// .build(); /// @@ -53,7 +53,7 @@ use x509_cert::{ /// let signer_cert_chain = vec![cert.clone()]; /// let req = OcspRequestBuilder::default() /// .with_request(Request::from_cert::(&issuer, &cert).unwrap()) -/// .with_extension(Nonce::generate(&mut rng, 32).unwrap()) +/// .with_extension(&Nonce::generate(&mut rng, 32).unwrap()) /// .unwrap() /// .sign(&mut signer, Some(signer_cert_chain)) /// .unwrap(); @@ -96,7 +96,7 @@ impl OcspRequestBuilder { /// extension encoding fails. /// /// [RFC 6960 Section 4.4]: https://datatracker.ietf.org/doc/html/rfc6960#section-4.4 - pub fn with_extension(mut self, ext: E) -> Result { + pub fn with_extension(mut self, ext: E) -> Result { let ext = ext.to_extension(&Name::default(), &[])?; match self.tbs.request_extensions { Some(ref mut exts) => exts.push(ext), diff --git a/x509-ocsp/src/builder/response.rs b/x509-ocsp/src/builder/response.rs index 06061ac74..7152418b0 100644 --- a/x509-ocsp/src/builder/response.rs +++ b/x509-ocsp/src/builder/response.rs @@ -11,7 +11,7 @@ use signature::{RandomizedSigner, Signer}; use spki::{DynSignatureAlgorithmIdentifier, SignatureBitStringEncoding}; use x509_cert::{ Certificate, - ext::{AsExtension, Extensions}, + ext::{Extensions, ToExtension}, name::Name, }; @@ -53,7 +53,7 @@ use x509_cert::{ /// ); /// /// if let Some(nonce) = req.nonce() { -/// builder = builder.with_extension(nonce).unwrap(); +/// builder = builder.with_extension(&nonce).unwrap(); /// } /// /// #[cfg(feature = "std")] @@ -101,7 +101,7 @@ impl OcspResponseBuilder { /// extension encoding fails. /// /// [RFC 6960 Section 4.4]: https://datatracker.ietf.org/doc/html/rfc6960#section-4.4 - pub fn with_extension(mut self, ext: E) -> Result { + pub fn with_extension(mut self, ext: E) -> Result { let ext = ext.to_extension(&Name::default(), &[])?; match self.response_extensions { Some(ref mut exts) => exts.push(ext), diff --git a/x509-ocsp/src/request.rs b/x509-ocsp/src/request.rs index 53b67f432..f6a3f71f3 100644 --- a/x509-ocsp/src/request.rs +++ b/x509-ocsp/src/request.rs @@ -127,7 +127,7 @@ mod builder { use crate::{CertId, Request, builder::Error}; use const_oid::AssociatedOid; use digest::Digest; - use x509_cert::{Certificate, ext::AsExtension, name::Name, serial_number::SerialNumber}; + use x509_cert::{Certificate, ext::ToExtension, name::Name, serial_number::SerialNumber}; impl Request { /// Returns a new `Request` with the specified `CertID` @@ -172,7 +172,7 @@ mod builder { /// extension encoding fails. /// /// [RFC 6960 Section 4.4]: https://datatracker.ietf.org/doc/html/rfc6960#section-4.4 - pub fn with_extension(mut self, ext: E) -> Result { + pub fn with_extension(mut self, ext: E) -> Result { let ext = ext.to_extension(&Name::default(), &[])?; match self.single_request_extensions { Some(ref mut exts) => exts.push(ext), diff --git a/x509-ocsp/tests/builder.rs b/x509-ocsp/tests/builder.rs index fe85c6090..6c91b94a5 100644 --- a/x509-ocsp/tests/builder.rs +++ b/x509-ocsp/tests/builder.rs @@ -126,12 +126,12 @@ fn encode_ocsp_req_multiple_extensions() { .with_request( Request::from_issuer::(&ISSUER, SerialNumber::from(0x10001usize)) .unwrap() - .with_extension(single_ext1) + .with_extension(&single_ext1) .unwrap(), ) - .with_extension(ext1) + .with_extension(&ext1) .unwrap() - .with_extension(ext2) + .with_extension(&ext2) .unwrap() .build(); assert_eq!(&req.to_der().unwrap(), &req_der); @@ -296,12 +296,12 @@ fn encode_ocsp_resp_multiple_extensions() { .with_next_update(OcspGeneralizedTime::from( DateTime::new(2020, 1, 1, 0, 0, 0).unwrap(), )) - .with_extension(single_ext1) + .with_extension(&single_ext1) .unwrap() - .with_extension(single_ext2) + .with_extension(&single_ext2) .unwrap(), ) - .with_extension(ext1) + .with_extension(&ext1) .unwrap() .sign( &mut signer,