Skip to content

add pluggable crypto provider - #450

Open
jgreeer wants to merge 19 commits into
rustls:mainfrom
jgreeer:add-pluggable-crypto-provider
Open

add pluggable crypto provider#450
jgreeer wants to merge 19 commits into
rustls:mainfrom
jgreeer:add-pluggable-crypto-provider

Conversation

@jgreeer

@jgreeer jgreeer commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

Adds a pluggable cryptography provider API to rcgen, allowing applications to use custom backends without depending on Ring or AWS-LC. This design is inspired by rustls and jsonwebtoken.

This PR is a follow up on #414. The goal is to support our own external SymCrypt provider crate.

Ring and AWS-LC remain available through built-in provider implementations.

  • Added a CryptoProvider trait providing:
    • Hashing
    • Key generation
    • Private-key loading
    • Signature verification
  • Updated APIs for key generation/loading, certificate signing, CRL signing, CSR parsing, and convenience helpers to take a provider explicitly.
  • Removed process-wide provider state and automatic provider selection.
  • Moved Ring and AWS-LC operations into independent provider modules.
  • Made KeyPair backend-neutral by storing a SigningKey trait object instead of concrete Ring/AWS-LC key types.
  • Routed certificate serial-number and key-identifier hashing through CryptoProvider::hash.
  • Routed CSR self-signature verification through CryptoProvider::verify instead of x509-parser crypto features.
  • Made P-521 algorithm metadata available to custom providers.
  • Brought over ML-DSA support in the AWS-LC provider (Take advantage of stable ML-DSA in aws-lc-rs #447).
  • Added provider documentation and end-to-end custom-provider tests.

Built-in providers must now be enabled and selected explicitly:

  • rcgen does not enable Ring or AWS-LC by default.
  • The ring feature exposes the built-in Ring provider.
  • The aws_lc_rs feature exposes the built-in AWS-LC provider.
  • If both are enabled, the application still chooses which provider to pass.

Providers are passed directly to APIs:

let provider = rcgen::crypto::ring::default_provider();

let key = KeyPair::generate(provider)?;
let cert = params.self_signed(&key, provider)?;

Resolves #228

@cpu

cpu commented Sep 2, 2026

Copy link
Copy Markdown
Member

Thanks for picking this up.

Custom providers can either be installed once:
...
Or passed directly to explicit APIs:
...
The explicit form is intended for libraries and avoids process-global state.

I think we should probably model this like rustls main and not like rustls 0.23. E.g. splitting the providers we offer first-party into their own crates in the workspace, and requiring all the library code consumers to instantiate the provider explicitly, not doing any kind of default in rcgen's lib code itself based on feature flags.

@djc Are we on the same page there?

@djc djc left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks! This looks directionally right to me.

Comment thread rcgen/src/crypto/mod.rs Outdated
Comment thread rcgen/src/crypto/mod.rs Outdated
Comment thread rcgen/src/crypto/mod.rs Outdated
Comment thread rcgen/src/crypto/mod.rs Outdated
Comment thread rcgen/src/crypto/mod.rs
fn load_private_key(
&self,
key_der: PrivateKeyDer<'static>,
algorithm: Option<&'static SignatureAlgorithm>,

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we need algorithm here?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i think this is for compatibility with from_der_and_sign_algo. the old KeyPair was already bound to SignatureAlgorithim. this could be changed to have the signing algorithim desicion happen at the time of actual signing though.

Comment thread rcgen/src/crypto/mod.rs Outdated
Comment thread rcgen/src/crypto/aws_lc_rs.rs Outdated
@jgreeer

jgreeer commented Sep 2, 2026

Copy link
Copy Markdown
Contributor Author

Thanks for picking this up.

Custom providers can either be installed once:
...
Or passed directly to explicit APIs:
...
The explicit form is intended for libraries and avoids process-global state.

I think we should probably model this like rustls main and not like rustls 0.23. E.g. splitting the providers we offer first-party into their own crates in the workspace, and requiring all the library code consumers to instantiate the provider explicitly, not doing any kind of default in rcgen's lib code itself based on feature flags.

@djc Are we on the same page there?

hey! thanks for the quick review. just before i change anything, are you in agreement with this as well @djc?

@djc

djc commented Sep 2, 2026

Copy link
Copy Markdown
Member

I agree that we should not have a default provider or enable any provider by default, and API that needs it should take one explicitly. I don't think it's worth splitting the providers out into separate crates at this point? We're not close to API stability and I feel like rcgen is much less likely than rustls to end up deeper in dependency graphs.

@cpu

cpu commented Sep 2, 2026

Copy link
Copy Markdown
Member

I don't think it's worth splitting the providers out into separate crates at this point?

That's fine with me 👍

Comment thread rcgen/src/certificate.rs Outdated
Comment thread rcgen/src/certificate.rs Outdated
public_key: &impl PublicKeyData,
public_key: &(impl PublicKeyData + ?Sized),
issuer: &Issuer<'_, impl SigningKey>,
#[cfg(feature = "crypto")] provider: &dyn CryptoProvider,

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we've kind of made a mess of this already with the crypto feature but I'd like to avoid making more pub API surface that has feature-gated params/fields. Unification makes it unpleasant for consumers who can have working code fail to compile after adding a dep that activates the feature under them.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed. Also given the existence of an impl SigningKey there must be a CryptoProvider in play somewhere?

Feels to me like we should just get rid of the crypto feature in this PR because the crypto dependencies are instead abstracted via traits, and the aws-lc-rs and ring features should in principle only be used for their respective provider implementations?

Comment thread rcgen/src/crypto/mod.rs Outdated
Comment thread rcgen/src/certificate.rs Outdated
public_key: &impl PublicKeyData,
public_key: &(impl PublicKeyData + ?Sized),
issuer: &Issuer<'_, impl SigningKey>,
#[cfg(feature = "crypto")] provider: &dyn CryptoProvider,

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed. Also given the existence of an impl SigningKey there must be a CryptoProvider in play somewhere?

Feels to me like we should just get rid of the crypto feature in this PR because the crypto dependencies are instead abstracted via traits, and the aws-lc-rs and ring features should in principle only be used for their respective provider implementations?

Comment thread .github/workflows/ci.yml Outdated
Comment thread .github/workflows/ci.yml Outdated
- run: cargo clippy --no-default-features --features aws_lc_rs_unstable,pem,x509-parser --all-targets
- run: cargo clippy --no-default-features --features aws_lc_rs --all-targets
- run: cargo clippy --no-default-features --features fips,pem,x509-parser --all-targets
- run: cargo clippy --no-default-features --features aws_lc_rs,fips,pem,x509-parser --all-targets

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this necessary?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i coupled aws_lc_rs + fips together as flags in this PR, but it's ultimately irrelevant to the actual provider abstration. since we're going to rewrite that in the future anyways, should i just revert to previous behaviour?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I went ahead and removed this for now, I'm not sure if the way the flag works right now is how it's intended to?

Comment thread rcgen/examples/rsa-irc-openssl.rs Outdated
Comment thread rcgen/src/crypto/mod.rs
Comment thread rcgen/src/crypto/mod.rs Outdated
Comment thread rcgen/src/crypto/mod.rs
fn generate(
&self,
algorithm: &'static SignatureAlgorithm,
key_size: Option<RsaKeySize>,

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of passing in a separate key size, suggest we first (in a separate PR) change it so there are separate SignatureAlgorithm values for different RSA key sizes.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, happy to make a PR for this

Comment thread rcgen/src/certificate.rs Outdated
Comment thread rcgen/src/certificate.rs Outdated
#[cfg(all(
test,
feature = "x509-parser",
any(feature = "ring", feature = "aws_lc_rs")

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think we need the any here?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah, there are some other lines where this was added too. took those out as well

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the test module in certificate.rs pulls in a provider through crate::test_provider(), so we actually do have to keep this.

Comment thread rcgen/src/certificate.rs Outdated
&self,
writer: &mut DERWriterSeq,
pub_key_spki: Option<&[u8]>,
provider: Option<&dyn CryptoProvider>,

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The separate Options for pub_key_spki and provider don't make sense.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do you mean combining them into one parameter? the only thing with that is that it's different from the shape of the other functions

Comment thread rcgen/src/crl.rs Outdated
})
}

