pub trait ActiveKeyExchange: Send + Sync {
// Required methods
fn complete(
self: Box<Self>,
peer_pub_key: &[u8],
) -> Result<SharedSecret, Error>;
fn pub_key(&self) -> &[u8] ⓘ;
fn group(&self) -> NamedGroup;
// Provided methods
fn complete_for_tls_version(
self: Box<Self>,
peer_pub_key: &[u8],
tls_version: &SupportedProtocolVersion,
) -> Result<SharedSecret, Error> { ... }
fn hybrid_component(&self) -> Option<(NamedGroup, &[u8])> { ... }
fn complete_hybrid_component(
self: Box<Self>,
_peer_pub_key: &[u8],
) -> Result<SharedSecret, Error> { ... }
fn ffdhe_group(&self) -> Option<FfdheGroup<'static>> { ... }
}
Expand description
An in-progress key exchange originating from a SupportedKxGroup
.
Required Methods§
Sourcefn complete(self: Box<Self>, peer_pub_key: &[u8]) -> Result<SharedSecret, Error>
fn complete(self: Box<Self>, peer_pub_key: &[u8]) -> Result<SharedSecret, Error>
Completes the key exchange, given the peer’s public key.
This method must return an error if peer_pub_key
is invalid: either
mis-encoded, or an invalid public key (such as, but not limited to, being
in a small order subgroup).
If the key exchange algorithm is FFDHE, the result must be left-padded with zeros,
as required by RFC 8446
(see complete_for_tls_version()
for more details).
The shared secret is returned as a SharedSecret
which can be constructed
from a &[u8]
.
This consumes and so terminates the ActiveKeyExchange
.
Sourcefn pub_key(&self) -> &[u8] ⓘ
fn pub_key(&self) -> &[u8] ⓘ
Return the public key being used.
For ECDHE, the encoding required is defined in RFC8446 section 4.2.8.2.
For FFDHE, the encoding required is defined in RFC8446 section 4.2.8.1.
Sourcefn group(&self) -> NamedGroup
fn group(&self) -> NamedGroup
Return the group being used.
Provided Methods§
Sourcefn complete_for_tls_version(
self: Box<Self>,
peer_pub_key: &[u8],
tls_version: &SupportedProtocolVersion,
) -> Result<SharedSecret, Error>
fn complete_for_tls_version( self: Box<Self>, peer_pub_key: &[u8], tls_version: &SupportedProtocolVersion, ) -> Result<SharedSecret, Error>
Completes the key exchange for the given TLS version, given the peer’s public key.
Note that finite-field Diffie–Hellman key exchange has different requirements for the derived shared secret in TLS 1.2 and TLS 1.3 (ECDHE key exchange is the same in TLS 1.2 and TLS 1.3):
In TLS 1.2, the calculated secret is required to be stripped of leading zeros (RFC 5246).
In TLS 1.3, the calculated secret is required to be padded with leading zeros to be the same byte-length as the group modulus (RFC 8446).
The default implementation of this method delegates to complete()
assuming it is
implemented for TLS 1.3 (i.e., for FFDHE KX, removes padding as needed). Implementers of this trait
are encouraged to just implement complete()
assuming TLS 1.3, and let the default
implementation of this method handle TLS 1.2-specific requirements.
This method must return an error if peer_pub_key
is invalid: either
mis-encoded, or an invalid public key (such as, but not limited to, being
in a small order subgroup).
The shared secret is returned as a SharedSecret
which can be constructed
from a &[u8]
.
This consumes and so terminates the ActiveKeyExchange
.
Sourcefn hybrid_component(&self) -> Option<(NamedGroup, &[u8])>
fn hybrid_component(&self) -> Option<(NamedGroup, &[u8])>
For hybrid key exchanges, returns the NamedGroup
and key share
for the classical half of this key exchange.
There is no requirement for a hybrid scheme (or any other!) to implement
hybrid_component()
. It only enables an optimization; described below.
“Hybrid” means a key exchange algorithm which is constructed from two (or more) independent component algorithms. Usually one is post-quantum-secure, and the other is “classical”. See https://datatracker.ietf.org/doc/draft-ietf-tls-hybrid-design/11/
§Background
Rustls always sends a presumptive key share in its ClientHello
, using
(absent any other information) the first item in CryptoProvider::kx_groups
.
If the server accepts the client’s selection, it can complete the handshake
using that key share. If not, the server sends a HelloRetryRequest
instructing
the client to send a different key share instead.
This request costs an extra round trip, and wastes the key exchange computation
(in SupportedKxGroup::start()
) the client already did. We would
like to avoid those wastes if possible.
It is early days for post-quantum-secure hybrid key exchange deployment.
This means (commonly) continuing to offer both the hybrid and classical
key exchanges, so the handshake can be completed without a HelloRetryRequest
for servers that support the offered hybrid or classical schemes.
Implementing hybrid_component()
enables two optimizations:
-
Sending both the hybrid and classical key shares in the
ClientHello
. -
Performing the classical key exchange setup only once. This is important because the classical key exchange setup is relatively expensive. This optimization is permitted and described in https://www.ietf.org/archive/id/draft-ietf-tls-hybrid-design-11.html#section-3.2
Both of these only happen if the classical algorithm appears separately in
the client’s CryptoProvider::kx_groups
, and if the hybrid algorithm appears
first in that list.
§How it works
This function is only called by rustls for clients. It is called when
constructing the initial ClientHello
. rustls follows these steps:
- If the return value is
None
, nothing further happens. - If the given
NamedGroup
does not appear inCryptoProvider::kx_groups
, nothing further happens. - The given key share is added to the
ClientHello
, after the hybrid entry.
Then, one of three things may happen when the server replies to the ClientHello
:
- The server sends a
HelloRetryRequest
. Everything is thrown away and we start again. - The server agrees to our hybrid key exchange: rustls calls
ActiveKeyExchange::complete()
consumingself
. - The server agrees to our classical key exchange: rustls calls
ActiveKeyExchange::complete_hybrid_component()
which discards the hybrid key data, and completes just the classical key exchange.
Sourcefn complete_hybrid_component(
self: Box<Self>,
_peer_pub_key: &[u8],
) -> Result<SharedSecret, Error>
fn complete_hybrid_component( self: Box<Self>, _peer_pub_key: &[u8], ) -> Result<SharedSecret, Error>
Completes the classical component of the key exchange, given the peer’s public key.
This is only called if hybrid_component
returns Some(_)
.
This method must return an error if peer_pub_key
is invalid: either
mis-encoded, or an invalid public key (such as, but not limited to, being
in a small order subgroup).
The shared secret is returned as a SharedSecret
which can be constructed
from a &[u8]
.
See the documentation on Self::hybrid_component()
for explanation.
Sourcefn ffdhe_group(&self) -> Option<FfdheGroup<'static>>
fn ffdhe_group(&self) -> Option<FfdheGroup<'static>>
FFDHE group the ActiveKeyExchange
is operating in.
Return None
if this group is not a FFDHE one.
The default implementation calls FfdheGroup::from_named_group
: this function
is extremely linker-unfriendly so it is recommended all key exchange implementers
provide this function.
rustls::ffdhe_groups
contains suitable values to return from this,
for example rustls::ffdhe_groups::FFDHE2048
.