rustls/rustls/src/client/client_conn.rs

700 lines
23 KiB
Rust

use crate::builder::{ConfigBuilder, WantsCipherSuites};
use crate::conn::{CommonState, ConnectionCommon, Protocol, Side};
use crate::enums::{CipherSuite, ProtocolVersion, SignatureScheme};
use crate::error::{Error, PeerMisbehaved};
use crate::kx::SupportedKxGroup;
#[cfg(feature = "logging")]
use crate::log::trace;
use crate::msgs::enums::AlertDescription;
use crate::msgs::enums::NamedGroup;
use crate::msgs::handshake::ClientExtension;
use crate::msgs::persist;
use crate::sign;
use crate::suites::SupportedCipherSuite;
use crate::verify;
use crate::versions;
#[cfg(feature = "secret_extraction")]
use crate::ExtractedSecrets;
use crate::KeyLog;
use super::hs;
#[cfg(feature = "quic")]
use crate::quic;
use std::error::Error as StdError;
use std::marker::PhantomData;
use std::net::IpAddr;
use std::ops::{Deref, DerefMut};
use std::sync::Arc;
use std::{fmt, io, mem};
/// A trait for the ability to store client session data, so that sessions
/// can be resumed in future connections.
///
/// Generally all data in this interface should be treated as
/// **highly sensitive**, containing enough key material to break all security
/// of the corresponding session.
///
/// `set_`, `insert_`, `remove_` and `take_` operations are mutating; this isn't
/// expressed in the type system to allow implementations freedom in
/// how to achieve interior mutability. `Mutex` is a common choice.
pub trait ClientSessionStore: Send + Sync {
/// Remember what `NamedGroup` the given server chose.
fn set_kx_hint(&self, server_name: &ServerName, group: NamedGroup);
/// This should return the value most recently passed to `set_kx_hint`
/// for the given `server_name`.
///
/// If `None` is returned, the caller chooses the first configured group,
/// and an extra round trip might happen if that choice is unsatisfactory
/// to the server.
fn kx_hint(&self, server_name: &ServerName) -> Option<NamedGroup>;
/// Remember a TLS1.2 session.
///
/// At most one of these can be remembered at a time, per `server_name`.
#[cfg(feature = "tls12")]
fn set_tls12_session(&self, server_name: &ServerName, value: persist::Tls12ClientSessionValue);
/// Get the most recently saved TLS1.2 session for `server_name` provided to `set_tls12_session`.
#[cfg(feature = "tls12")]
fn tls12_session(&self, server_name: &ServerName) -> Option<persist::Tls12ClientSessionValue>;
/// Remove and forget any saved TLS1.2 session for `server_name`.
#[cfg(feature = "tls12")]
fn remove_tls12_session(&self, server_name: &ServerName);
/// Remove and forget any saved TLS1.2 session for `server_name`.
#[cfg(not(feature = "tls12"))]
fn remove_tls12_session(&self, _: &ServerName) {}
/// Remember a TLS1.3 ticket that might be retrieved later from `take_tls13_ticket`, allowing
/// resumption of this session.
///
/// This can be called multiple times for a given session, allowing multiple independent tickets
/// to be valid at once. The number of times this is called is controlled by the server, so
/// implementations of this trait should apply a reasonable bound of how many items are stored
/// simultaneously.
fn insert_tls13_ticket(
&self,
server_name: &ServerName,
value: persist::Tls13ClientSessionValue,
);
/// Return a TLS1.3 ticket previously provided to `add_tls13_ticket`.
///
/// Implementations of this trait must return each value provided to `add_tls13_ticket` _at most once_.
fn take_tls13_ticket(
&self,
server_name: &ServerName,
) -> Option<persist::Tls13ClientSessionValue>;
}
/// A trait for the ability to choose a certificate chain and
/// private key for the purposes of client authentication.
pub trait ResolvesClientCert: Send + Sync {
/// With the server-supplied acceptable issuers in `acceptable_issuers`,
/// the server's supported signature schemes in `sigschemes`,
/// return a certificate chain and signing key to authenticate.
///
/// `acceptable_issuers` is undecoded and unverified by the rustls
/// library, but it should be expected to contain a DER encodings
/// of X501 NAMEs.
///
/// Return None to continue the handshake without any client
/// authentication. The server may reject the handshake later
/// if it requires authentication.
fn resolve(
&self,
acceptable_issuers: &[&[u8]],
sigschemes: &[SignatureScheme],
) -> Option<Arc<sign::CertifiedKey>>;
/// Return true if any certificates at all are available.
fn has_certs(&self) -> bool;
}
/// Common configuration for (typically) all connections made by
/// a program.
///
/// Making one of these can be expensive, and should be
/// once per process rather than once per connection.
///
/// These must be created via the [`ClientConfig::builder()`] function.
///
/// # Defaults
///
/// * [`ClientConfig::max_fragment_size`]: the default is `None`: TLS packets are not fragmented to a specific size.
/// * [`ClientConfig::session_storage`]: the default stores 256 sessions in memory.
/// * [`ClientConfig::alpn_protocols`]: the default is empty -- no ALPN protocol is negotiated.
/// * [`ClientConfig::key_log`]: key material is not logged.
#[derive(Clone)]
pub struct ClientConfig {
/// List of ciphersuites, in preference order.
pub(super) cipher_suites: Vec<SupportedCipherSuite>,
/// List of supported key exchange algorithms, in preference order -- the
/// first element is the highest priority.
///
/// The first element in this list is the _default key share algorithm_,
/// and in TLS1.3 a key share for it is sent in the client hello.
pub(super) kx_groups: Vec<&'static SupportedKxGroup>,
/// Which ALPN protocols we include in our client hello.
/// If empty, no ALPN extension is sent.
pub alpn_protocols: Vec<Vec<u8>>,
/// How we store session data or tickets.
pub session_storage: Arc<dyn ClientSessionStore>,
/// The maximum size of TLS message we'll emit. If None, we don't limit TLS
/// message lengths except to the 2**16 limit specified in the standard.
///
/// rustls enforces an arbitrary minimum of 32 bytes for this field.
/// Out of range values are reported as errors from ClientConnection::new.
///
/// Setting this value to the TCP MSS may improve latency for stream-y workloads.
pub max_fragment_size: Option<usize>,
/// How to decide what client auth certificate/keys to use.
pub client_auth_cert_resolver: Arc<dyn ResolvesClientCert>,
/// Whether to support RFC5077 tickets. You must provide a working
/// `session_storage` member for this to have any meaningful
/// effect.
///
/// The default is true.
pub enable_tickets: bool,
/// Supported versions, in no particular order. The default
/// is all supported versions.
pub(super) versions: versions::EnabledVersions,
/// Whether to send the Server Name Indication (SNI) extension
/// during the client handshake.
///
/// The default is true.
pub enable_sni: bool,
/// How to verify the server certificate chain.
pub(super) verifier: Arc<dyn verify::ServerCertVerifier>,
/// How to output key material for debugging. The default
/// does nothing.
pub key_log: Arc<dyn KeyLog>,
/// Allows traffic secrets to be extracted after the handshake,
/// e.g. for kTLS setup.
#[cfg(feature = "secret_extraction")]
pub enable_secret_extraction: bool,
/// Whether to send data on the first flight ("early data") in
/// TLS 1.3 handshakes.
///
/// The default is false.
pub enable_early_data: bool,
/// Send unsupported cipher suites to appease fingerprinting servers.
///
/// Note: if the server selects one of these suites, the connection will fail.
pub unsupported_suites: Vec<CipherSuite>,
}
impl fmt::Debug for ClientConfig {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("ClientConfig")
.field("alpn_protocols", &self.alpn_protocols)
.field("max_fragment_size", &self.max_fragment_size)
.field("enable_tickets", &self.enable_tickets)
.field("enable_sni", &self.enable_sni)
.field("enable_early_data", &self.enable_early_data)
.finish_non_exhaustive()
}
}
impl ClientConfig {
/// Create a builder to build up the client configuration.
///
/// For more information, see the [`ConfigBuilder`] documentation.
pub fn builder() -> ConfigBuilder<Self, WantsCipherSuites> {
ConfigBuilder {
state: WantsCipherSuites(()),
side: PhantomData::default(),
}
}
/// We support a given TLS version if it's quoted in the configured
/// versions *and* at least one ciphersuite for this version is
/// also configured.
pub(crate) fn supports_version(&self, v: ProtocolVersion) -> bool {
self.versions.contains(v)
&& self
.cipher_suites
.iter()
.any(|cs| cs.version().version == v)
}
/// Access configuration options whose use is dangerous and requires
/// extra care.
#[cfg(feature = "dangerous_configuration")]
pub fn dangerous(&mut self) -> danger::DangerousClientConfig {
danger::DangerousClientConfig { cfg: self }
}
pub(super) fn find_cipher_suite(
&self,
suite: CipherSuite,
retried: bool,
common: &mut CommonState,
) -> Result<SupportedCipherSuite, Error> {
let supported = self
.cipher_suites
.iter()
.copied()
.find(|&scs| scs.suite() == suite);
if let Some(supported) = supported {
Ok(supported)
} else if self.unsupported_suites.contains(&suite) {
Err(Error::UnsupportedSuiteSelected)
} else if retried {
Err(common
.illegal_param(PeerMisbehaved::IllegalHelloRetryRequestWithUnofferedCipherSuite))
} else {
common.send_fatal_alert(AlertDescription::HandshakeFailure);
Err(Error::PeerMisbehaved(
PeerMisbehaved::SelectedUnofferedCipherSuite,
))
}
}
}
/// Encodes ways a client can know the expected name of the server.
///
/// This currently covers knowing the DNS name of the server, but
/// will be extended in the future to supporting privacy-preserving names
/// for the server ("ECH"). For this reason this enum is `non_exhaustive`.
///
/// # Making one
///
/// If you have a DNS name as a `&str`, this type implements `TryFrom<&str>`,
/// so you can do:
///
/// ```
/// # use rustls::ServerName;
/// ServerName::try_from("example.com").expect("invalid DNS name");
///
/// // or, alternatively...
///
/// let x = "example.com".try_into().expect("invalid DNS name");
/// # let _: ServerName = x;
/// ```
#[non_exhaustive]
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
pub enum ServerName {
/// The server is identified by a DNS name. The name
/// is sent in the TLS Server Name Indication (SNI)
/// extension.
DnsName(verify::DnsName),
/// The server is identified by an IP address. SNI is not
/// done.
IpAddress(IpAddr),
}
impl ServerName {
/// Return the name that should go in the SNI extension.
/// If [`None`] is returned, the SNI extension is not included
/// in the handshake.
pub(crate) fn for_sni(&self) -> Option<webpki::DnsNameRef> {
match self {
Self::DnsName(dns_name) => Some(dns_name.0.as_ref()),
Self::IpAddress(_) => None,
}
}
}
/// Attempt to make a ServerName from a string by parsing
/// it as a DNS name.
impl TryFrom<&str> for ServerName {
type Error = InvalidDnsNameError;
fn try_from(s: &str) -> Result<Self, Self::Error> {
match webpki::DnsNameRef::try_from_ascii_str(s) {
Ok(dns) => Ok(Self::DnsName(verify::DnsName(dns.into()))),
Err(webpki::InvalidDnsNameError) => match s.parse() {
Ok(ip) => Ok(Self::IpAddress(ip)),
Err(_) => Err(InvalidDnsNameError),
},
}
}
}
/// The provided input could not be parsed because
/// it is not a syntactically-valid DNS Name.
#[derive(Debug)]
pub struct InvalidDnsNameError;
impl fmt::Display for InvalidDnsNameError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.write_str("invalid dns name")
}
}
impl StdError for InvalidDnsNameError {}
/// Container for unsafe APIs
#[cfg(feature = "dangerous_configuration")]
pub(super) mod danger {
use std::sync::Arc;
use super::verify::ServerCertVerifier;
use super::ClientConfig;
/// Accessor for dangerous configuration options.
#[derive(Debug)]
#[cfg_attr(docsrs, doc(cfg(feature = "dangerous_configuration")))]
pub struct DangerousClientConfig<'a> {
/// The underlying ClientConfig
pub cfg: &'a mut ClientConfig,
}
impl<'a> DangerousClientConfig<'a> {
/// Overrides the default `ServerCertVerifier` with something else.
pub fn set_certificate_verifier(&mut self, verifier: Arc<dyn ServerCertVerifier>) {
self.cfg.verifier = verifier;
}
}
}
#[derive(Debug, PartialEq)]
enum EarlyDataState {
Disabled,
Ready,
Accepted,
AcceptedFinished,
Rejected,
}
pub(super) struct EarlyData {
state: EarlyDataState,
left: usize,
}
impl EarlyData {
fn new() -> Self {
Self {
left: 0,
state: EarlyDataState::Disabled,
}
}
pub(super) fn is_enabled(&self) -> bool {
matches!(self.state, EarlyDataState::Ready | EarlyDataState::Accepted)
}
fn is_accepted(&self) -> bool {
matches!(
self.state,
EarlyDataState::Accepted | EarlyDataState::AcceptedFinished
)
}
pub(super) fn enable(&mut self, max_data: usize) {
assert_eq!(self.state, EarlyDataState::Disabled);
self.state = EarlyDataState::Ready;
self.left = max_data;
}
pub(super) fn rejected(&mut self) {
trace!("EarlyData rejected");
self.state = EarlyDataState::Rejected;
}
pub(super) fn accepted(&mut self) {
trace!("EarlyData accepted");
assert_eq!(self.state, EarlyDataState::Ready);
self.state = EarlyDataState::Accepted;
}
pub(super) fn finished(&mut self) {
trace!("EarlyData finished");
self.state = match self.state {
EarlyDataState::Accepted => EarlyDataState::AcceptedFinished,
_ => panic!("bad EarlyData state"),
}
}
fn check_write(&mut self, sz: usize) -> io::Result<usize> {
match self.state {
EarlyDataState::Disabled => unreachable!(),
EarlyDataState::Ready | EarlyDataState::Accepted => {
let take = if self.left < sz {
mem::replace(&mut self.left, 0)
} else {
self.left -= sz;
sz
};
Ok(take)
}
EarlyDataState::Rejected | EarlyDataState::AcceptedFinished => {
Err(io::Error::from(io::ErrorKind::InvalidInput))
}
}
}
fn bytes_left(&self) -> usize {
self.left
}
}
/// Stub that implements io::Write and dispatches to `write_early_data`.
pub struct WriteEarlyData<'a> {
sess: &'a mut ClientConnection,
}
impl<'a> WriteEarlyData<'a> {
fn new(sess: &'a mut ClientConnection) -> WriteEarlyData<'a> {
WriteEarlyData { sess }
}
/// How many bytes you may send. Writes will become short
/// once this reaches zero.
pub fn bytes_left(&self) -> usize {
self.sess
.inner
.data
.early_data
.bytes_left()
}
}
impl<'a> io::Write for WriteEarlyData<'a> {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
self.sess.write_early_data(buf)
}
fn flush(&mut self) -> io::Result<()> {
Ok(())
}
}
/// This represents a single TLS client connection.
pub struct ClientConnection {
inner: ConnectionCommon<ClientConnectionData>,
}
impl fmt::Debug for ClientConnection {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_struct("ClientConnection")
.finish()
}
}
impl ClientConnection {
/// Make a new ClientConnection. `config` controls how
/// we behave in the TLS protocol, `name` is the
/// name of the server we want to talk to.
pub fn new(config: Arc<ClientConfig>, name: ServerName) -> Result<Self, Error> {
Self::new_inner(config, name, Vec::new(), Protocol::Tcp)
}
fn new_inner(
config: Arc<ClientConfig>,
name: ServerName,
extra_exts: Vec<ClientExtension>,
proto: Protocol,
) -> Result<Self, Error> {
let mut common_state = CommonState::new(Side::Client);
common_state.set_max_fragment_size(config.max_fragment_size)?;
common_state.protocol = proto;
#[cfg(feature = "secret_extraction")]
{
common_state.enable_secret_extraction = config.enable_secret_extraction;
}
let mut data = ClientConnectionData::new();
let mut cx = hs::ClientContext {
common: &mut common_state,
data: &mut data,
};
let state = hs::start_handshake(name, extra_exts, config, &mut cx)?;
let inner = ConnectionCommon::new(state, data, common_state);
Ok(Self { inner })
}
/// Returns an `io::Write` implementer you can write bytes to
/// to send TLS1.3 early data (a.k.a. "0-RTT data") to the server.
///
/// This returns None in many circumstances when the capability to
/// send early data is not available, including but not limited to:
///
/// - The server hasn't been talked to previously.
/// - The server does not support resumption.
/// - The server does not support early data.
/// - The resumption data for the server has expired.
///
/// The server specifies a maximum amount of early data. You can
/// learn this limit through the returned object, and writes through
/// it will process only this many bytes.
///
/// The server can choose not to accept any sent early data --
/// in this case the data is lost but the connection continues. You
/// can tell this happened using `is_early_data_accepted`.
pub fn early_data(&mut self) -> Option<WriteEarlyData> {
if self.inner.data.early_data.is_enabled() {
Some(WriteEarlyData::new(self))
} else {
None
}
}
/// Returns True if the server signalled it will process early data.
///
/// If you sent early data and this returns false at the end of the
/// handshake then the server will not process the data. This
/// is not an error, but you may wish to resend the data.
pub fn is_early_data_accepted(&self) -> bool {
self.inner.data.early_data.is_accepted()
}
fn write_early_data(&mut self, data: &[u8]) -> io::Result<usize> {
self.inner
.data
.early_data
.check_write(data.len())
.map(|sz| {
self.inner
.common_state
.send_early_plaintext(&data[..sz])
})
}
/// Extract secrets, so they can be used when configuring kTLS, for example.
#[cfg(feature = "secret_extraction")]
pub fn extract_secrets(self) -> Result<ExtractedSecrets, Error> {
self.inner.extract_secrets()
}
}
impl Deref for ClientConnection {
type Target = ConnectionCommon<ClientConnectionData>;
fn deref(&self) -> &Self::Target {
&self.inner
}
}
impl DerefMut for ClientConnection {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.inner
}
}
#[doc(hidden)]
impl<'a> TryFrom<&'a mut crate::Connection> for &'a mut ClientConnection {
type Error = ();
fn try_from(value: &'a mut crate::Connection) -> Result<Self, Self::Error> {
use crate::Connection::*;
match value {
Client(conn) => Ok(conn),
Server(_) => Err(()),
}
}
}
impl From<ClientConnection> for crate::Connection {
fn from(conn: ClientConnection) -> Self {
Self::Client(conn)
}
}
/// State associated with a client connection.
pub struct ClientConnectionData {
pub(super) early_data: EarlyData,
pub(super) resumption_ciphersuite: Option<SupportedCipherSuite>,
}
impl ClientConnectionData {
fn new() -> Self {
Self {
early_data: EarlyData::new(),
resumption_ciphersuite: None,
}
}
}
impl crate::conn::SideData for ClientConnectionData {}
#[cfg(feature = "quic")]
impl quic::QuicExt for ClientConnection {
fn quic_transport_parameters(&self) -> Option<&[u8]> {
self.inner
.common_state
.quic
.params
.as_ref()
.map(|v| v.as_ref())
}
fn zero_rtt_keys(&self) -> Option<quic::DirectionalKeys> {
Some(quic::DirectionalKeys::new(
self.inner
.data
.resumption_ciphersuite
.and_then(|suite| suite.tls13())?,
self.inner
.common_state
.quic
.early_secret
.as_ref()?,
))
}
fn read_hs(&mut self, plaintext: &[u8]) -> Result<(), Error> {
self.inner.read_quic_hs(plaintext)
}
fn write_hs(&mut self, buf: &mut Vec<u8>) -> Option<quic::KeyChange> {
quic::write_hs(&mut self.inner.common_state, buf)
}
fn alert(&self) -> Option<AlertDescription> {
self.inner.common_state.quic.alert
}
}
/// Methods specific to QUIC client sessions
#[cfg(feature = "quic")]
#[cfg_attr(docsrs, doc(cfg(feature = "quic")))]
pub trait ClientQuicExt {
/// Make a new QUIC ClientConnection. This differs from `ClientConnection::new()`
/// in that it takes an extra argument, `params`, which contains the
/// TLS-encoded transport parameters to send.
fn new_quic(
config: Arc<ClientConfig>,
quic_version: quic::Version,
name: ServerName,
params: Vec<u8>,
) -> Result<ClientConnection, Error> {
if !config.supports_version(ProtocolVersion::TLSv1_3) {
return Err(Error::General(
"TLS 1.3 support is required for QUIC".into(),
));
}
let ext = match quic_version {
quic::Version::V1Draft => ClientExtension::TransportParametersDraft(params),
quic::Version::V1 => ClientExtension::TransportParameters(params),
};
ClientConnection::new_inner(config, name, vec![ext], Protocol::Quic)
}
}
#[cfg(feature = "quic")]
impl ClientQuicExt for ClientConnection {}