fn validate(&self, issuer: &Issuer<'_, impl SigningKey>) -> Result<(), Error> {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why move this into a separate method?

If you're only going to get your LLM to iterate on this PR we might as well stop reviewing this here, or your employer can pay me for my time.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, that change doesn't really make sense, will revert. I try to review the commits, but clearly some nonsensical changes slipped in. After reviewing the rest of your comments, I'll go through and make sure there aren't any more unrelated additions here, sorry about that. Thanks so much for the reviews.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is really not great that you're getting volunteers to review something that you haven't even reviewed yourself.

@jgreeer

jgreeer commented Sep 9, 2026

Copy link
Copy Markdown
Contributor Author

Reverted many of the unnecessary changes, remaining edits should be only crypto provider related

Changes:

  • Removed the changing of the fips flag logic, it now behaves exactly like it used to. This does mean that the testing functions in certificate.rs require the flag in the any section of the config now.
  • Removed other changes to README, the only README change is the new Cryptography providers section, which simply explains that you need to enable a provider now and pass it through the functions & the provider in the example
  • cargo.toml now has the original feature flags, except for crypto which we agreed to remove
  • Derive the SKI inline instead of before calling write_x509_extension in certificate.rs, same change in serialize_der from crl.rs
  • Switch back to retrieving the private key via private_key.contents() in key_pair.rs, the provider calls clone_key later for ownership.
  • Restore original shape of serialize_pem in key_pair.rs where we assign serialize_der to `contents
  • Returned the SPKI parsing flow in from_der in key_pair.rs to its original form
  • Removed boxed blanket implementations for PublicKeyData and SigningKey in key_pair.rs
  • Added back inline certificate parsing in self_signed in lib.rs
  • Use to_vec() and 0..20 in the derive function in KeyIdMethod
  • Revert changes entirely to string.rs, nothing should be touched there
  • In aws-lc-rs.rs
    • Variables have been renamed to match their original names in key_pair.rs
    • ECSDA keys use from EcdsaKeyPair::from_pkcs8 instead of load_with_algorithm
    • Wrapped match in Ok in the implementation of sign instead of around each signature to match original function
  • In ring.rs
    • Restored original variable names
    • Use ecdsa_from_pkcs8 instead of load_with_algorithim for ECDSA keys
    • Return a Ok(KeyPair) out in each if block like the generate
    • Changed SigningKey implementation to be like original KeyPair.rs
  • Drop unneeded test in custom_provider.rs

Comment thread rcgen/src/key_pair.rs
}

/// Obtains the key pair from a DER formatted key
/// Obtains the key pair from a PEM formatted key

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this change isn't relevant, but i do think this comment is wrong? (copy pasted from from_pkcs8_der_and_sign_algo maybe). but could break this change off into another PR

@jgreeer
jgreeer force-pushed the add-pluggable-crypto-provider branch from ec38547 to c08332c Compare September 9, 2026 15:40
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.

Support CryptoProvider like plugin mechanism

3 participants