-
Notifications
You must be signed in to change notification settings - Fork 734
Draft: Generalize API for extensible non-X.509 support #2560
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
Replace certificate-centric architecture with Identity abstraction to cleanly support X.509 certificates, Raw Public Keys (RPK), and custom authentication types. Changes: - Add Identity concept as unified abstraction above certificate types - Refactor core logic to use Identity instead of direct certificate handling - Implement clean RPK support via new trait interfaces - Add extensibility for custom certificate/auth types - Maintain full backward compatibility for existing X.509 workflows This establishes a proper architectural foundation for diverse authentication mechanisms beyond the previous X.509-focused design.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a quick, partial review. I think there are some reasonable ideas in here, and I'm open to this sort of change, but one big commit makes it hard to evaluate in detail.
} | ||
|
||
#[derive(Clone, Debug)] | ||
pub struct KeyPair { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this naming is much too generic. I am concerned that a typical user of rustls does not have a use for RPK, but this naming makes it sound enticingly useful.
Maybe we could have an rustls::rpk
module (or something) collecting these things, whose doc comment says "if you stumbled upon this this isn't what you want"
rustls/src/identity.rs
Outdated
use core::fmt::Debug; | ||
use pki_types::{CertificateDer, SubjectPublicKeyInfoDer}; | ||
use std::any::Any; | ||
use std::prelude::rust_2015::{Box, Vec}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is odd. Use alloc::vec::Vec
and alloc::boxed::Box
?
macro_rules! trace ( ($($tt:tt)*) => {{}} ); | ||
macro_rules! debug ( ($($tt:tt)*) => {{}} ); | ||
macro_rules! error ( ($($tt:tt)*) => {{}} ); | ||
macro_rules! _warn ( ($($tt:tt)*) => {{}} ); | ||
macro_rules! trace ( ($($tt:tt)*) => {{}} ); | ||
macro_rules! debug ( ($($tt:tt)*) => {{}} ); | ||
macro_rules! error ( ($($tt:tt)*) => {{}} ); | ||
macro_rules! _warn ( ($($tt:tt)*) => {{}} ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
unnecessary whitespace change
rustls/src/client/client_conn.rs
Outdated
use alloc::vec::Vec; | ||
use core::marker::PhantomData; | ||
use core::ops::{Deref, DerefMut}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please run cargo +nightly fmt-unstable
to sort out the import ordering/groupings.
#[derive(Debug)] | ||
pub(crate) struct ResolvesClientCertCompat(Arc<dyn ResolvesClientCert>); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I appreciate the idea of this compatibility layer, but I think ResolvesClientIdentity
can replace ResolvesClientCert
in its entirety. (main is open for breaking changes right now, but I suspect it wasn't when you started these changes.)
let cert_type = cx | ||
.common | ||
.negotiated_client_cert_type() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Generally let's not insert things into cx.common
early in the handshake and then extract them later. Instead, thread these into the handshake states and expose them in the public API at exactly the same point as cx.common.peer_identity
is changed.
impl<'a> From<&'a Vec<u8>> for Payload<'a> { | ||
fn from(value: &'a Vec<u8>) -> Self { | ||
Self::Borrowed(value.as_slice()) | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is weird.
pub(crate) cert: CertificateDer<'a>, | ||
pub(crate) extensions: CertificateExtensions<'a>, | ||
#[derive(Debug, Clone)] | ||
pub struct IdentityEntry<'a> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These types should be named -- as far as possible -- after types in RFC8446. I suggest we keep this as CertificateEntry
but make it a rust enum with RawPublicKey
and X509
variants. ref: https://datatracker.ietf.org/doc/html/rfc8446#section-4.4.2
rustls/src/msgs/handshake.rs
Outdated
Certificate(x) => x.encode(bytes), | ||
Certificate(x) => Codec::encode(x, bytes), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is weird.
!Ranges: | ||
Custom => 224..=255, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This seems to be unused/unmotivated? FWIW this type can already represent the full range of standard values (unassigned ones via the Unknown(_)
variant)
Conceptually this is definitely interesting to me -- decoupling TLS and X.509 seems like a worthwhile way to further modularize rustls and make it usable across a broader range of use cases. Ideally we'd broaden the interface while trying not to meaningfully degrade the ergonomics for the vast majority that wants to just use certificates. On the implementation side, I think this needs a lot of work to be viable. I'd suggest picking much smaller chunks to happen at a time, starting with picking client or server and working out what it needs. Some aspects of your PR seem to be affected by LLM-assisted coding. While I'm a happy Copilot user, I'd strongly suggest writing any descriptive prose yourself and making sure to closely self-review any LLM-generated code before submitting it to us for review. We usually work with small commits exactly because they make review easier. Most of this is explained in our |
What
This draft PR introduces an approach for cleaner, more extensible support of non-X.509 identity schemes, such as Raw Public Keys. The focus is on a long-term design that better represents the realities of modern TLS. It also solves issue #2258.
Why
Historically, TLS and X.509 certificates were tightly coupled, both by convention and implementation. However, the TLS protocol has been evolving towards a more flexible design. RFC 7250 added support for
Raw Public Keys
(RPK) to TLS 1.2, and RFC 8446 (TLS 1.3) went further by makingcertificate_type
negotiation a standard part of the handshake.The specification is extensible, currently allowing:
0
)2
)224-255
)(Note: OpenPGP, which used certificate type id
19
, was specified in RFC 6091, but its support was removed in RFC 8446 due to a lack of adoption.)Rustls' initial RPK support (PR #2062) minimized changes to the public API by reusing existing types (
CertificateDer
to hold RPKs) and traits (CertVerifier
). This required adding specialized methods likerequires_raw_public_keys
and hardcoded logic to the handshake to differentiate between schemes.While this approach reduced the scope of the PR and preserved backward compatibility, it limits how cleanly Rustls can support schemes beyond X.509, as highlighted in discussions #2258 and #2266. The
CertVerifier
traits, designed exclusively for X.509, now have potentially invalid states and are awkward to use for other identity types.Furthermore, the current design does not scale to support future schemes and does not support custom/private types at all. For me, enabling custom schemes is the primary motivation for this PR.
How
Introduced
Identity
Abstraction: This PR introduces a newTlsIdentity
trait to decouple the API from X.509 certificates. Implementations for X.509 and RPK are included. A peer's identity is accessible viapeer_identity()
, whilepeer_certificates()
is kept for backward compatibility. Convenience methods likeIdentity::as_certificates
andIdentity::as_public_key
are provided, withIdentity::as_any
available for custom types.New Verifier/Resolver Traits: New, scheme-neutral traits have been introduced:
ServerIdVerifier
,ClientIdVerifier
,ResolvesServerIdentity
, andResolvesClientIdentity
. Implementations are only required for new or custom schemes. This PR includes implementations for X.509 (wrapping the existingCertVerifier
traits for compatibility) and new, purpose-builtRpkVerifier
/RpkResolver
traits for RPK users.Dynamic Handshake Logic: Hardcoded handshake logic has been replaced with a dynamic approach that delegates negotiation to the new verifier/resolver traits. X.509-specific code has been moved into its respective trait implementation, making the main code paths identity-scheme neutral.
Support for Custom Types: The
CertificateType
enum was extended to support the custom/private use range (224-255). Basic range support was added to theenum_builder
.Terminology Update: Throughout the codebase,
certificate
/cert
has been replaced withidentity
/id
where appropriate to reflect the new abstractions.Impact
The changes are necessarily invasive but establish a foundation for long-term API evolution. The number of changed LOC is substantial, though many are simple renames.
Code Changes
The most meaningful changes are in:
identity.rs
: Contains the mainIdentity
type, theTlsIdentity
&IdentitySigner
traits and their respective implementations.verify.rs
: Adds the newServerIdVerifier
&ClientIdVerifier
traits, compatibility wrappers for X.509, and the newServerRpkVerifier
&ClientRpkVerifier
traits.server_conn.rs
&client_conn.rs
: Adds new identity resolvers and public setters for using non-X.509 schemes.common_state.rs
: Adds thepeer_identity()
method and fields to store the negotiated certificate types.builder.rs
: Addswith_*_rpk_resolver
andwith_*_identity_resolver
methods to supplement existing ones.Performance
Performance should be unaffected. Any significant regression should be considered a bug.
Backward Compatibility
Although the changes are substantial, care has been taken to minimize breakage.
RPK Users: This is a breaking change. Users must migrate to the new, purpose-built RPK traits (e.g.,
ClientRpkVerifier
instead ofClientCertVerifier
) and use the newwith_rpk_*
configuration methods. The peer's key should be accessed viapeer_identity().as_public_key()
. These changes result in a cleaner, more correct API for RPK usage.Direct Config Field Access: For users who directly access the public
ClientConfig::client_auth_cert_resolver
orServerConfig::cert_resolver
fields, 100% backward compatibility cannot be maintained, as these fields have been renamed and re-typed. Affected users must use the similarly named methods instead (e.g. change fromconfig.cert_resolver = X;
toconfig.cert_resolver(X);
.Note: Users who rely on the builder methods are not affected.
Overall, this has been an interesting endeavor. I believe these changes are needed in one form or another. Looking forward to everyone's feedback and opinions.