Skip to content

Conversation

@npmccallum
Copy link
Contributor

@npmccallum npmccallum commented Dec 7, 2025

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:

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:
// Use default criticality.
builder.add_extension(&SubjectAltName(names)))?;

// Override default criticality.
builder.add_extension(&(true, SubjectAltName(names)))?;
  1. Implement AsExtension directly for non-DER-encoded extensions:
impl AsExtension for MyCustomExtension {
    type Error = MyError;
    fn to_extension(&self, ..) -> Result<Extension, MyError> {
        // Custom encoding logic
    }
}
  1. Builders return E::Error directly, letting callers decide error handling:
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.

…sion

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.
@carl-wallace
Copy link
Contributor

carl-wallace commented Dec 7, 2025 via email

@npmccallum
Copy link
Contributor Author

I found the formatting of the email from @carl-wallace hard to read. So I'm extracting what I think is his comment to make it easier to see. Please let me know if I got it wrong.

[CW] Even though some certs feature “anything” in the OCTET STRING,
the contents of the OCTET STRING are supposed to be DER-encodable.
RFC 5280 states:

   Each extension includes an OID and an ASN.1 structure. When an
   extension appears in a certificate, the OID appears as the field
   extnID and the corresponding ASN.1 DER encoded structure is the
   value of the octet string extnValue.

RFC5912 defines Extension this way with a CONTAINING clause:

  Extension{EXTENSION:ExtensionSet} ::= SEQUENCE {
      extnID      EXTENSION.&id({ExtensionSet}),
      critical    BOOLEAN
  --                     (EXTENSION.&Critical({ExtensionSet}{@extnID}))
                       DEFAULT FALSE,
      extnValue   OCTET STRING (CONTAINING
                  EXTENSION.&ExtnType({ExtensionSet}{@extnID}))
                  --  contains the DER encoding of the ASN.1 value
                  --  corresponding to the extension type identified
                  --  by extnID
  }

@carl-wallace
Copy link
Contributor

That's it. The snip was copied from here: Yubico/yubico-piv-tool#181). I don't much care if there is an accommodation for mis-encoded certs (given they exist in the wild) but we ought not to encourage them.

@tarcieri
Copy link
Member

Sidebar: I find it a bit odd that the trait is called AsExtension but has one method to_extension. Should it be called ToExtension instead? As* usually suggests borrowing.

@tarcieri tarcieri merged commit a20bcb8 into RustCrypto:master Dec 10, 2025
54 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants