mirror of https://github.com/ctz/rustls
939 lines
31 KiB
Rust
939 lines
31 KiB
Rust
use crate::check::inappropriate_message;
|
|
use crate::common_state::{CommonState, Side, State};
|
|
use crate::conn::ConnectionRandoms;
|
|
use crate::enums::ProtocolVersion;
|
|
use crate::enums::{AlertDescription, ContentType, HandshakeType};
|
|
use crate::error::{Error, PeerIncompatible, PeerMisbehaved};
|
|
use crate::hash_hs::HandshakeHash;
|
|
use crate::key::Certificate;
|
|
#[cfg(feature = "logging")]
|
|
use crate::log::{debug, trace};
|
|
use crate::msgs::base::Payload;
|
|
use crate::msgs::ccs::ChangeCipherSpecPayload;
|
|
use crate::msgs::codec::Codec;
|
|
use crate::msgs::handshake::{ClientECDHParams, HandshakeMessagePayload, HandshakePayload};
|
|
use crate::msgs::handshake::{NewSessionTicketPayload, SessionID};
|
|
use crate::msgs::message::{Message, MessagePayload};
|
|
use crate::msgs::persist;
|
|
#[cfg(feature = "secret_extraction")]
|
|
use crate::suites::PartiallyExtractedSecrets;
|
|
use crate::tls12::{self, ConnectionSecrets, Tls12CipherSuite};
|
|
use crate::{kx, ticketer, verify};
|
|
|
|
use super::common::ActiveCertifiedKey;
|
|
use super::hs::{self, ServerContext};
|
|
use super::server_conn::{ProducesTickets, ServerConfig, ServerConnectionData};
|
|
|
|
use ring::constant_time;
|
|
|
|
use std::sync::Arc;
|
|
|
|
pub(super) use client_hello::CompleteClientHelloHandling;
|
|
|
|
mod client_hello {
|
|
use crate::enums::SignatureScheme;
|
|
use crate::msgs::enums::ECPointFormat;
|
|
use crate::msgs::enums::{ClientCertificateType, Compression};
|
|
use crate::msgs::handshake::ServerECDHParams;
|
|
use crate::msgs::handshake::{CertificateRequestPayload, ClientSessionTicket, Random};
|
|
use crate::msgs::handshake::{CertificateStatus, ECDHEServerKeyExchange};
|
|
use crate::msgs::handshake::{ClientExtension, SessionID};
|
|
use crate::msgs::handshake::{ClientHelloPayload, ServerHelloPayload};
|
|
use crate::msgs::handshake::{ServerExtension, ServerKeyExchangePayload};
|
|
use crate::sign;
|
|
use crate::verify::DigitallySignedStruct;
|
|
|
|
use super::*;
|
|
|
|
pub(in crate::server) struct CompleteClientHelloHandling {
|
|
pub(in crate::server) config: Arc<ServerConfig>,
|
|
pub(in crate::server) transcript: HandshakeHash,
|
|
pub(in crate::server) session_id: SessionID,
|
|
pub(in crate::server) suite: &'static Tls12CipherSuite,
|
|
pub(in crate::server) using_ems: bool,
|
|
pub(in crate::server) randoms: ConnectionRandoms,
|
|
pub(in crate::server) send_ticket: bool,
|
|
pub(in crate::server) extra_exts: Vec<ServerExtension>,
|
|
}
|
|
|
|
impl CompleteClientHelloHandling {
|
|
pub(in crate::server) fn handle_client_hello(
|
|
mut self,
|
|
cx: &mut ServerContext<'_>,
|
|
server_key: ActiveCertifiedKey,
|
|
chm: &Message,
|
|
client_hello: &ClientHelloPayload,
|
|
sigschemes_ext: Vec<SignatureScheme>,
|
|
tls13_enabled: bool,
|
|
) -> hs::NextStateOrError {
|
|
// -- TLS1.2 only from hereon in --
|
|
self.transcript.add_message(chm);
|
|
|
|
if client_hello.ems_support_offered() {
|
|
self.using_ems = true;
|
|
}
|
|
|
|
let groups_ext = client_hello
|
|
.get_namedgroups_extension()
|
|
.ok_or_else(|| {
|
|
hs::incompatible(cx.common, PeerIncompatible::NamedGroupsExtensionRequired)
|
|
})?;
|
|
let ecpoints_ext = client_hello
|
|
.get_ecpoints_extension()
|
|
.ok_or_else(|| {
|
|
hs::incompatible(cx.common, PeerIncompatible::EcPointsExtensionRequired)
|
|
})?;
|
|
|
|
trace!("namedgroups {:?}", groups_ext);
|
|
trace!("ecpoints {:?}", ecpoints_ext);
|
|
|
|
if !ecpoints_ext.contains(&ECPointFormat::Uncompressed) {
|
|
cx.common
|
|
.send_fatal_alert(AlertDescription::IllegalParameter);
|
|
return Err(PeerIncompatible::UncompressedEcPointsRequired.into());
|
|
}
|
|
|
|
// -- If TLS1.3 is enabled, signal the downgrade in the server random
|
|
if tls13_enabled {
|
|
self.randoms.server[24..].copy_from_slice(&tls12::DOWNGRADE_SENTINEL);
|
|
}
|
|
|
|
// -- Check for resumption --
|
|
// We can do this either by (in order of preference):
|
|
// 1. receiving a ticket that decrypts
|
|
// 2. receiving a sessionid that is in our cache
|
|
//
|
|
// If we receive a ticket, the sessionid won't be in our
|
|
// cache, so don't check.
|
|
//
|
|
// If either works, we end up with a ServerConnectionValue
|
|
// which is passed to start_resumption and concludes
|
|
// our handling of the ClientHello.
|
|
//
|
|
let mut ticket_received = false;
|
|
let resume_data = client_hello
|
|
.get_ticket_extension()
|
|
.and_then(|ticket_ext| match ticket_ext {
|
|
ClientExtension::SessionTicket(ClientSessionTicket::Offer(ticket)) => {
|
|
Some(ticket)
|
|
}
|
|
_ => None,
|
|
})
|
|
.and_then(|ticket| {
|
|
ticket_received = true;
|
|
debug!("Ticket received");
|
|
let data = self.config.ticketer.decrypt(&ticket.0);
|
|
if data.is_none() {
|
|
debug!("Ticket didn't decrypt");
|
|
}
|
|
data
|
|
})
|
|
.or_else(|| {
|
|
// Perhaps resume? If we received a ticket, the sessionid
|
|
// does not correspond to a real session.
|
|
if client_hello.session_id.is_empty() || ticket_received {
|
|
return None;
|
|
}
|
|
|
|
self.config
|
|
.session_storage
|
|
.get(&client_hello.session_id.get_encoding())
|
|
})
|
|
.and_then(|x| persist::ServerSessionValue::read_bytes(&x).ok())
|
|
.filter(|resumedata| {
|
|
hs::can_resume(self.suite.into(), &cx.data.sni, self.using_ems, resumedata)
|
|
});
|
|
|
|
if let Some(data) = resume_data {
|
|
return self.start_resumption(cx, client_hello, &client_hello.session_id, data);
|
|
}
|
|
|
|
// Now we have chosen a ciphersuite, we can make kx decisions.
|
|
let sigschemes = self
|
|
.suite
|
|
.resolve_sig_schemes(&sigschemes_ext);
|
|
|
|
if sigschemes.is_empty() {
|
|
return Err(hs::incompatible(
|
|
cx.common,
|
|
PeerIncompatible::NoSignatureSchemesInCommon,
|
|
));
|
|
}
|
|
|
|
let group = self
|
|
.config
|
|
.kx_groups
|
|
.iter()
|
|
.find(|skxg| groups_ext.contains(&skxg.name))
|
|
.cloned()
|
|
.ok_or_else(|| hs::incompatible(cx.common, PeerIncompatible::NoKxGroupsInCommon))?;
|
|
|
|
let ecpoint = ECPointFormat::SUPPORTED
|
|
.iter()
|
|
.find(|format| ecpoints_ext.contains(format))
|
|
.cloned()
|
|
.ok_or_else(|| {
|
|
hs::incompatible(cx.common, PeerIncompatible::NoEcPointFormatsInCommon)
|
|
})?;
|
|
|
|
debug_assert_eq!(ecpoint, ECPointFormat::Uncompressed);
|
|
|
|
let (mut ocsp_response, mut sct_list) =
|
|
(server_key.get_ocsp(), server_key.get_sct_list());
|
|
|
|
// If we're not offered a ticket or a potential session ID, allocate a session ID.
|
|
if !self.config.session_storage.can_cache() {
|
|
self.session_id = SessionID::empty();
|
|
} else if self.session_id.is_empty() && !ticket_received {
|
|
self.session_id = SessionID::random()?;
|
|
}
|
|
|
|
self.send_ticket = emit_server_hello(
|
|
&self.config,
|
|
&mut self.transcript,
|
|
cx,
|
|
self.session_id,
|
|
self.suite,
|
|
self.using_ems,
|
|
&mut ocsp_response,
|
|
&mut sct_list,
|
|
client_hello,
|
|
None,
|
|
&self.randoms,
|
|
self.extra_exts,
|
|
)?;
|
|
emit_certificate(&mut self.transcript, cx.common, server_key.get_cert());
|
|
if let Some(ocsp_response) = ocsp_response {
|
|
emit_cert_status(&mut self.transcript, cx.common, ocsp_response);
|
|
}
|
|
let server_kx = emit_server_kx(
|
|
&mut self.transcript,
|
|
cx.common,
|
|
sigschemes,
|
|
group,
|
|
server_key.get_key(),
|
|
&self.randoms,
|
|
)?;
|
|
let doing_client_auth = emit_certificate_req(&self.config, &mut self.transcript, cx)?;
|
|
emit_server_hello_done(&mut self.transcript, cx.common);
|
|
|
|
if doing_client_auth {
|
|
Ok(Box::new(ExpectCertificate {
|
|
config: self.config,
|
|
transcript: self.transcript,
|
|
randoms: self.randoms,
|
|
session_id: self.session_id,
|
|
suite: self.suite,
|
|
using_ems: self.using_ems,
|
|
server_kx,
|
|
send_ticket: self.send_ticket,
|
|
}))
|
|
} else {
|
|
Ok(Box::new(ExpectClientKx {
|
|
config: self.config,
|
|
transcript: self.transcript,
|
|
randoms: self.randoms,
|
|
session_id: self.session_id,
|
|
suite: self.suite,
|
|
using_ems: self.using_ems,
|
|
server_kx,
|
|
client_cert: None,
|
|
send_ticket: self.send_ticket,
|
|
}))
|
|
}
|
|
}
|
|
|
|
fn start_resumption(
|
|
mut self,
|
|
cx: &mut ServerContext<'_>,
|
|
client_hello: &ClientHelloPayload,
|
|
id: &SessionID,
|
|
resumedata: persist::ServerSessionValue,
|
|
) -> hs::NextStateOrError {
|
|
debug!("Resuming connection");
|
|
|
|
if resumedata.extended_ms && !self.using_ems {
|
|
return Err(cx
|
|
.common
|
|
.illegal_param(PeerMisbehaved::ResumptionAttemptedWithVariedEms));
|
|
}
|
|
|
|
self.session_id = *id;
|
|
self.send_ticket = emit_server_hello(
|
|
&self.config,
|
|
&mut self.transcript,
|
|
cx,
|
|
self.session_id,
|
|
self.suite,
|
|
self.using_ems,
|
|
&mut None,
|
|
&mut None,
|
|
client_hello,
|
|
Some(&resumedata),
|
|
&self.randoms,
|
|
self.extra_exts,
|
|
)?;
|
|
|
|
let secrets = ConnectionSecrets::new_resume(
|
|
self.randoms,
|
|
self.suite,
|
|
&resumedata.master_secret.0,
|
|
);
|
|
self.config.key_log.log(
|
|
"CLIENT_RANDOM",
|
|
&secrets.randoms.client,
|
|
&secrets.master_secret,
|
|
);
|
|
cx.common
|
|
.start_encryption_tls12(&secrets, Side::Server);
|
|
cx.common.peer_certificates = resumedata.client_cert_chain;
|
|
|
|
if self.send_ticket {
|
|
emit_ticket(
|
|
&secrets,
|
|
&mut self.transcript,
|
|
self.using_ems,
|
|
cx,
|
|
&*self.config.ticketer,
|
|
)?;
|
|
}
|
|
emit_ccs(cx.common);
|
|
cx.common
|
|
.record_layer
|
|
.start_encrypting();
|
|
emit_finished(&secrets, &mut self.transcript, cx.common);
|
|
|
|
Ok(Box::new(ExpectCcs {
|
|
config: self.config,
|
|
secrets,
|
|
transcript: self.transcript,
|
|
session_id: self.session_id,
|
|
using_ems: self.using_ems,
|
|
resuming: true,
|
|
send_ticket: self.send_ticket,
|
|
}))
|
|
}
|
|
}
|
|
|
|
fn emit_server_hello(
|
|
config: &ServerConfig,
|
|
transcript: &mut HandshakeHash,
|
|
cx: &mut ServerContext<'_>,
|
|
session_id: SessionID,
|
|
suite: &'static Tls12CipherSuite,
|
|
using_ems: bool,
|
|
ocsp_response: &mut Option<&[u8]>,
|
|
sct_list: &mut Option<&[u8]>,
|
|
hello: &ClientHelloPayload,
|
|
resumedata: Option<&persist::ServerSessionValue>,
|
|
randoms: &ConnectionRandoms,
|
|
extra_exts: Vec<ServerExtension>,
|
|
) -> Result<bool, Error> {
|
|
let mut ep = hs::ExtensionProcessing::new();
|
|
ep.process_common(
|
|
config,
|
|
cx,
|
|
ocsp_response,
|
|
sct_list,
|
|
hello,
|
|
resumedata,
|
|
extra_exts,
|
|
)?;
|
|
ep.process_tls12(config, hello, using_ems);
|
|
|
|
let sh = Message {
|
|
version: ProtocolVersion::TLSv1_2,
|
|
payload: MessagePayload::handshake(HandshakeMessagePayload {
|
|
typ: HandshakeType::ServerHello,
|
|
payload: HandshakePayload::ServerHello(ServerHelloPayload {
|
|
legacy_version: ProtocolVersion::TLSv1_2,
|
|
random: Random::from(randoms.server),
|
|
session_id,
|
|
cipher_suite: suite.common.suite,
|
|
compression_method: Compression::Null,
|
|
extensions: ep.exts,
|
|
}),
|
|
}),
|
|
};
|
|
|
|
trace!("sending server hello {:?}", sh);
|
|
transcript.add_message(&sh);
|
|
cx.common.send_msg(sh, false);
|
|
Ok(ep.send_ticket)
|
|
}
|
|
|
|
fn emit_certificate(
|
|
transcript: &mut HandshakeHash,
|
|
common: &mut CommonState,
|
|
cert_chain: &[Certificate],
|
|
) {
|
|
let c = Message {
|
|
version: ProtocolVersion::TLSv1_2,
|
|
payload: MessagePayload::handshake(HandshakeMessagePayload {
|
|
typ: HandshakeType::Certificate,
|
|
payload: HandshakePayload::Certificate(cert_chain.to_owned()),
|
|
}),
|
|
};
|
|
|
|
transcript.add_message(&c);
|
|
common.send_msg(c, false);
|
|
}
|
|
|
|
fn emit_cert_status(transcript: &mut HandshakeHash, common: &mut CommonState, ocsp: &[u8]) {
|
|
let st = CertificateStatus::new(ocsp.to_owned());
|
|
|
|
let c = Message {
|
|
version: ProtocolVersion::TLSv1_2,
|
|
payload: MessagePayload::handshake(HandshakeMessagePayload {
|
|
typ: HandshakeType::CertificateStatus,
|
|
payload: HandshakePayload::CertificateStatus(st),
|
|
}),
|
|
};
|
|
|
|
transcript.add_message(&c);
|
|
common.send_msg(c, false);
|
|
}
|
|
|
|
fn emit_server_kx(
|
|
transcript: &mut HandshakeHash,
|
|
common: &mut CommonState,
|
|
sigschemes: Vec<SignatureScheme>,
|
|
skxg: &'static kx::SupportedKxGroup,
|
|
signing_key: &dyn sign::SigningKey,
|
|
randoms: &ConnectionRandoms,
|
|
) -> Result<kx::KeyExchange, Error> {
|
|
let kx = kx::KeyExchange::start(skxg).ok_or(Error::FailedToGetRandomBytes)?;
|
|
let secdh = ServerECDHParams::new(skxg.name, kx.pubkey.as_ref());
|
|
|
|
let mut msg = Vec::new();
|
|
msg.extend(randoms.client);
|
|
msg.extend(randoms.server);
|
|
secdh.encode(&mut msg);
|
|
|
|
let signer = signing_key
|
|
.choose_scheme(&sigschemes)
|
|
.ok_or_else(|| Error::General("incompatible signing key".to_string()))?;
|
|
let sigscheme = signer.scheme();
|
|
let sig = signer.sign(&msg)?;
|
|
|
|
let skx = ServerKeyExchangePayload::ECDHE(ECDHEServerKeyExchange {
|
|
params: secdh,
|
|
dss: DigitallySignedStruct::new(sigscheme, sig),
|
|
});
|
|
|
|
let m = Message {
|
|
version: ProtocolVersion::TLSv1_2,
|
|
payload: MessagePayload::handshake(HandshakeMessagePayload {
|
|
typ: HandshakeType::ServerKeyExchange,
|
|
payload: HandshakePayload::ServerKeyExchange(skx),
|
|
}),
|
|
};
|
|
|
|
transcript.add_message(&m);
|
|
common.send_msg(m, false);
|
|
Ok(kx)
|
|
}
|
|
|
|
fn emit_certificate_req(
|
|
config: &ServerConfig,
|
|
transcript: &mut HandshakeHash,
|
|
cx: &mut ServerContext<'_>,
|
|
) -> Result<bool, Error> {
|
|
let client_auth = &config.verifier;
|
|
|
|
if !client_auth.offer_client_auth() {
|
|
return Ok(false);
|
|
}
|
|
|
|
let verify_schemes = client_auth.supported_verify_schemes();
|
|
|
|
let names = config
|
|
.verifier
|
|
.client_auth_root_subjects()
|
|
.to_vec();
|
|
|
|
let cr = CertificateRequestPayload {
|
|
certtypes: vec![
|
|
ClientCertificateType::RSASign,
|
|
ClientCertificateType::ECDSASign,
|
|
],
|
|
sigschemes: verify_schemes,
|
|
canames: names,
|
|
};
|
|
|
|
let m = Message {
|
|
version: ProtocolVersion::TLSv1_2,
|
|
payload: MessagePayload::handshake(HandshakeMessagePayload {
|
|
typ: HandshakeType::CertificateRequest,
|
|
payload: HandshakePayload::CertificateRequest(cr),
|
|
}),
|
|
};
|
|
|
|
trace!("Sending CertificateRequest {:?}", m);
|
|
transcript.add_message(&m);
|
|
cx.common.send_msg(m, false);
|
|
Ok(true)
|
|
}
|
|
|
|
fn emit_server_hello_done(transcript: &mut HandshakeHash, common: &mut CommonState) {
|
|
let m = Message {
|
|
version: ProtocolVersion::TLSv1_2,
|
|
payload: MessagePayload::handshake(HandshakeMessagePayload {
|
|
typ: HandshakeType::ServerHelloDone,
|
|
payload: HandshakePayload::ServerHelloDone,
|
|
}),
|
|
};
|
|
|
|
transcript.add_message(&m);
|
|
common.send_msg(m, false);
|
|
}
|
|
}
|
|
|
|
// --- Process client's Certificate for client auth ---
|
|
struct ExpectCertificate {
|
|
config: Arc<ServerConfig>,
|
|
transcript: HandshakeHash,
|
|
randoms: ConnectionRandoms,
|
|
session_id: SessionID,
|
|
suite: &'static Tls12CipherSuite,
|
|
using_ems: bool,
|
|
server_kx: kx::KeyExchange,
|
|
send_ticket: bool,
|
|
}
|
|
|
|
impl State<ServerConnectionData> for ExpectCertificate {
|
|
fn handle(mut self: Box<Self>, cx: &mut ServerContext<'_>, m: Message) -> hs::NextStateOrError {
|
|
self.transcript.add_message(&m);
|
|
let cert_chain = require_handshake_msg_move!(
|
|
m,
|
|
HandshakeType::Certificate,
|
|
HandshakePayload::Certificate
|
|
)?;
|
|
|
|
// If we can't determine if the auth is mandatory, abort
|
|
let mandatory = self
|
|
.config
|
|
.verifier
|
|
.client_auth_mandatory();
|
|
|
|
trace!("certs {:?}", cert_chain);
|
|
|
|
let client_cert = match cert_chain.split_first() {
|
|
None if mandatory => {
|
|
cx.common
|
|
.send_fatal_alert(AlertDescription::CertificateRequired);
|
|
return Err(Error::NoCertificatesPresented);
|
|
}
|
|
None => {
|
|
debug!("client auth requested but no certificate supplied");
|
|
self.transcript.abandon_client_auth();
|
|
None
|
|
}
|
|
Some((end_entity, intermediates)) => {
|
|
let now = std::time::SystemTime::now();
|
|
self.config
|
|
.verifier
|
|
.verify_client_cert(end_entity, intermediates, now)
|
|
.map_err(|err| {
|
|
cx.common
|
|
.send_cert_verify_error_alert(err)
|
|
})?;
|
|
|
|
Some(cert_chain)
|
|
}
|
|
};
|
|
|
|
Ok(Box::new(ExpectClientKx {
|
|
config: self.config,
|
|
transcript: self.transcript,
|
|
randoms: self.randoms,
|
|
session_id: self.session_id,
|
|
suite: self.suite,
|
|
using_ems: self.using_ems,
|
|
server_kx: self.server_kx,
|
|
client_cert,
|
|
send_ticket: self.send_ticket,
|
|
}))
|
|
}
|
|
}
|
|
|
|
// --- Process client's KeyExchange ---
|
|
struct ExpectClientKx {
|
|
config: Arc<ServerConfig>,
|
|
transcript: HandshakeHash,
|
|
randoms: ConnectionRandoms,
|
|
session_id: SessionID,
|
|
suite: &'static Tls12CipherSuite,
|
|
using_ems: bool,
|
|
server_kx: kx::KeyExchange,
|
|
client_cert: Option<Vec<Certificate>>,
|
|
send_ticket: bool,
|
|
}
|
|
|
|
impl State<ServerConnectionData> for ExpectClientKx {
|
|
fn handle(mut self: Box<Self>, cx: &mut ServerContext<'_>, m: Message) -> hs::NextStateOrError {
|
|
let client_kx = require_handshake_msg!(
|
|
m,
|
|
HandshakeType::ClientKeyExchange,
|
|
HandshakePayload::ClientKeyExchange
|
|
)?;
|
|
self.transcript.add_message(&m);
|
|
let ems_seed = self
|
|
.using_ems
|
|
.then(|| self.transcript.get_current_hash());
|
|
|
|
// Complete key agreement, and set up encryption with the
|
|
// resulting premaster secret.
|
|
let peer_kx_params =
|
|
tls12::decode_ecdh_params::<ClientECDHParams>(cx.common, &client_kx.0)?;
|
|
let secrets = ConnectionSecrets::from_key_exchange(
|
|
self.server_kx,
|
|
&peer_kx_params.public.0,
|
|
ems_seed,
|
|
self.randoms,
|
|
self.suite,
|
|
)?;
|
|
|
|
self.config.key_log.log(
|
|
"CLIENT_RANDOM",
|
|
&secrets.randoms.client,
|
|
&secrets.master_secret,
|
|
);
|
|
cx.common
|
|
.start_encryption_tls12(&secrets, Side::Server);
|
|
|
|
if let Some(client_cert) = self.client_cert {
|
|
Ok(Box::new(ExpectCertificateVerify {
|
|
config: self.config,
|
|
secrets,
|
|
transcript: self.transcript,
|
|
session_id: self.session_id,
|
|
using_ems: self.using_ems,
|
|
client_cert,
|
|
send_ticket: self.send_ticket,
|
|
}))
|
|
} else {
|
|
Ok(Box::new(ExpectCcs {
|
|
config: self.config,
|
|
secrets,
|
|
transcript: self.transcript,
|
|
session_id: self.session_id,
|
|
using_ems: self.using_ems,
|
|
resuming: false,
|
|
send_ticket: self.send_ticket,
|
|
}))
|
|
}
|
|
}
|
|
}
|
|
|
|
// --- Process client's certificate proof ---
|
|
struct ExpectCertificateVerify {
|
|
config: Arc<ServerConfig>,
|
|
secrets: ConnectionSecrets,
|
|
transcript: HandshakeHash,
|
|
session_id: SessionID,
|
|
using_ems: bool,
|
|
client_cert: Vec<Certificate>,
|
|
send_ticket: bool,
|
|
}
|
|
|
|
impl State<ServerConnectionData> for ExpectCertificateVerify {
|
|
fn handle(mut self: Box<Self>, cx: &mut ServerContext<'_>, m: Message) -> hs::NextStateOrError {
|
|
let rc = {
|
|
let sig = require_handshake_msg!(
|
|
m,
|
|
HandshakeType::CertificateVerify,
|
|
HandshakePayload::CertificateVerify
|
|
)?;
|
|
|
|
match self.transcript.take_handshake_buf() {
|
|
Some(msgs) => {
|
|
let certs = &self.client_cert;
|
|
self.config
|
|
.verifier
|
|
.verify_tls12_signature(&msgs, &certs[0], sig)
|
|
}
|
|
None => {
|
|
// This should be unreachable; the handshake buffer was initialized with
|
|
// client authentication if the verifier wants to offer it.
|
|
// `transcript.abandon_client_auth()` can extract it, but its only caller in
|
|
// this flow will also set `ExpectClientKx::client_cert` to `None`, making it
|
|
// impossible to reach this state.
|
|
cx.common
|
|
.send_fatal_alert(AlertDescription::AccessDenied);
|
|
return Err(Error::General("client authentication not set up".into()));
|
|
}
|
|
}
|
|
};
|
|
|
|
if let Err(e) = rc {
|
|
return Err(cx
|
|
.common
|
|
.send_cert_verify_error_alert(e));
|
|
}
|
|
|
|
trace!("client CertificateVerify OK");
|
|
cx.common.peer_certificates = Some(self.client_cert);
|
|
|
|
self.transcript.add_message(&m);
|
|
Ok(Box::new(ExpectCcs {
|
|
config: self.config,
|
|
secrets: self.secrets,
|
|
transcript: self.transcript,
|
|
session_id: self.session_id,
|
|
using_ems: self.using_ems,
|
|
resuming: false,
|
|
send_ticket: self.send_ticket,
|
|
}))
|
|
}
|
|
}
|
|
|
|
// --- Process client's ChangeCipherSpec ---
|
|
struct ExpectCcs {
|
|
config: Arc<ServerConfig>,
|
|
secrets: ConnectionSecrets,
|
|
transcript: HandshakeHash,
|
|
session_id: SessionID,
|
|
using_ems: bool,
|
|
resuming: bool,
|
|
send_ticket: bool,
|
|
}
|
|
|
|
impl State<ServerConnectionData> for ExpectCcs {
|
|
fn handle(self: Box<Self>, cx: &mut ServerContext<'_>, m: Message) -> hs::NextStateOrError {
|
|
match m.payload {
|
|
MessagePayload::ChangeCipherSpec(..) => {}
|
|
payload => {
|
|
return Err(inappropriate_message(
|
|
&payload,
|
|
&[ContentType::ChangeCipherSpec],
|
|
))
|
|
}
|
|
}
|
|
|
|
// CCS should not be received interleaved with fragmented handshake-level
|
|
// message.
|
|
cx.common.check_aligned_handshake()?;
|
|
|
|
cx.common
|
|
.record_layer
|
|
.start_decrypting();
|
|
Ok(Box::new(ExpectFinished {
|
|
config: self.config,
|
|
secrets: self.secrets,
|
|
transcript: self.transcript,
|
|
session_id: self.session_id,
|
|
using_ems: self.using_ems,
|
|
resuming: self.resuming,
|
|
send_ticket: self.send_ticket,
|
|
}))
|
|
}
|
|
}
|
|
|
|
// --- Process client's Finished ---
|
|
fn get_server_connection_value_tls12(
|
|
secrets: &ConnectionSecrets,
|
|
using_ems: bool,
|
|
cx: &ServerContext<'_>,
|
|
time_now: ticketer::TimeBase,
|
|
) -> persist::ServerSessionValue {
|
|
let version = ProtocolVersion::TLSv1_2;
|
|
let secret = secrets.get_master_secret();
|
|
|
|
let mut v = persist::ServerSessionValue::new(
|
|
cx.data.sni.as_ref(),
|
|
version,
|
|
secrets.suite().common.suite,
|
|
secret,
|
|
cx.common.peer_certificates.clone(),
|
|
cx.common.alpn_protocol.clone(),
|
|
cx.data.resumption_data.clone(),
|
|
time_now,
|
|
0,
|
|
);
|
|
|
|
if using_ems {
|
|
v.set_extended_ms_used();
|
|
}
|
|
|
|
v
|
|
}
|
|
|
|
fn emit_ticket(
|
|
secrets: &ConnectionSecrets,
|
|
transcript: &mut HandshakeHash,
|
|
using_ems: bool,
|
|
cx: &mut ServerContext<'_>,
|
|
ticketer: &dyn ProducesTickets,
|
|
) -> Result<(), Error> {
|
|
let time_now = ticketer::TimeBase::now()?;
|
|
let plain = get_server_connection_value_tls12(secrets, using_ems, cx, time_now).get_encoding();
|
|
|
|
// If we can't produce a ticket for some reason, we can't
|
|
// report an error. Send an empty one.
|
|
let ticket = ticketer
|
|
.encrypt(&plain)
|
|
.unwrap_or_default();
|
|
let ticket_lifetime = ticketer.lifetime();
|
|
|
|
let m = Message {
|
|
version: ProtocolVersion::TLSv1_2,
|
|
payload: MessagePayload::handshake(HandshakeMessagePayload {
|
|
typ: HandshakeType::NewSessionTicket,
|
|
payload: HandshakePayload::NewSessionTicket(NewSessionTicketPayload::new(
|
|
ticket_lifetime,
|
|
ticket,
|
|
)),
|
|
}),
|
|
};
|
|
|
|
transcript.add_message(&m);
|
|
cx.common.send_msg(m, false);
|
|
Ok(())
|
|
}
|
|
|
|
fn emit_ccs(common: &mut CommonState) {
|
|
let m = Message {
|
|
version: ProtocolVersion::TLSv1_2,
|
|
payload: MessagePayload::ChangeCipherSpec(ChangeCipherSpecPayload {}),
|
|
};
|
|
|
|
common.send_msg(m, false);
|
|
}
|
|
|
|
fn emit_finished(
|
|
secrets: &ConnectionSecrets,
|
|
transcript: &mut HandshakeHash,
|
|
common: &mut CommonState,
|
|
) {
|
|
let vh = transcript.get_current_hash();
|
|
let verify_data = secrets.server_verify_data(&vh);
|
|
let verify_data_payload = Payload::new(verify_data);
|
|
|
|
let f = Message {
|
|
version: ProtocolVersion::TLSv1_2,
|
|
payload: MessagePayload::handshake(HandshakeMessagePayload {
|
|
typ: HandshakeType::Finished,
|
|
payload: HandshakePayload::Finished(verify_data_payload),
|
|
}),
|
|
};
|
|
|
|
transcript.add_message(&f);
|
|
common.send_msg(f, true);
|
|
}
|
|
|
|
struct ExpectFinished {
|
|
config: Arc<ServerConfig>,
|
|
secrets: ConnectionSecrets,
|
|
transcript: HandshakeHash,
|
|
session_id: SessionID,
|
|
using_ems: bool,
|
|
resuming: bool,
|
|
send_ticket: bool,
|
|
}
|
|
|
|
impl State<ServerConnectionData> for ExpectFinished {
|
|
fn handle(mut self: Box<Self>, cx: &mut ServerContext<'_>, m: Message) -> hs::NextStateOrError {
|
|
let finished =
|
|
require_handshake_msg!(m, HandshakeType::Finished, HandshakePayload::Finished)?;
|
|
|
|
cx.common.check_aligned_handshake()?;
|
|
|
|
let vh = self.transcript.get_current_hash();
|
|
let expect_verify_data = self.secrets.client_verify_data(&vh);
|
|
|
|
let _fin_verified =
|
|
constant_time::verify_slices_are_equal(&expect_verify_data, &finished.0)
|
|
.map_err(|_| {
|
|
cx.common
|
|
.send_fatal_alert(AlertDescription::DecryptError);
|
|
Error::DecryptError
|
|
})
|
|
.map(|_| verify::FinishedMessageVerified::assertion())?;
|
|
|
|
// Save connection, perhaps
|
|
if !self.resuming && !self.session_id.is_empty() {
|
|
let time_now = ticketer::TimeBase::now()?;
|
|
let value =
|
|
get_server_connection_value_tls12(&self.secrets, self.using_ems, cx, time_now);
|
|
|
|
let worked = self
|
|
.config
|
|
.session_storage
|
|
.put(self.session_id.get_encoding(), value.get_encoding());
|
|
if worked {
|
|
debug!("Session saved");
|
|
} else {
|
|
debug!("Session not saved");
|
|
}
|
|
}
|
|
|
|
// Send our CCS and Finished.
|
|
self.transcript.add_message(&m);
|
|
if !self.resuming {
|
|
if self.send_ticket {
|
|
emit_ticket(
|
|
&self.secrets,
|
|
&mut self.transcript,
|
|
self.using_ems,
|
|
cx,
|
|
&*self.config.ticketer,
|
|
)?;
|
|
}
|
|
emit_ccs(cx.common);
|
|
cx.common
|
|
.record_layer
|
|
.start_encrypting();
|
|
emit_finished(&self.secrets, &mut self.transcript, cx.common);
|
|
}
|
|
|
|
cx.common.start_traffic();
|
|
Ok(Box::new(ExpectTraffic {
|
|
secrets: self.secrets,
|
|
_fin_verified,
|
|
}))
|
|
}
|
|
}
|
|
|
|
// --- Process traffic ---
|
|
struct ExpectTraffic {
|
|
secrets: ConnectionSecrets,
|
|
_fin_verified: verify::FinishedMessageVerified,
|
|
}
|
|
|
|
impl ExpectTraffic {}
|
|
|
|
impl State<ServerConnectionData> for ExpectTraffic {
|
|
fn handle(self: Box<Self>, cx: &mut ServerContext<'_>, m: Message) -> hs::NextStateOrError {
|
|
match m.payload {
|
|
MessagePayload::ApplicationData(payload) => cx
|
|
.common
|
|
.take_received_plaintext(payload),
|
|
payload => {
|
|
return Err(inappropriate_message(
|
|
&payload,
|
|
&[ContentType::ApplicationData],
|
|
));
|
|
}
|
|
}
|
|
Ok(self)
|
|
}
|
|
|
|
fn export_keying_material(
|
|
&self,
|
|
output: &mut [u8],
|
|
label: &[u8],
|
|
context: Option<&[u8]>,
|
|
) -> Result<(), Error> {
|
|
self.secrets
|
|
.export_keying_material(output, label, context);
|
|
Ok(())
|
|
}
|
|
|
|
#[cfg(feature = "secret_extraction")]
|
|
fn extract_secrets(&self) -> Result<PartiallyExtractedSecrets, Error> {
|
|
self.secrets
|
|
.extract_secrets(Side::Server)
|
|
}
|
|
}
|