Skip to content

Commit

Permalink
Add API exposing shape of the performed handshake
Browse files Browse the repository at this point in the history
This allows callers to see if their handshake was Resumed,
Full, or Full-with-HelloRetryRequest (which, broadly, are the
three "cost" levels for handshakes).

This is exposed as soon as it is known for sure.
  • Loading branch information
ctz committed Apr 16, 2024
1 parent 740ca41 commit d8a2ae0
Show file tree
Hide file tree
Showing 8 changed files with 126 additions and 8 deletions.
3 changes: 2 additions & 1 deletion rustls/src/client/hs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ use crate::check::inappropriate_handshake_message;
use crate::client::client_conn::ClientConnectionData;
use crate::client::common::ClientHelloDetails;
use crate::client::{tls13, ClientConfig};
use crate::common_state::{CommonState, State};
use crate::common_state::{CommonState, HandshakeKind, State};
use crate::conn::ConnectionRandoms;
use crate::crypto::{ActiveKeyExchange, KeyExchangeAlgorithm};
use crate::enums::{AlertDescription, CipherSuite, ContentType, HandshakeType, ProtocolVersion};
Expand Down Expand Up @@ -843,6 +843,7 @@ impl ExpectServerHelloOrHelloRetryRequest {

// HRR selects the ciphersuite.
cx.common.suite = Some(cs);
cx.common.handshake_kind = Some(HandshakeKind::FullWithHelloRetryRequest);

// This is the draft19 change where the transcript became a tree
let transcript = self
Expand Down
4 changes: 3 additions & 1 deletion rustls/src/client/tls12.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ use super::hs::ClientContext;
use crate::check::{inappropriate_handshake_message, inappropriate_message};
use crate::client::common::{ClientAuthDetails, ServerCertDetails};
use crate::client::{hs, ClientConfig};
use crate::common_state::{CommonState, Side, State};
use crate::common_state::{CommonState, HandshakeKind, Side, State};
use crate::conn::ConnectionRandoms;
use crate::crypto::KeyExchangeAlgorithm;
use crate::enums::{AlertDescription, ContentType, HandshakeType, ProtocolVersion};
Expand Down Expand Up @@ -138,6 +138,7 @@ mod server_hello {
.clone()
.into_owned(),
);
cx.common.handshake_kind = Some(HandshakeKind::Resumed);
let cert_verified = verify::ServerCertVerified::assertion();
let sig_verified = verify::HandshakeSignatureValid::assertion();

Expand Down Expand Up @@ -172,6 +173,7 @@ mod server_hello {
}
}

