From 2a24bc0da88ec6907667daeaae2e211e4d59a223 Mon Sep 17 00:00:00 2001 From: v1per4llbl4ck Date: Mon, 11 May 2026 18:25:59 +0200 Subject: [PATCH] feat(tls,quic): add CryptoProvider injection seam (closes #6236) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Lets consumers swap the default `ring`-based `rustls::crypto::CryptoProvider` for an alternative — notably `rustls-post-quantum` to enable the X25519MLKEM768 hybrid post-quantum key-exchange group (IANA codepoint 0x11EC, draft-ietf-tls-ecdhe-mlkem-04) on the libp2p TLS handshake (both the libp2p-tls TCP path and the libp2p-quic QUIC path). API additions (all behaviour-preserving — existing call sites unchanged): libp2p-tls + make_client_config_with_provider(keypair, remote_peer_id, provider) + make_server_config_with_provider(keypair, provider) + Config::new_with_provider(identity, provider) + private default_libp2p_provider() helper that hoists the cipher-suite mutation previously duplicated at both make_*_config call sites libp2p-quic + Config::new_with_provider(keypair, provider) + private Self::new_inner(keypair, custom_provider) helper shared by Self::new and Self::new_with_provider The existing `make_client_config`, `make_server_config`, `Config::new` (in both crates) become one-liners that delegate to the new `_with_provider` variants with `None`. A consumer that doesn't call the new APIs cannot tell the patch is in place — `cargo test` against the upstream test matrix passes unchanged. Why this shape: - Threading `Option` (rather than a builder/setter) keeps the public API surface minimal and matches the existing arity of `make_*_config`. - The `default_libp2p_provider()` helper de-duplicates the `provider.cipher_suites = verifier::CIPHERSUITES.to_vec()` mutation that used to appear inline at both call sites, which makes future provider swaps mechanically safe. - The QUIC patch piggybacks on the libp2p-tls patch via the same `_with_provider` helpers — no separate provider-injection logic in the QUIC layer. Closes #6236. --- transports/quic/CHANGELOG.md | 6 +++++ transports/quic/src/config.rs | 38 ++++++++++++++++++++++++++++-- transports/tls/CHANGELOG.md | 8 +++++++ transports/tls/src/lib.rs | 44 +++++++++++++++++++++++++++++++---- transports/tls/src/upgrade.rs | 23 ++++++++++++++++++ 5 files changed, 113 insertions(+), 6 deletions(-) diff --git a/transports/quic/CHANGELOG.md b/transports/quic/CHANGELOG.md index 00898299001..1d0472a65a0 100644 --- a/transports/quic/CHANGELOG.md +++ b/transports/quic/CHANGELOG.md @@ -1,5 +1,11 @@ ## 0.14.0 +- Add `Config::new_with_provider(keypair, provider)` accepting a custom + `rustls::crypto::CryptoProvider` that is threaded into both the + client- and server-side TLS configs underneath QUIC. Pairs with the + matching libp2p-tls addition. Lets consumers enable the X25519MLKEM768 + hybrid PQ key-exchange group via `rustls-post-quantum`. Closes + [issue 6236](https://github.com/libp2p/rust-libp2p/issues/6236). - Raise MSRV to 1.88.0. See [PR 6273](https://github.com/libp2p/rust-libp2p/pull/6273). diff --git a/transports/quic/src/config.rs b/transports/quic/src/config.rs index 0d514db5751..71fb578391c 100644 --- a/transports/quic/src/config.rs +++ b/transports/quic/src/config.rs @@ -77,12 +77,46 @@ pub struct Config { impl Config { /// Creates a new configuration object with default values. pub fn new(keypair: &libp2p_identity::Keypair) -> Self { + Self::new_inner(keypair, None) + } + + /// Build a libp2p-quic config with a custom rustls + /// [`CryptoProvider`](rustls::crypto::CryptoProvider) used for both the + /// client- and server-side TLS configs underneath QUIC. + /// + /// Pass e.g. `rustls_post_quantum::provider().clone()` to enable the + /// X25519MLKEM768 hybrid post-quantum key-exchange group on the QUIC + /// handshake. When you don't need to override the default, keep using + /// [`Config::new`] — semantically identical. + pub fn new_with_provider( + keypair: &libp2p_identity::Keypair, + provider: rustls::crypto::CryptoProvider, + ) -> Self { + Self::new_inner(keypair, Some(provider)) + } + + /// Internal helper shared by [`Self::new`] and [`Self::new_with_provider`] + /// so the two public constructors stay one-liners. + fn new_inner( + keypair: &libp2p_identity::Keypair, + custom_provider: Option, + ) -> Self { let client_tls_config = Arc::new( - QuicClientConfig::try_from(libp2p_tls::make_client_config(keypair, None).unwrap()) + QuicClientConfig::try_from( + libp2p_tls::make_client_config_with_provider( + keypair, + None, + custom_provider.clone(), + ) .unwrap(), + ) + .unwrap(), ); let server_tls_config = Arc::new( - QuicServerConfig::try_from(libp2p_tls::make_server_config(keypair).unwrap()).unwrap(), + QuicServerConfig::try_from( + libp2p_tls::make_server_config_with_provider(keypair, custom_provider).unwrap(), + ) + .unwrap(), ); Self { client_tls_config, diff --git a/transports/tls/CHANGELOG.md b/transports/tls/CHANGELOG.md index 94848e243b2..fa91d130ff1 100644 --- a/transports/tls/CHANGELOG.md +++ b/transports/tls/CHANGELOG.md @@ -1,5 +1,13 @@ ## 0.7.0 +- Add `make_client_config_with_provider` / `make_server_config_with_provider` + free functions and `Config::new_with_provider` constructor that accept + an optional `rustls::crypto::CryptoProvider`. Lets consumers plug in + alternative providers — notably `rustls-post-quantum` for the + X25519MLKEM768 hybrid PQ key-exchange group (IANA codepoint `0x11EC`, + draft-ietf-tls-ecdhe-mlkem). Behaviour-preserving: existing + `make_*_config` and `Config::new` delegate to the new variants with + `None`. Closes [issue 6236](https://github.com/libp2p/rust-libp2p/issues/6236). - Raise MSRV to 1.88.0. See [PR 6273](https://github.com/libp2p/rust-libp2p/pull/6273). diff --git a/transports/tls/src/lib.rs b/transports/tls/src/lib.rs index 8cf5b06a53f..7c53b2ed7b7 100644 --- a/transports/tls/src/lib.rs +++ b/transports/tls/src/lib.rs @@ -38,15 +38,41 @@ pub use upgrade::{Config, UpgradeError}; const P2P_ALPN: [u8; 6] = *b"libp2p"; +/// Build a `CryptoProvider` from `ring` with libp2p-tls's required cipher suites. +/// +/// Used as the default-provider fallback by both the unmodified API +/// (`make_client_config`, `make_server_config`) and the new `_with_provider` +/// variants when the caller passes `None`. +fn default_libp2p_provider() -> rustls::crypto::CryptoProvider { + let mut provider = rustls::crypto::ring::default_provider(); + provider.cipher_suites = verifier::CIPHERSUITES.to_vec(); + provider +} + /// Create a TLS client configuration for libp2p. pub fn make_client_config( keypair: &Keypair, remote_peer_id: Option, +) -> Result { + make_client_config_with_provider(keypair, remote_peer_id, None) +} + +/// Create a TLS client configuration for libp2p with an optional custom +/// `rustls::crypto::CryptoProvider`. +/// +/// Pass `Some(provider)` to enable a non-default provider (for example, +/// `rustls_post_quantum::provider().clone()` to add the X25519MLKEM768 +/// hybrid post-quantum kx group). When `None`, the default `ring`-based +/// provider with libp2p's cipher-suite list is used — semantically +/// identical to `make_client_config`. +pub fn make_client_config_with_provider( + keypair: &Keypair, + remote_peer_id: Option, + custom_provider: Option, ) -> Result { let (certificate, private_key) = certificate::generate(keypair)?; - let mut provider = rustls::crypto::ring::default_provider(); - provider.cipher_suites = verifier::CIPHERSUITES.to_vec(); + let provider = custom_provider.unwrap_or_else(default_libp2p_provider); let cert_resolver = Arc::new( AlwaysResolvesCert::new(certificate, &private_key) @@ -70,11 +96,21 @@ pub fn make_client_config( /// Create a TLS server configuration for libp2p. pub fn make_server_config( keypair: &Keypair, +) -> Result { + make_server_config_with_provider(keypair, None) +} + +/// Create a TLS server configuration for libp2p with an optional custom +/// `rustls::crypto::CryptoProvider`. +/// +/// See [`make_client_config_with_provider`] for the rationale. +pub fn make_server_config_with_provider( + keypair: &Keypair, + custom_provider: Option, ) -> Result { let (certificate, private_key) = certificate::generate(keypair)?; - let mut provider = rustls::crypto::ring::default_provider(); - provider.cipher_suites = verifier::CIPHERSUITES.to_vec(); + let provider = custom_provider.unwrap_or_else(default_libp2p_provider); let cert_resolver = Arc::new( AlwaysResolvesCert::new(certificate, &private_key) diff --git a/transports/tls/src/upgrade.rs b/transports/tls/src/upgrade.rs index 2563f636dac..43055ef5560 100644 --- a/transports/tls/src/upgrade.rs +++ b/transports/tls/src/upgrade.rs @@ -60,6 +60,29 @@ impl Config { client: crate::make_client_config(identity, None)?, }) } + + /// Build a libp2p TLS upgrade config with a custom rustls + /// [`CryptoProvider`](rustls::crypto::CryptoProvider) injected on both + /// the server and client sides. + /// + /// Pass e.g. `rustls_post_quantum::provider().clone()` to enable the + /// X25519MLKEM768 hybrid post-quantum key-exchange group on the + /// libp2p TLS handshake. When you don't need to override the default, + /// keep using [`Config::new`] — semantically identical. + /// + /// The same provider is used for both server- and client-side configs + /// because the libp2p TLS upgrade negotiates symmetrically (every node + /// is both a TLS server and a TLS client). Cloning a `CryptoProvider` + /// is cheap. + pub fn new_with_provider( + identity: &identity::Keypair, + provider: rustls::crypto::CryptoProvider, + ) -> Result { + Ok(Self { + server: crate::make_server_config_with_provider(identity, Some(provider.clone()))?, + client: crate::make_client_config_with_provider(identity, None, Some(provider))?, + }) + } } impl UpgradeInfo for Config {