mirror of https://github.com/ctz/rustls
738 lines
27 KiB
Rust
738 lines
27 KiB
Rust
use crate::msgs::enums::{ContentType, HandshakeType, ExtensionType};
|
|
use crate::msgs::enums::{Compression, ProtocolVersion, AlertDescription};
|
|
use crate::msgs::message::{Message, MessagePayload};
|
|
use crate::msgs::base::Payload;
|
|
use crate::msgs::handshake::{HandshakePayload, HandshakeMessagePayload, ClientHelloPayload};
|
|
use crate::msgs::handshake::{SessionID, Random};
|
|
use crate::msgs::handshake::{ClientExtension, HasServerExtensions};
|
|
use crate::msgs::handshake::{ECPointFormatList, SupportedPointFormats};
|
|
use crate::msgs::handshake::{ProtocolNameList, ConvertProtocolNameList};
|
|
use crate::msgs::handshake::HelloRetryRequest;
|
|
use crate::msgs::handshake::{CertificateStatusRequest, SCTList};
|
|
use crate::msgs::enums::{PSKKeyExchangeMode, ECPointFormat};
|
|
use crate::msgs::codec::{Codec, Reader};
|
|
use crate::msgs::persist;
|
|
use crate::client::ClientSessionImpl;
|
|
use crate::session::SessionSecrets;
|
|
use crate::key_schedule::SecretKind;
|
|
use crate::cipher;
|
|
use crate::suites;
|
|
use crate::verify;
|
|
use crate::rand;
|
|
use crate::ticketer;
|
|
#[cfg(feature = "logging")]
|
|
use crate::log::{debug, trace};
|
|
use crate::error::TLSError;
|
|
use crate::handshake::check_handshake_message;
|
|
#[cfg(feature = "quic")]
|
|
use crate::{
|
|
msgs::base::PayloadU16,
|
|
session::Protocol
|
|
};
|
|
|
|
use crate::client::common::{ServerCertDetails, HandshakeDetails};
|
|
use crate::client::common::{ClientHelloDetails, ReceivedTicketDetails};
|
|
use crate::client::{tls12, tls13};
|
|
|
|
use webpki;
|
|
|
|
macro_rules! extract_handshake(
|
|
( $m:expr, $t:path ) => (
|
|
match $m.payload {
|
|
MessagePayload::Handshake(ref hsp) => match hsp.payload {
|
|
$t(ref hm) => Some(hm),
|
|
_ => None
|
|
},
|
|
_ => None
|
|
}
|
|
)
|
|
);
|
|
|
|
macro_rules! extract_handshake_mut(
|
|
( $m:expr, $t:path ) => (
|
|
match $m.payload {
|
|
MessagePayload::Handshake(hsp) => match hsp.payload {
|
|
$t(hm) => Some(hm),
|
|
_ => None
|
|
},
|
|
_ => None
|
|
}
|
|
)
|
|
);
|
|
|
|
pub type CheckResult = Result<(), TLSError>;
|
|
pub type NextState = Box<dyn State + Send + Sync>;
|
|
pub type NextStateOrError = Result<NextState, TLSError>;
|
|
|
|
pub trait State {
|
|
fn check_message(&self, m: &Message) -> CheckResult;
|
|
fn handle(self: Box<Self>, sess: &mut ClientSessionImpl, m: Message) -> NextStateOrError;
|
|
}
|
|
|
|
pub fn illegal_param(sess: &mut ClientSessionImpl, why: &str) -> TLSError {
|
|
sess.common.send_fatal_alert(AlertDescription::IllegalParameter);
|
|
TLSError::PeerMisbehavedError(why.to_string())
|
|
}
|
|
|
|
pub fn check_aligned_handshake(sess: &mut ClientSessionImpl) -> Result<(), TLSError> {
|
|
if !sess.common.handshake_joiner.is_empty() {
|
|
Err(illegal_param(sess, "keys changed with pending hs fragment"))
|
|
} else {
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
fn find_session(sess: &mut ClientSessionImpl, dns_name: webpki::DNSNameRef)
|
|
-> Option<persist::ClientSessionValue> {
|
|
let key = persist::ClientSessionKey::session_for_dns_name(dns_name);
|
|
let key_buf = key.get_encoding();
|
|
|
|
let maybe_value = sess.config.session_persistence.get(&key_buf);
|
|
|
|
if maybe_value.is_none() {
|
|
debug!("No cached session for {:?}", dns_name);
|
|
return None;
|
|
}
|
|
|
|
let value = maybe_value.unwrap();
|
|
let mut reader = Reader::init(&value[..]);
|
|
if let Some(result) = persist::ClientSessionValue::read(&mut reader) {
|
|
if result.has_expired(ticketer::timebase()) {
|
|
None
|
|
} else {
|
|
#[cfg(feature = "quic")] {
|
|
if sess.common.protocol == Protocol::Quic {
|
|
let params = PayloadU16::read(&mut reader)?;
|
|
sess.common.quic.params = Some(params.0);
|
|
}
|
|
}
|
|
Some(result)
|
|
}
|
|
} else {
|
|
None
|
|
}
|
|
}
|
|
|
|
fn random_sessionid() -> SessionID {
|
|
let mut random_id = [0u8; 32];
|
|
rand::fill_random(&mut random_id);
|
|
SessionID::new(&random_id)
|
|
}
|
|
|
|
/// If we have a ticket, we use the sessionid as a signal that we're
|
|
/// doing an abbreviated handshake. See section 3.4 in RFC5077.
|
|
fn random_sessionid_for_ticket(csv: &mut persist::ClientSessionValue) {
|
|
if !csv.ticket.0.is_empty() {
|
|
csv.session_id = random_sessionid();
|
|
}
|
|
}
|
|
|
|
struct InitialState {
|
|
handshake: HandshakeDetails,
|
|
}
|
|
|
|
impl InitialState {
|
|
fn new(host_name: webpki::DNSName, extra_exts: Vec<ClientExtension>) -> InitialState {
|
|
InitialState {
|
|
handshake: HandshakeDetails::new(host_name, extra_exts),
|
|
}
|
|
}
|
|
|
|
fn emit_initial_client_hello(mut self, sess: &mut ClientSessionImpl) -> NextState {
|
|
if sess.config.client_auth_cert_resolver.has_certs() {
|
|
self.handshake.transcript.set_client_auth_enabled();
|
|
}
|
|
let hello_details = ClientHelloDetails::new();
|
|
emit_client_hello_for_retry(sess, self.handshake, hello_details, None)
|
|
}
|
|
}
|
|
|
|
|
|
pub fn start_handshake(sess: &mut ClientSessionImpl, host_name: webpki::DNSName,
|
|
extra_exts: Vec<ClientExtension>) -> NextState {
|
|
InitialState::new(host_name, extra_exts)
|
|
.emit_initial_client_hello(sess)
|
|
}
|
|
|
|
struct ExpectServerHello {
|
|
handshake: HandshakeDetails,
|
|
hello: ClientHelloDetails,
|
|
server_cert: ServerCertDetails,
|
|
may_send_cert_status: bool,
|
|
must_issue_new_ticket: bool,
|
|
}
|
|
|
|
struct ExpectServerHelloOrHelloRetryRequest(ExpectServerHello);
|
|
|
|
pub fn compatible_suite(sess: &ClientSessionImpl,
|
|
resuming_suite: Option<&suites::SupportedCipherSuite>) -> bool {
|
|
match resuming_suite {
|
|
Some(resuming_suite) => {
|
|
if let Some(suite) = sess.common.get_suite() {
|
|
suite.can_resume_to(&resuming_suite)
|
|
} else {
|
|
true
|
|
}
|
|
}
|
|
None => false
|
|
}
|
|
}
|
|
|
|
fn emit_client_hello_for_retry(sess: &mut ClientSessionImpl,
|
|
mut handshake: HandshakeDetails,
|
|
mut hello: ClientHelloDetails,
|
|
retryreq: Option<&HelloRetryRequest>) -> NextState {
|
|
// Do we have a SessionID or ticket cached for this host?
|
|
handshake.resuming_session = find_session(sess, handshake.dns_name.as_ref());
|
|
let (session_id, ticket, resume_version) = if handshake.resuming_session.is_some() {
|
|
let resuming = handshake.resuming_session.as_mut().unwrap();
|
|
if resuming.version == ProtocolVersion::TLSv1_2 {
|
|
random_sessionid_for_ticket(resuming);
|
|
}
|
|
debug!("Resuming session");
|
|
(resuming.session_id, resuming.ticket.0.clone(), resuming.version)
|
|
} else {
|
|
debug!("Not resuming any session");
|
|
if handshake.session_id.is_empty() {
|
|
handshake.session_id = random_sessionid();
|
|
}
|
|
(handshake.session_id, Vec::new(), ProtocolVersion::Unknown(0))
|
|
};
|
|
|
|
let support_tls12 = sess.config.supports_version(ProtocolVersion::TLSv1_2);
|
|
let support_tls13 = sess.config.supports_version(ProtocolVersion::TLSv1_3);
|
|
|
|
let mut supported_versions = Vec::new();
|
|
if support_tls13 {
|
|
supported_versions.push(ProtocolVersion::TLSv1_3);
|
|
}
|
|
|
|
if support_tls12 {
|
|
supported_versions.push(ProtocolVersion::TLSv1_2);
|
|
}
|
|
|
|
let mut exts = Vec::new();
|
|
if !supported_versions.is_empty() {
|
|
exts.push(ClientExtension::SupportedVersions(supported_versions));
|
|
}
|
|
if sess.config.enable_sni {
|
|
exts.push(ClientExtension::make_sni(handshake.dns_name.as_ref()));
|
|
}
|
|
exts.push(ClientExtension::ECPointFormats(ECPointFormatList::supported()));
|
|
exts.push(ClientExtension::NamedGroups(suites::KeyExchange::supported_groups().to_vec()));
|
|
exts.push(ClientExtension::SignatureAlgorithms(verify::supported_verify_schemes().to_vec()));
|
|
exts.push(ClientExtension::ExtendedMasterSecretRequest);
|
|
exts.push(ClientExtension::CertificateStatusRequest(CertificateStatusRequest::build_ocsp()));
|
|
|
|
if sess.config.ct_logs.is_some() {
|
|
exts.push(ClientExtension::SignedCertificateTimestampRequest);
|
|
}
|
|
|
|
if support_tls13 {
|
|
tls13::choose_kx_groups(sess, &mut exts, &mut hello, &mut handshake, retryreq);
|
|
}
|
|
|
|
if let Some(cookie) = retryreq.and_then(HelloRetryRequest::get_cookie) {
|
|
exts.push(ClientExtension::Cookie(cookie.clone()));
|
|
}
|
|
|
|
if support_tls13 && sess.config.enable_tickets {
|
|
// We could support PSK_KE here too. Such connections don't
|
|
// have forward secrecy, and are similar to TLS1.2 resumption.
|
|
let psk_modes = vec![ PSKKeyExchangeMode::PSK_DHE_KE ];
|
|
exts.push(ClientExtension::PresharedKeyModes(psk_modes));
|
|
}
|
|
|
|
if !sess.config.alpn_protocols.is_empty() {
|
|
exts.push(ClientExtension::Protocols(ProtocolNameList::from_slices(&sess.config
|
|
.alpn_protocols
|
|
.iter()
|
|
.map(|proto| &proto[..])
|
|
.collect::<Vec<_>>()
|
|
)));
|
|
}
|
|
|
|
// Extra extensions must be placed before the PSK extension
|
|
exts.extend(handshake.extra_exts.iter().cloned());
|
|
|
|
let fill_in_binder = if support_tls13 && sess.config.enable_tickets &&
|
|
resume_version == ProtocolVersion::TLSv1_3 &&
|
|
!ticket.is_empty() {
|
|
tls13::prepare_resumption(sess, ticket, &handshake, &mut exts,
|
|
retryreq.is_some())
|
|
} else if sess.config.enable_tickets {
|
|
// If we have a ticket, include it. Otherwise, request one.
|
|
if ticket.is_empty() {
|
|
exts.push(ClientExtension::SessionTicketRequest);
|
|
} else {
|
|
exts.push(ClientExtension::SessionTicketOffer(Payload::new(ticket)));
|
|
}
|
|
false
|
|
} else {
|
|
false
|
|
};
|
|
|
|
// Note what extensions we sent.
|
|
hello.sent_extensions = exts.iter()
|
|
.map(ClientExtension::get_type)
|
|
.collect();
|
|
|
|
let mut chp = HandshakeMessagePayload {
|
|
typ: HandshakeType::ClientHello,
|
|
payload: HandshakePayload::ClientHello(ClientHelloPayload {
|
|
client_version: ProtocolVersion::TLSv1_2,
|
|
random: Random::from_slice(&handshake.randoms.client),
|
|
session_id,
|
|
cipher_suites: sess.get_cipher_suites(),
|
|
compression_methods: vec![Compression::Null],
|
|
extensions: exts,
|
|
}),
|
|
};
|
|
|
|
if fill_in_binder {
|
|
tls13::fill_in_psk_binder(sess, &mut handshake, &mut chp);
|
|
}
|
|
|
|
let ch = Message {
|
|
typ: ContentType::Handshake,
|
|
// "This value MUST be set to 0x0303 for all records generated
|
|
// by a TLS 1.3 implementation other than an initial ClientHello
|
|
// (i.e., one not generated after a HelloRetryRequest)"
|
|
version: if retryreq.is_some() {
|
|
ProtocolVersion::TLSv1_2
|
|
} else {
|
|
ProtocolVersion::TLSv1_0
|
|
},
|
|
payload: MessagePayload::Handshake(chp),
|
|
};
|
|
|
|
if retryreq.is_some() {
|
|
// send dummy CCS to fool middleboxes prior
|
|
// to second client hello
|
|
tls13::emit_fake_ccs(&mut handshake, sess);
|
|
}
|
|
|
|
trace!("Sending ClientHello {:#?}", ch);
|
|
|
|
handshake.transcript.add_message(&ch);
|
|
sess.common.send_msg(ch, false);
|
|
|
|
// Calculate the hash of ClientHello and use it to derive EarlyTrafficSecret
|
|
if sess.early_data.is_enabled() {
|
|
// For middlebox compatibility
|
|
tls13::emit_fake_ccs(&mut handshake, sess);
|
|
|
|
// It is safe to call unwrap() because fill_in_binder is true.
|
|
let resuming_suite = handshake.resuming_session
|
|
.as_ref()
|
|
.and_then(|resume| sess.find_cipher_suite(resume.cipher_suite)).unwrap();
|
|
|
|
let client_hello_hash = handshake.transcript.get_hash_given(resuming_suite.get_hash(), &[]);
|
|
let client_early_traffic_secret = sess.common
|
|
.get_key_schedule()
|
|
.derive_logged_secret(SecretKind::ClientEarlyTrafficSecret, &client_hello_hash,
|
|
&*sess.config.key_log,
|
|
&handshake.randoms.client);
|
|
// Set early data encryption key
|
|
sess.common
|
|
.set_message_encrypter(cipher::new_tls13_write(resuming_suite, &client_early_traffic_secret));
|
|
|
|
#[cfg(feature = "quic")]
|
|
{
|
|
sess.common.quic.early_secret = Some(client_early_traffic_secret);
|
|
}
|
|
|
|
// Now the client can send encrypted early data
|
|
sess.common.early_traffic = true;
|
|
trace!("Starting early data traffic");
|
|
sess.common.we_now_encrypting();
|
|
}
|
|
|
|
let next = ExpectServerHello {
|
|
handshake, hello,
|
|
server_cert: ServerCertDetails::new(),
|
|
may_send_cert_status: false,
|
|
must_issue_new_ticket: false,
|
|
};
|
|
|
|
if support_tls13 && retryreq.is_none() {
|
|
Box::new(ExpectServerHelloOrHelloRetryRequest(next))
|
|
} else {
|
|
Box::new(next)
|
|
}
|
|
}
|
|
|
|
pub fn process_alpn_protocol(sess: &mut ClientSessionImpl,
|
|
proto: Option<&[u8]>)
|
|
-> Result<(), TLSError> {
|
|
sess.alpn_protocol = proto.map(ToOwned::to_owned);
|
|
if sess.alpn_protocol.is_some() &&
|
|
!sess.config.alpn_protocols.contains(sess.alpn_protocol.as_ref().unwrap()) {
|
|
return Err(illegal_param(sess, "server sent non-offered ALPN protocol"));
|
|
}
|
|
debug!("ALPN protocol is {:?}", sess.alpn_protocol);
|
|
Ok(())
|
|
}
|
|
|
|
pub fn sct_list_is_invalid(scts: &SCTList) -> bool {
|
|
scts.is_empty() ||
|
|
scts.iter().any(|sct| sct.0.is_empty())
|
|
}
|
|
|
|
impl ExpectServerHello {
|
|
fn into_expect_tls13_encrypted_extensions(self) -> NextState {
|
|
Box::new(tls13::ExpectEncryptedExtensions {
|
|
handshake: self.handshake,
|
|
server_cert: self.server_cert,
|
|
hello: self.hello,
|
|
})
|
|
}
|
|
|
|
fn into_expect_tls12_new_ticket_resume(self,
|
|
certv: verify::ServerCertVerified,
|
|
sigv: verify::HandshakeSignatureValid) -> NextState {
|
|
Box::new(tls12::ExpectNewTicket {
|
|
handshake: self.handshake,
|
|
resuming: true,
|
|
cert_verified: certv,
|
|
sig_verified: sigv,
|
|
})
|
|
}
|
|
|
|
fn into_expect_tls12_ccs_resume(self,
|
|
certv: verify::ServerCertVerified,
|
|
sigv: verify::HandshakeSignatureValid) -> NextState {
|
|
Box::new(tls12::ExpectCCS {
|
|
handshake: self.handshake,
|
|
ticket: ReceivedTicketDetails::new(),
|
|
resuming: true,
|
|
cert_verified: certv,
|
|
sig_verified: sigv,
|
|
})
|
|
}
|
|
|
|
fn into_expect_tls12_certificate(self) -> NextState {
|
|
Box::new(tls12::ExpectCertificate {
|
|
handshake: self.handshake,
|
|
server_cert: self.server_cert,
|
|
may_send_cert_status: self.may_send_cert_status,
|
|
must_issue_new_ticket: self.must_issue_new_ticket,
|
|
})
|
|
}
|
|
}
|
|
|
|
impl State for ExpectServerHello {
|
|
fn check_message(&self, m: &Message) -> CheckResult {
|
|
check_handshake_message(m, &[HandshakeType::ServerHello])
|
|
}
|
|
|
|
fn handle(mut self: Box<Self>, sess: &mut ClientSessionImpl, m: Message) -> NextStateOrError {
|
|
let server_hello = extract_handshake!(m, HandshakePayload::ServerHello).unwrap();
|
|
trace!("We got ServerHello {:#?}", server_hello);
|
|
|
|
use crate::ProtocolVersion::{TLSv1_2, TLSv1_3};
|
|
let tls13_supported = sess.config.supports_version(TLSv1_3);
|
|
|
|
let server_version = if server_hello.legacy_version == TLSv1_2 {
|
|
server_hello.get_supported_versions()
|
|
.unwrap_or(server_hello.legacy_version)
|
|
} else {
|
|
server_hello.legacy_version
|
|
};
|
|
|
|
match server_version {
|
|
TLSv1_3 if tls13_supported => {
|
|
sess.common.negotiated_version = Some(TLSv1_3);
|
|
}
|
|
TLSv1_2 if sess.config.supports_version(TLSv1_2) => {
|
|
if sess.early_data.is_enabled() && sess.common.early_traffic {
|
|
// The client must fail with a dedicated error code if the server
|
|
// responds with TLS 1.2 when offering 0-RTT.
|
|
return Err(TLSError::PeerMisbehavedError("server chose v1.2 when offering 0-rtt"
|
|
.to_string()));
|
|
}
|
|
sess.common.negotiated_version = Some(TLSv1_2);
|
|
|
|
if server_hello.get_supported_versions().is_some() {
|
|
return Err(illegal_param(sess, "server chose v1.2 using v1.3 extension"));
|
|
}
|
|
}
|
|
_ => {
|
|
sess.common.send_fatal_alert(AlertDescription::ProtocolVersion);
|
|
return Err(TLSError::PeerIncompatibleError("server does not support TLS v1.2/v1.3"
|
|
.to_string()));
|
|
}
|
|
};
|
|
|
|
if server_hello.compression_method != Compression::Null {
|
|
return Err(illegal_param(sess, "server chose non-Null compression"));
|
|
}
|
|
|
|
if server_hello.has_duplicate_extension() {
|
|
sess.common.send_fatal_alert(AlertDescription::DecodeError);
|
|
return Err(TLSError::PeerMisbehavedError("server sent duplicate extensions".to_string()));
|
|
}
|
|
|
|
let allowed_unsolicited = [ ExtensionType::RenegotiationInfo ];
|
|
if self.hello.server_sent_unsolicited_extensions(&server_hello.extensions,
|
|
&allowed_unsolicited) {
|
|
sess.common.send_fatal_alert(AlertDescription::UnsupportedExtension);
|
|
return Err(TLSError::PeerMisbehavedError("server sent unsolicited extension".to_string()));
|
|
}
|
|
|
|
// Extract ALPN protocol
|
|
if !sess.common.is_tls13() {
|
|
process_alpn_protocol(sess, server_hello.get_alpn_protocol())?;
|
|
}
|
|
|
|
// If ECPointFormats extension is supplied by the server, it must contain
|
|
// Uncompressed. But it's allowed to be omitted.
|
|
if let Some(point_fmts) = server_hello.get_ecpoints_extension() {
|
|
if !point_fmts.contains(&ECPointFormat::Uncompressed) {
|
|
sess.common.send_fatal_alert(AlertDescription::HandshakeFailure);
|
|
return Err(TLSError::PeerMisbehavedError("server does not support uncompressed points"
|
|
.to_string()));
|
|
}
|
|
}
|
|
|
|
let scs = sess.find_cipher_suite(server_hello.cipher_suite);
|
|
|
|
if scs.is_none() {
|
|
sess.common.send_fatal_alert(AlertDescription::HandshakeFailure);
|
|
return Err(TLSError::PeerMisbehavedError("server chose non-offered ciphersuite"
|
|
.to_string()));
|
|
}
|
|
|
|
debug!("Using ciphersuite {:?}", server_hello.cipher_suite);
|
|
if !sess.common.set_suite(scs.unwrap()) {
|
|
return Err(illegal_param(sess, "server varied selected ciphersuite"));
|
|
}
|
|
|
|
let version = sess.common.negotiated_version.unwrap();
|
|
if !sess.common.get_suite_assert().usable_for_version(version) {
|
|
return Err(illegal_param(sess, "server chose unusable ciphersuite for version"));
|
|
}
|
|
|
|
// Start our handshake hash, and input the server-hello.
|
|
let starting_hash = sess.common.get_suite_assert().get_hash();
|
|
self.handshake.transcript.start_hash(starting_hash);
|
|
self.handshake.transcript.add_message(&m);
|
|
|
|
// For TLS1.3, start message encryption using
|
|
// handshake_traffic_secret.
|
|
if sess.common.is_tls13() {
|
|
tls13::validate_server_hello(sess, server_hello)?;
|
|
tls13::start_handshake_traffic(sess, server_hello, &mut self.handshake, &mut self.hello)?;
|
|
tls13::emit_fake_ccs(&mut self.handshake, sess);
|
|
return Ok(self.into_expect_tls13_encrypted_extensions());
|
|
}
|
|
|
|
// TLS1.2 only from here-on
|
|
|
|
// Save ServerRandom and SessionID
|
|
server_hello.random.write_slice(&mut self.handshake.randoms.server);
|
|
self.handshake.session_id = server_hello.session_id;
|
|
|
|
// Look for TLS1.3 downgrade signal in server random
|
|
if tls13_supported && self.handshake.randoms.has_tls12_downgrade_marker() {
|
|
return Err(illegal_param(sess, "downgrade to TLS1.2 when TLS1.3 is supported"));
|
|
}
|
|
|
|
// Doing EMS?
|
|
if server_hello.ems_support_acked() {
|
|
self.handshake.using_ems = true;
|
|
}
|
|
|
|
// Might the server send a ticket?
|
|
let with_tickets = if server_hello.find_extension(ExtensionType::SessionTicket).is_some() {
|
|
debug!("Server supports tickets");
|
|
true
|
|
} else {
|
|
false
|
|
};
|
|
self.must_issue_new_ticket = with_tickets;
|
|
|
|
// Might the server send a CertificateStatus between Certificate and
|
|
// ServerKeyExchange?
|
|
if server_hello.find_extension(ExtensionType::StatusRequest).is_some() {
|
|
debug!("Server may staple OCSP response");
|
|
self.may_send_cert_status = true;
|
|
}
|
|
|
|
// Save any sent SCTs for verification against the certificate.
|
|
if let Some(sct_list) = server_hello.get_sct_list() {
|
|
debug!("Server sent {:?} SCTs", sct_list.len());
|
|
|
|
if sct_list_is_invalid(sct_list) {
|
|
let error_msg = "server sent invalid SCT list".to_string();
|
|
return Err(TLSError::PeerMisbehavedError(error_msg));
|
|
}
|
|
self.server_cert.scts = Some(sct_list.clone());
|
|
}
|
|
|
|
// See if we're successfully resuming.
|
|
let mut abbreviated_handshake = false;
|
|
if let Some(ref resuming) = self.handshake.resuming_session {
|
|
if resuming.session_id == self.handshake.session_id {
|
|
debug!("Server agreed to resume");
|
|
abbreviated_handshake = true;
|
|
|
|
// Is the server telling lies about the ciphersuite?
|
|
if resuming.cipher_suite != scs.unwrap().suite {
|
|
let error_msg = "abbreviated handshake offered, but with varied cs".to_string();
|
|
return Err(TLSError::PeerMisbehavedError(error_msg));
|
|
}
|
|
|
|
// And about EMS support?
|
|
if resuming.extended_ms != self.handshake.using_ems {
|
|
let error_msg = "server varied ems support over resume".to_string();
|
|
return Err(TLSError::PeerMisbehavedError(error_msg));
|
|
}
|
|
|
|
let secrets = SessionSecrets::new_resume(&self.handshake.randoms,
|
|
scs.unwrap().get_hash(),
|
|
&resuming.master_secret.0);
|
|
sess.config.key_log.log("CLIENT_RANDOM",
|
|
&secrets.randoms.client,
|
|
&secrets.master_secret);
|
|
sess.common.start_encryption_tls12(secrets);
|
|
}
|
|
}
|
|
|
|
if abbreviated_handshake {
|
|
// Since we're resuming, we verified the certificate and
|
|
// proof of possession in the prior session.
|
|
let certv = verify::ServerCertVerified::assertion();
|
|
let sigv = verify::HandshakeSignatureValid::assertion();
|
|
|
|
if self.must_issue_new_ticket {
|
|
Ok(self.into_expect_tls12_new_ticket_resume(certv, sigv))
|
|
} else {
|
|
Ok(self.into_expect_tls12_ccs_resume(certv, sigv))
|
|
}
|
|
} else {
|
|
Ok(self.into_expect_tls12_certificate())
|
|
}
|
|
}
|
|
}
|
|
|
|
impl ExpectServerHelloOrHelloRetryRequest {
|
|
fn into_expect_server_hello(self) -> NextState {
|
|
Box::new(self.0)
|
|
}
|
|
|
|
fn handle_hello_retry_request(mut self, sess: &mut ClientSessionImpl, m: Message) -> NextStateOrError {
|
|
check_handshake_message(&m, &[HandshakeType::HelloRetryRequest])?;
|
|
|
|
let hrr = extract_handshake!(m, HandshakePayload::HelloRetryRequest).unwrap();
|
|
trace!("Got HRR {:?}", hrr);
|
|
|
|
let has_cookie = hrr.get_cookie().is_some();
|
|
let req_group = hrr.get_requested_key_share_group();
|
|
|
|
// A retry request is illegal if it contains no cookie and asks for
|
|
// retry of a group we already sent.
|
|
if !has_cookie && req_group.map(|g| self.0.hello.has_key_share(g)).unwrap_or(false) {
|
|
return Err(illegal_param(sess, "server requested hrr with our group"));
|
|
}
|
|
|
|
// Or asks for us to retry on an unsupported group.
|
|
if let Some(group) = req_group {
|
|
if !suites::KeyExchange::supported_groups().contains(&group) {
|
|
return Err(illegal_param(sess, "server requested hrr with bad group"));
|
|
}
|
|
}
|
|
|
|
// Or has an empty cookie.
|
|
if has_cookie && hrr.get_cookie().unwrap().0.is_empty() {
|
|
return Err(illegal_param(sess, "server requested hrr with empty cookie"));
|
|
}
|
|
|
|
// Or has something unrecognised
|
|
if hrr.has_unknown_extension() {
|
|
sess.common.send_fatal_alert(AlertDescription::UnsupportedExtension);
|
|
return Err(TLSError::PeerIncompatibleError("server sent hrr with unhandled extension"
|
|
.to_string()));
|
|
}
|
|
|
|
// Or has the same extensions more than once
|
|
if hrr.has_duplicate_extension() {
|
|
return Err(illegal_param(sess, "server send duplicate hrr extensions"));
|
|
}
|
|
|
|
// Or asks us to change nothing.
|
|
if !has_cookie && req_group.is_none() {
|
|
return Err(illegal_param(sess, "server requested hrr with no changes"));
|
|
}
|
|
|
|
// Or asks us to talk a protocol we didn't offer, or doesn't support HRR at all.
|
|
match hrr.get_supported_versions() {
|
|
Some(ProtocolVersion::TLSv1_3) => {
|
|
sess.common.negotiated_version = Some(ProtocolVersion::TLSv1_3);
|
|
}
|
|
_ => {
|
|
return Err(illegal_param(sess, "server requested unsupported version in hrr"));
|
|
}
|
|
}
|
|
|
|
// Or asks us to use a ciphersuite we didn't offer.
|
|
let maybe_cs = sess.find_cipher_suite(hrr.cipher_suite);
|
|
let cs = match maybe_cs {
|
|
Some(cs) => cs,
|
|
None => {
|
|
return Err(illegal_param(sess, "server requested unsupported cs in hrr"));
|
|
}
|
|
};
|
|
|
|
// HRR selects the ciphersuite.
|
|
sess.common.set_suite(cs);
|
|
|
|
// This is the draft19 change where the transcript became a tree
|
|
self.0.handshake.transcript.start_hash(cs.get_hash());
|
|
self.0.handshake.transcript.rollup_for_hrr();
|
|
self.0.handshake.transcript.add_message(&m);
|
|
|
|
// Early data is not alllowed after HelloRetryrequest
|
|
if sess.early_data.is_enabled() {
|
|
sess.early_data.rejected();
|
|
}
|
|
|
|
Ok(emit_client_hello_for_retry(sess,
|
|
self.0.handshake,
|
|
self.0.hello,
|
|
Some(hrr)))
|
|
}
|
|
}
|
|
|
|
impl State for ExpectServerHelloOrHelloRetryRequest {
|
|
fn check_message(&self, m: &Message) -> CheckResult {
|
|
check_handshake_message(m,
|
|
&[HandshakeType::ServerHello,
|
|
HandshakeType::HelloRetryRequest])
|
|
}
|
|
|
|
fn handle(self: Box<Self>, sess: &mut ClientSessionImpl, m: Message) -> NextStateOrError {
|
|
if m.is_handshake_type(HandshakeType::ServerHello) {
|
|
self.into_expect_server_hello().handle(sess, m)
|
|
} else {
|
|
self.handle_hello_retry_request(sess, m)
|
|
}
|
|
}
|
|
}
|
|
|
|
pub fn send_cert_error_alert(sess: &mut ClientSessionImpl, err: TLSError) -> TLSError {
|
|
match err {
|
|
TLSError::WebPKIError(webpki::Error::BadDER) => {
|
|
sess.common.send_fatal_alert(AlertDescription::DecodeError);
|
|
}
|
|
TLSError::PeerMisbehavedError(_) => {
|
|
sess.common.send_fatal_alert(AlertDescription::IllegalParameter);
|
|
}
|
|
_ => {
|
|
sess.common.send_fatal_alert(AlertDescription::BadCertificate);
|
|
}
|
|
};
|
|
|
|
err
|
|
}
|