Skip to content

Commit 0eb5e83

Browse files
committed
refactor(x509-cert): decompose AsExtension into Criticality + AsExtension
The existing `AsExtension` trait has two design problems: 1. **Criticality is coupled to the type.** The `critical()` method attempts to determine criticality from `subject` and `extensions`, but these are not the only factors. Policy decisions, certificate profiles, or issuer preferences may also influence criticality. Some extensions (like `BasicConstraints`) "MAY appear as critical or non-critical" per RFC 5280. 2. **Requires `der::Encode`.** The trait bounds force all extensions to be DER-encodable, but an extension's `extnValue` is just an `OCTET STRING` - the encoding could theoretically be anything. This refactor extracts a new `Criticality` trait and simplifies `AsExtension`: ```rust pub trait Criticality { fn criticality(&self, subject: &Name, extensions: &[Extension]) -> bool; } pub trait AsExtension { type Error; fn to_extension(&self, subject: &Name, extensions: &[Extension]) -> Result<Extension, Self::Error>; } ``` A blanket impl provides `AsExtension` for `T: Criticality + AssociatedOid + der::Encode`, so most existing extension types just need to rename their impl. **New capabilities:** 1. Override criticality at the call site using a tuple: ```rust // Use default criticality. builder.add_extension(&SubjectAltName(names)))?; // Override default criticality. builder.add_extension(&(true, SubjectAltName(names)))?; ``` 2. Implement `AsExtension` directly for non-DER-encoded extensions: ```rust impl AsExtension for MyCustomExtension { type Error = MyError; fn to_extension(&self, ..) -> Result<Extension, MyError> { // Custom encoding logic } } ``` 3. Builders return `E::Error` directly, letting callers decide error handling: ```rust builder.add_extension(&ext)?; // propagate E::Error builder.add_extension(&ext).map_err(…)? // convert to another type ``` **Migration:** Replace `impl AsExtension` with `impl Criticality` and rename `critical()` to `criticality()`. The blanket impl provides `AsExtension` automatically.
1 parent fe76359 commit 0eb5e83

File tree

11 files changed

+67
-38
lines changed

11 files changed

+67
-38
lines changed

x509-cert/src/builder.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -219,10 +219,9 @@ where
219219
/// Extensions need to implement [`AsExtension`], examples may be found in
220220
/// in [`AsExtension` documentation](../ext/trait.AsExtension.html#examples) or
221221
/// [the implementors](../ext/trait.AsExtension.html#implementors).
222-
pub fn add_extension<E: AsExtension>(&mut self, extension: &E) -> Result<()> {
222+
pub fn add_extension<E: AsExtension>(&mut self, extension: &E) -> core::result::Result<(), E::Error> {
223223
let ext = extension.to_extension(&self.tbs.subject, &self.extensions)?;
224224
self.extensions.push(ext);
225-
226225
Ok(())
227226
}
228227
}

x509-cert/src/ext.rs