cx.common.handshake_kind = Some(HandshakeKind::Full);
Ok(Box::new(ExpectCertificate {
config: self.config,
resuming_session: None,
Expand Down
6 changes: 5 additions & 1 deletion rustls/src/client/tls13.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ use super::hs::ClientContext;
use crate::check::inappropriate_handshake_message;
use crate::client::common::{ClientAuthDetails, ClientHelloDetails, ServerCertDetails};
use crate::client::{hs, ClientConfig, ClientSessionStore};
use crate::common_state::{CommonState, Protocol, Side, State};
use crate::common_state::{CommonState, HandshakeKind, Protocol, Side, State};
use crate::conn::ConnectionRandoms;
use crate::crypto::ActiveKeyExchange;
use crate::enums::{
Expand Down Expand Up @@ -425,6 +425,7 @@ impl State<ClientConnectionData> for ExpectEncryptedExtensions {
.server_cert_chain()
.clone(),
);
cx.common.handshake_kind = Some(HandshakeKind::Resumed);

// We *don't* reverify the certificate chain here: resumption is a
// continuation of the previous session in terms of security policy.
Expand All @@ -445,6 +446,9 @@ impl State<ClientConnectionData> for ExpectEncryptedExtensions {
if exts.early_data_extension_offered() {
return Err(PeerMisbehaved::EarlyDataExtensionWithoutResumption.into());
}
cx.common
.handshake_kind
.get_or_insert(HandshakeKind::Full);
Ok(Box::new(ExpectCertificateOrCertReq {
config: self.config,
server_name: self.server_name,
Expand Down
37 changes: 37 additions & 0 deletions rustls/src/common_state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ use crate::{quic, record_layer};
/// Connection state common to both client and server connections.
pub struct CommonState {
pub(crate) negotiated_version: Option<ProtocolVersion>,
pub(crate) handshake_kind: Option<HandshakeKind>,
pub(crate) side: Side,
pub(crate) record_layer: record_layer::RecordLayer,
pub(crate) suite: Option<SupportedCipherSuite>,
Expand Down Expand Up @@ -56,6 +57,7 @@ impl CommonState {
pub(crate) fn new(side: Side) -> Self {
Self {
negotiated_version: None,
handshake_kind: None,
side,
record_layer: record_layer::RecordLayer::new(),
suite: None,
Expand Down Expand Up @@ -140,6 +142,17 @@ impl CommonState {
self.negotiated_version
}

/// Which kind of handshake was performed.
///
/// This tells you whether the handshake was a resumption or not.
///
/// This will return `Err(Error::HandshakeNotComplete)` before it is
/// known which sort of handshake occurred.
pub fn handshake_kind(&self) -> Result<HandshakeKind, Error> {
self.handshake_kind
.ok_or(Error::HandshakeNotComplete)
}

pub(crate) fn is_tls13(&self) -> bool {
matches!(self.negotiated_version, Some(ProtocolVersion::TLSv1_3))
}
Expand Down Expand Up @@ -682,6 +695,30 @@ impl CommonState {
}
}

/// Describes which sort of handshake happened.
#[derive(Debug, PartialEq, Clone, Copy)]
pub enum HandshakeKind {
/// A full handshake.
///
/// This is the typical TLS connection initiation process when resumption is
/// not yet unavailable, and the initial `ClientHello` was accepted by the server.
Full,

/// A full TLS1.3 handshake, with an extra round-trip for a `HelloRetryRequest`.
///
/// The server can respond with a `HelloRetryRequest` if the initial `ClientHello`
/// is unacceptable for several reasons, the most likely if no supported key
/// shares were offered by the client.
FullWithHelloRetryRequest,

/// A resumed handshake.
///
/// Resumed handshakes involve fewer round trips and less cryptography than
/// full ones, but can only happen when the peers have previously done a full
/// handshake together, and then remember data about it.
Resumed,
}

/// Values of this structure are returned from [`Connection::process_new_packets`]
/// and tell the caller the current I/O state of the TLS connection.
///
Expand Down
2 changes: 1 addition & 1 deletion rustls/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -499,7 +499,7 @@ pub mod unbuffered {

// The public interface is:
pub use crate::builder::{ConfigBuilder, ConfigSide, WantsVerifier, WantsVersions};
pub use crate::common_state::{CommonState, IoState, Side};
pub use crate::common_state::{CommonState, HandshakeKind, IoState, Side};
#[cfg(feature = "std")]
pub use crate::conn::{Connection, Reader, Writer};
pub use crate::conn::{ConnectionCommon, SideData};
Expand Down
5 changes: 4 additions & 1 deletion rustls/src/server/tls12.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ use super::common::ActiveCertifiedKey;
use super::hs::{self, ServerContext};
use super::server_conn::{ProducesTickets, ServerConfig, ServerConnectionData};
use crate::check::inappropriate_message;
use crate::common_state::{CommonState, Side, State};
use crate::common_state::{CommonState, HandshakeKind, Side, State};
use crate::conn::ConnectionRandoms;
use crate::crypto::ActiveKeyExchange;
use crate::enums::{AlertDescription, ContentType, HandshakeType, ProtocolVersion};
Expand Down Expand Up @@ -192,6 +192,8 @@ mod client_hello {
self.session_id = SessionId::random(self.config.provider.secure_random)?;
}

cx.common.handshake_kind = Some(HandshakeKind::Full);

self.send_ticket = emit_server_hello(
&self.config,
&mut self.transcript,
Expand Down Expand Up @@ -290,6 +292,7 @@ mod client_hello {
cx.common
.start_encryption_tls12(&secrets, Side::Server);
cx.common.peer_certificates = resumedata.client_cert_chain;
cx.common.handshake_kind = Some(HandshakeKind::Resumed);

if self.send_ticket {
let now = self.config.current_time()?;
Expand Down
11 changes: 10 additions & 1 deletion rustls/src/server/tls13.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ use subtle::ConstantTimeEq;
use super::hs::{self, HandshakeHashOrBuffer, ServerContext};
use super::server_conn::ServerConnectionData;
use crate::check::{inappropriate_handshake_message, inappropriate_message};
use crate::common_state::{CommonState, Protocol, Side, State};
use crate::common_state::{CommonState, HandshakeKind, Protocol, Side, State};
use crate::conn::ConnectionRandoms;
use crate::enums::{AlertDescription, ContentType, HandshakeType, ProtocolVersion};
use crate::error::{Error, PeerIncompatible, PeerMisbehaved};
Expand Down Expand Up @@ -329,6 +329,14 @@ mod client_hello {
emit_fake_ccs(cx.common);
}

if full_handshake {
cx.common
.handshake_kind
.get_or_insert(HandshakeKind::Full);
} else {
cx.common.handshake_kind = Some(HandshakeKind::Resumed);
}

let mut ocsp_response = server_key.get_ocsp();
let doing_early_data = emit_encrypted_extensions(
&mut self.transcript,
Expand Down Expand Up @@ -555,6 +563,7 @@ mod client_hello {
transcript.rollup_for_hrr();
transcript.add_message(&m);
common.send_msg(m, false);
common.handshake_kind = Some(HandshakeKind::FullWithHelloRetryRequest);
}

fn decide_if_early_data_allowed(
Expand Down
66 changes: 64 additions & 2 deletions rustls/tests/api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,8 @@ use rustls::internal::msgs::message::{Message, MessagePayload};
use rustls::server::{ClientHello, ParsedCertificate, ResolvesServerCert};
use rustls::SupportedCipherSuite;
use rustls::{
sign, AlertDescription, CertificateError, ConnectionCommon, ContentType, Error, InvalidMessage,
KeyLog, PeerIncompatible, PeerMisbehaved, SideData,
sign, AlertDescription, CertificateError, ConnectionCommon, ContentType, Error, HandshakeKind,
InvalidMessage, KeyLog, PeerIncompatible, PeerMisbehaved, SideData,
};
use rustls::{CipherSuite, ProtocolVersion, SignatureScheme};
use rustls::{ClientConfig, ClientConnection};
Expand Down Expand Up @@ -506,6 +506,29 @@ fn server_can_get_client_cert_after_resumption() {
}
}

#[test]
fn resumption_combinations() {
for kt in ALL_KEY_TYPES {
let server_config = make_server_config(*kt);
for version in rustls::ALL_VERSIONS {
let client_config = make_client_config_with_versions(*kt, &[version]);
let (mut client, mut server) =
make_pair_for_configs(client_config.clone(), server_config.clone());
do_handshake(&mut client, &mut server);

assert_eq!(client.handshake_kind(), Ok(HandshakeKind::Full));
assert_eq!(server.handshake_kind(), Ok(HandshakeKind::Full));

let (mut client, mut server) =
make_pair_for_configs(client_config.clone(), server_config.clone());
do_handshake(&mut client, &mut server);

assert_eq!(client.handshake_kind(), Ok(HandshakeKind::Resumed));
assert_eq!(server.handshake_kind(), Ok(HandshakeKind::Resumed));
}
}
}

#[test]
fn test_config_builders_debug() {
if !provider_is_ring() {
Expand Down Expand Up @@ -3759,6 +3782,8 @@ fn tls13_stateful_resumption() {
.map(|certs| certs.len()),
Some(3)
);
assert_eq!(client.handshake_kind(), Ok(HandshakeKind::Full));
assert_eq!(server.handshake_kind(), Ok(HandshakeKind::Full));

// resumed
let (mut client, mut server) = make_pair_for_arc_configs(&client_config, &server_config);
Expand All @@ -3774,6 +3799,8 @@ fn tls13_stateful_resumption() {
.map(|certs| certs.len()),
Some(3)
);
assert_eq!(client.handshake_kind(), Ok(HandshakeKind::Resumed));
assert_eq!(server.handshake_kind(), Ok(HandshakeKind::Resumed));

// resumed again
let (mut client, mut server) = make_pair_for_arc_configs(&client_config, &server_config);
Expand All @@ -3789,6 +3816,8 @@ fn tls13_stateful_resumption() {
.map(|certs| certs.len()),
Some(3)
);
assert_eq!(client.handshake_kind(), Ok(HandshakeKind::Resumed));
assert_eq!(server.handshake_kind(), Ok(HandshakeKind::Resumed));
}

#[test]
Expand All @@ -3815,6 +3844,8 @@ fn tls13_stateless_resumption() {
.map(|certs| certs.len()),
Some(3)
);
assert_eq!(client.handshake_kind(), Ok(HandshakeKind::Full));
assert_eq!(server.handshake_kind(), Ok(HandshakeKind::Full));

// resumed
let (mut client, mut server) = make_pair_for_arc_configs(&client_config, &server_config);
Expand All @@ -3830,6 +3861,8 @@ fn tls13_stateless_resumption() {
.map(|certs| certs.len()),
Some(3)
);
assert_eq!(client.handshake_kind(), Ok(HandshakeKind::Resumed));
assert_eq!(server.handshake_kind(), Ok(HandshakeKind::Resumed));

// resumed again
let (mut client, mut server) = make_pair_for_arc_configs(&client_config, &server_config);
Expand All @@ -3845,6 +3878,8 @@ fn tls13_stateless_resumption() {
.map(|certs| certs.len()),
Some(3)
);
assert_eq!(client.handshake_kind(), Ok(HandshakeKind::Resumed));
assert_eq!(server.handshake_kind(), Ok(HandshakeKind::Resumed));
}

#[test]
Expand Down Expand Up @@ -4740,6 +4775,9 @@ fn test_client_sends_helloretryrequest() {

let (mut client, mut server) = make_pair_for_configs(client_config, server_config);

assert_eq!(client.handshake_kind(), Err(Error::HandshakeNotComplete));
assert_eq!(server.handshake_kind(), Err(Error::HandshakeNotComplete));

// client sends hello
{
let mut pipe = OtherSession::new(&mut server);
Expand All @@ -4749,6 +4787,12 @@ fn test_client_sends_helloretryrequest() {
assert!(pipe.writevs[0].len() == 1);
}

assert_eq!(client.handshake_kind(), Err(Error::HandshakeNotComplete));
assert_eq!(
server.handshake_kind(),
Ok(HandshakeKind::FullWithHelloRetryRequest)
);

// server sends HRR
{
let mut pipe = OtherSession::new(&mut client);
Expand All @@ -4758,6 +4802,15 @@ fn test_client_sends_helloretryrequest() {
assert!(pipe.writevs[0].len() == 2); // hello retry request and CCS
}

assert_eq!(
client.handshake_kind(),
Ok(HandshakeKind::FullWithHelloRetryRequest)
);
assert_eq!(
server.handshake_kind(),
Ok(HandshakeKind::FullWithHelloRetryRequest)
);

// client sends fixed hello
{
let mut pipe = OtherSession::new(&mut server);
Expand All @@ -4776,6 +4829,15 @@ fn test_client_sends_helloretryrequest() {
assert!(pipe.writevs[0].len() == 5); // server hello / encrypted exts / cert / cert-verify / finished
}

assert_eq!(
client.handshake_kind(),
Ok(HandshakeKind::FullWithHelloRetryRequest)
);
assert_eq!(
server.handshake_kind(),
Ok(HandshakeKind::FullWithHelloRetryRequest)
);

do_handshake_until_error(&mut client, &mut server).unwrap();

// client only did following storage queries:
Expand Down

0 comments on commit d8a2ae0

Please sign in to comment.