Lines changed: 51 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,26 @@ pub struct Extension {
4444
/// [RFC 5280 Section 4.1.2.9]: https://datatracker.ietf.org/doc/html/rfc5280#section-4.1.2.9
4545
pub type Extensions = alloc::vec::Vec<Extension>;
4646

47+
/// Trait for types that define their default criticality as an extension.
48+
///
49+
/// This is used for most der::Encode types that are used as extensions.
50+
pub trait Criticality {
51+
/// Should the extension be marked critical
52+
///
53+
/// This affects the behavior of a validator when using the generated certificate.
54+
/// See [RFC 5280 Section 4.2]:
55+
/// ```text
56+
/// A certificate-using system MUST reject the certificate if it encounters
57+
/// a critical extension it does not recognize or a critical extension
58+
/// that contains information that it cannot process. A non-critical
59+
/// extension MAY be ignored if it is not recognized, but MUST be
60+
/// processed if it is recognized.
61+
/// ```
62+
///
63+
/// [RFC 5280 Section 4.2]: https://www.rfc-editor.org/rfc/rfc5280#section-4.2
64+
fn criticality(&self, subject: &crate::name::Name, extensions: &[Extension]) -> bool;
65+
}
66+
4767
/// Trait to be implemented by extensions to allow them to be formatted as x509 v3 extensions by
4868
/// builder.
4969
///
@@ -65,40 +85,50 @@ pub type Extensions = alloc::vec::Vec<Extension>;
6585
/// const OID: ObjectIdentifier = ObjectIdentifier::new_unwrap("1.3.6.1.4.1.32473.1");
6686
/// }
6787
///
68-
/// impl ext::AsExtension for CaptainAge {
69-
/// fn critical(&self, _subject: &name::Name, _extensions: &[ext::Extension]) -> bool {
88+
/// impl ext::Criticality for CaptainAge {
89+
/// fn criticality(&self, _subject: &name::Name, _extensions: &[ext::Extension]) -> bool {
7090
/// false
7191
/// }
7292
/// }
7393
/// ```
74-
pub trait AsExtension: AssociatedOid + der::Encode {
75-
/// Should the extension be marked critical
76-
///
77-
/// This affects the behavior of a validator when using the generated certificate.
78-
/// See [RFC 5280 Section 4.2]:
79-
/// ```text
80-
/// A certificate-using system MUST reject the certificate if it encounters
81-
/// a critical extension it does not recognize or a critical extension
82-
/// that contains information that it cannot process. A non-critical
83-
/// extension MAY be ignored if it is not recognized, but MUST be
84-
/// processed if it is recognized.
85-
/// ```
86-
///
87-
/// [RFC 5280 Section 4.2]: https://www.rfc-editor.org/rfc/rfc5280#section-4.2
88-
fn critical(&self, subject: &crate::name::Name, extensions: &[Extension]) -> bool;
94+
pub trait AsExtension {
95+
/// The error type returned when encoding the extension.
96+
type Error;
8997

9098
/// Returns the Extension with the content encoded.
9199
fn to_extension(
92100
&self,
93101
subject: &crate::name::Name,
94102
extensions: &[Extension],
95-
) -> Result<Extension, der::Error> {
96-
let content = OctetString::new(<Self as der::Encode>::to_der(self)?)?;
103+
) -> Result<Extension, Self::Error>;
104+
}
105+
106+
impl<T: Criticality + AssociatedOid + der::Encode> AsExtension for T {
107+
type Error = der::Error;
97108

109+
fn to_extension(
110+
&self,
111+
subject: &crate::name::Name,
112+
extensions: &[Extension],
113+
) -> Result<Extension, Self::Error> {
98114
Ok(Extension {
99115
extn_id: <Self as AssociatedOid>::OID,
100-
critical: self.critical(subject, extensions),
101-
extn_value: content,
116+
critical: self.criticality(subject, extensions),
117+
extn_value: OctetString::new(self.to_der()?)?,
102118
})
103119
}
104120
}
121+
122+
impl<T: AsExtension> AsExtension for (bool, T) {
123+
type Error = T::Error;
124+
125+
fn to_extension(
126+
&self,
127+
subject: &crate::name::Name,
128+
extensions: &[Extension],
129+
) -> Result<Extension, Self::Error> {
130+
let mut extension = self.1.to_extension(subject, extensions)?;
131+
extension.critical = self.0;
132+
Ok(extension)
133+
}
134+
}

x509-cert/src/ext/pkix.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -78,8 +78,8 @@ impl AssociatedOid for SubjectAltName {
7878

7979
impl_newtype!(SubjectAltName, name::GeneralNames);
8080

81-
impl crate::ext::AsExtension for SubjectAltName {
82-
fn critical(&self, subject: &crate::name::Name, _extensions: &[super::Extension]) -> bool {
81+
impl crate::ext::Criticality for SubjectAltName {
82+
fn criticality(&self, subject: &crate::name::Name, _extensions: &[super::Extension]) -> bool {
8383
// https://datatracker.ietf.org/doc/html/rfc5280#section-4.2.1.6
8484
// Further, if the only subject identity included in the certificate is
8585
// an alternative name form (e.g., an electronic mail address), then the

x509-cert/src/ext/pkix/constraints/basic.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,8 @@ impl AssociatedOid for BasicConstraints {
2323
const OID: ObjectIdentifier = ID_CE_BASIC_CONSTRAINTS;
2424
}
2525

26-
impl crate::ext::AsExtension for BasicConstraints {
27-
fn critical(
26+
impl crate::ext::Criticality for BasicConstraints {
27+
fn criticality(
2828
&self,
2929
_subject: &crate::name::Name,
3030
_extensions: &[crate::ext::Extension],

x509-cert/src/macros.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -81,14 +81,14 @@ macro_rules! impl_newtype {
8181
};
8282
}
8383

84-
/// Implements the AsExtension traits for every defined Extension paylooad
84+
/// Implements the Criticality trait for every defined Extension paylooad
8585
macro_rules! impl_extension {
8686
($newtype:ty) => {
8787
impl_extension!($newtype, critical = false);
8888
};
8989
($newtype:ty, critical = $critical:expr) => {
90-
impl crate::ext::AsExtension for $newtype {
91-
fn critical(
90+
impl crate::ext::Criticality for $newtype {
91+
fn criticality(
9292
&self,
9393
_subject: &crate::name::Name,
9494
_extensions: &[crate::ext::Extension],

x509-cert/src/request/builder.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ impl RequestBuilder {
8080
/// Extensions need to implement [`AsExtension`], examples may be found in
8181
/// in [`AsExtension` documentation](../ext/trait.AsExtension.html#examples) or
8282
/// [the implementors](../ext/trait.AsExtension.html#implementors).
83-
pub fn add_extension<E: AsExtension>(&mut self, extension: &E) -> Result<()> {
83+
pub fn add_extension<E: AsExtension>(&mut self, extension: &E) -> core::result::Result<(), E::Error> {
8484
let ext = extension.to_extension(&self.info.subject, &self.extension_req.0)?;
8585

8686
self.extension_req.0.push(ext);

x509-ocsp/src/basic.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -171,7 +171,7 @@ mod builder {
171171
/// extension encoding fails.
172172
///
173173
/// [RFC 6960 Section 4.4]: https://datatracker.ietf.org/doc/html/rfc6960#section-4.4
174-
pub fn with_extension(mut self, ext: impl AsExtension) -> Result<Self, Error> {
174+
pub fn with_extension<E: AsExtension>(mut self, ext: E) -> Result<Self, E::Error> {
175175
let ext = ext.to_extension(&Name::default(), &[])?;
176176
match self.single_extensions {
177177
Some(ref mut exts) => exts.push(ext),

x509-ocsp/src/builder/request.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ impl OcspRequestBuilder {
9696
/// extension encoding fails.
9797
///
9898
/// [RFC 6960 Section 4.4]: https://datatracker.ietf.org/doc/html/rfc6960#section-4.4
99-
pub fn with_extension(mut self, ext: impl AsExtension) -> Result<Self, Error> {
99+
pub fn with_extension<E: AsExtension>(mut self, ext: E) -> Result<Self, E::Error> {
100100
let ext = ext.to_extension(&Name::default(), &[])?;
101101
match self.tbs.request_extensions {
102102
Some(ref mut exts) => exts.push(ext),

x509-ocsp/src/builder/response.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ impl OcspResponseBuilder {
101101
/// extension encoding fails.
102102
///
103103
/// [RFC 6960 Section 4.4]: https://datatracker.ietf.org/doc/html/rfc6960#section-4.4
104-
pub fn with_extension(mut self, ext: impl AsExtension) -> Result<Self, Error> {
104+
pub fn with_extension<E: AsExtension>(mut self, ext: E) -> Result<Self, E::Error> {
105105
let ext = ext.to_extension(&Name::default(), &[])?;
106106
match self.response_extensions {
107107
Some(ref mut exts) => exts.push(ext),

x509-ocsp/src/ext.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ use der::{
1515
};
1616
use spki::AlgorithmIdentifierOwned;
1717
use x509_cert::{
18-
ext::{AsExtension, Extension, pkix::AuthorityInfoAccessSyntax},
18+
ext::{Criticality, Extension, pkix::AuthorityInfoAccessSyntax},
1919
impl_newtype,
2020
name::Name,
2121
};
@@ -26,8 +26,8 @@ use rand_core::CryptoRng;
2626
// x509-cert's is not exported
2727
macro_rules! impl_extension {
2828
($newtype:ty, critical = $critical:expr) => {
29-
impl AsExtension for $newtype {
30-
fn critical(&self, _subject: &Name, _extensions: &[Extension]) -> bool {
29+
impl Criticality for $newtype {
30+
fn criticality(&self, _subject: &Name, _extensions: &[Extension]) -> bool {
3131
$critical
3232
}
3333
}

0 commit comments

Comments
 (0)