#![allow(dead_code, unreachable_pub)]
use super::super::codec::{ParsedRecord, is_legal_record_version, read_record, write_record};
use super::client::{ClientCertConfig, ClientKey};
use crate::ct::ConstantTimeEq;
use crate::ec::x25519::X25519PrivateKey;
use crate::ec::{BoxedEcdhPrivateKey, BoxedEcdsaPublicKey, CurveId};
use crate::hash::{Sha256, Sha384, Sha512};
use crate::rng::RngCore;
use crate::signature_registry::SignaturePolicy;
use crate::tls::codec::extension as ext;
use crate::tls::codec::handshake12::{
CertificateRequest12, ClientKeyExchange, NewSessionTicket12, ServerHelloDone,
ServerKeyExchange, signed_message,
};
use crate::tls::codec::{
CipherSuite, ClientHello, ExtensionType, NamedGroup, Random, ReadCursor, ServerHello, hs_type,
read_handshake, with_len_u16, with_len_u24,
};
use crate::tls::crypto::HashAlg;
use crate::tls::crypto::aead12::RecordCrypter12;
use crate::tls::crypto::prf::{
extended_master_secret, finished_verify_data, key_block, master_secret, tls12_exporter,
};
use crate::tls::crypto::{AeadAlg, Transcript, verify_signature};
use crate::tls::keylog::KeyLog;
use crate::tls::pki::{CrlStore, RootCertStore, verify_chain_with_crls, verify_hostname};
use crate::tls::{Alert, AlertDescription, ContentType, Error, ProtocolVersion};
use crate::x509::{AnyPublicKey, Certificate, Time};
use alloc::string::String;
use alloc::sync::Arc;
use alloc::vec::Vec;
pub(crate) struct ClientConfig12 {
pub roots: RootCertStore,
pub verify_certificates: bool,
pub verification_time: Option<Time>,
pub alpn_protocols: Vec<Vec<u8>>,
pub record_size_limit: Option<u16>,
pub signature_policy: SignaturePolicy,
pub client_cert: Option<ClientCertConfig>,
pub session: Option<StoredSession12>,
pub send_fallback_scsv: bool,
pub accept_downgrade_sentinel: bool,
pub crls: CrlStore,
pub key_log: Option<Arc<dyn KeyLog>>,
pub require_ems: bool,
}
impl ClientConfig12 {
pub fn new(roots: RootCertStore) -> Self {
ClientConfig12 {
roots,
verify_certificates: true,
verification_time: None,
alpn_protocols: Vec::new(),
record_size_limit: None,
signature_policy: SignaturePolicy::modern(),
client_cert: None,
session: None,
send_fallback_scsv: false,
accept_downgrade_sentinel: true,
crls: CrlStore::new(),
key_log: None,
require_ems: true,
}
}
pub fn with_require_ems(mut self, required: bool) -> Self {
self.require_ems = required;
self
}
pub fn with_crls(mut self, crls: CrlStore) -> Self {
self.crls = crls;
self
}
pub fn with_alpn(mut self, protocols: Vec<Vec<u8>>) -> Self {
self.alpn_protocols = protocols;
self
}
pub fn with_record_size_limit(mut self, limit: u16) -> Self {
self.record_size_limit = Some(limit);
self
}
pub fn with_signature_policy(mut self, policy: SignaturePolicy) -> Self {
self.signature_policy = policy;
self
}
pub fn with_verification_time(mut self, t: Time) -> Self {
self.verification_time = Some(t);
self
}
pub fn with_client_cert(mut self, cert: ClientCertConfig) -> Self {
self.client_cert = Some(cert);
self
}
pub fn with_session(mut self, session: StoredSession12) -> Self {
self.session = Some(session);
self
}
pub fn with_fallback_scsv(mut self, enabled: bool) -> Self {
self.send_fallback_scsv = enabled;
self
}
pub fn with_accept_downgrade_sentinel(mut self, enabled: bool) -> Self {
self.accept_downgrade_sentinel = enabled;
self
}
}
pub(crate) const DOWNGRADE_SENTINEL_TLS12: [u8; 8] =
[0x44, 0x4F, 0x57, 0x4E, 0x47, 0x52, 0x44, 0x01];
pub(crate) const DOWNGRADE_SENTINEL_TLS11_OR_BELOW: [u8; 8] =
[0x44, 0x4F, 0x57, 0x4E, 0x47, 0x52, 0x44, 0x00];
pub(crate) const TLS_FALLBACK_SCSV: u16 = 0x5600;
#[derive(Clone, Debug)]
pub struct StoredSession12 {
pub ticket: Vec<u8>,
pub master_secret: [u8; 48],
pub cipher_suite: u16,
pub alpn: Option<Vec<u8>>,
pub received_at: Option<Time>,
pub ems_used: bool,
}
#[derive(Copy, Clone, PartialEq, Eq)]
pub(crate) enum SigKind {
Rsa,
Ecdsa,
}
#[derive(Copy, Clone)]
pub(crate) struct SuiteParams12 {
pub(crate) suite: CipherSuite,
pub(crate) hash: HashAlg,
pub(crate) aead: AeadAlg,
pub(crate) key_len: usize,
pub(crate) sig_kind: SigKind,
}
pub(crate) const SUITES_12: [SuiteParams12; 6] = [
SuiteParams12 {
suite: CipherSuite::TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
hash: HashAlg::Sha256,
aead: AeadAlg::Aes128Gcm,
key_len: 16,
sig_kind: SigKind::Ecdsa,
},
SuiteParams12 {
suite: CipherSuite::TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
hash: HashAlg::Sha256,
aead: AeadAlg::Aes128Gcm,
key_len: 16,
sig_kind: SigKind::Rsa,
},
SuiteParams12 {
suite: CipherSuite::TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256,
hash: HashAlg::Sha256,
aead: AeadAlg::ChaCha20Poly1305,
key_len: 32,
sig_kind: SigKind::Ecdsa,
},
SuiteParams12 {
suite: CipherSuite::TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256,
hash: HashAlg::Sha256,
aead: AeadAlg::ChaCha20Poly1305,
key_len: 32,
sig_kind: SigKind::Rsa,
},
SuiteParams12 {
suite: CipherSuite::TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
hash: HashAlg::Sha384,
aead: AeadAlg::Aes256Gcm,
key_len: 32,
sig_kind: SigKind::Ecdsa,
},
SuiteParams12 {
suite: CipherSuite::TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
hash: HashAlg::Sha384,
aead: AeadAlg::Aes256Gcm,
key_len: 32,
sig_kind: SigKind::Rsa,
},
];
pub(crate) fn lookup_suite_12(s: CipherSuite) -> Option<SuiteParams12> {
SUITES_12.iter().copied().find(|p| p.suite == s)
}
fn key_matches_sig_kind(key: &AnyPublicKey, kind: SigKind) -> bool {
match (key, kind) {
(AnyPublicKey::Rsa(_), SigKind::Rsa) => true,
(AnyPublicKey::Ecdsa(_), SigKind::Ecdsa) => true,
_ => false,
}
}
#[derive(PartialEq, Eq, Debug)]
enum State {
WaitServerHello,
WaitCertificate,
WaitCertificateStatus,
WaitServerKeyExchange,
WaitServerHelloDone,
WaitServerFinished,
WaitResumedServerFinished,
Connected,
Closed,
}
#[cfg(feature = "std")]
fn system_now() -> Option<Time> {
use std::time::{SystemTime, UNIX_EPOCH};
SystemTime::now()
.duration_since(UNIX_EPOCH)
.ok()
.map(|d| Time::from_unix(d.as_secs()))
}
#[cfg(not(feature = "std"))]
fn system_now() -> Option<Time> {
None
}
pub struct ClientConnection12 {
config: ClientConfig12,
server_name: String,
state: State,
inbuf: Vec<u8>,
outbuf: Vec<u8>,
hs_pending: Vec<u8>,
app_in: Vec<u8>,
transcript: Transcript,
ccs_window_open: bool,
ccs_received: bool,
x25519: X25519PrivateKey,
p256: BoxedEcdhPrivateKey,
p384: BoxedEcdhPrivateKey,
client_random: Random,
server_random: Option<Random>,
offered_suites: Vec<CipherSuite>,
offered_groups: Vec<NamedGroup>,
suite: Option<SuiteParams12>,
cert_chain: Vec<Vec<u8>>,
leaf_key: Option<AnyPublicKey>,
peer_share: Option<(NamedGroup, Vec<u8>)>,
alpn_negotiated: Option<Vec<u8>>,
master: Option<[u8; 48]>,
client_crypter: Option<RecordCrypter12>,
server_crypter: Option<RecordCrypter12>,
pending_server_crypter: Option<RecordCrypter12>,
cert_request_received: bool,
received_ticket: Option<Vec<u8>>,
#[allow(dead_code)]
received_ticket_lifetime: u32,
resumed: bool,
pub(crate) ems_offered: bool,
pub(crate) ems_negotiated: bool,
ems_session_hash: Option<Vec<u8>>,
server_echoed_ocsp_staple: bool,
peer_ocsp_response: Option<Vec<u8>>,
}
impl ClientConnection12 {
pub fn new<R: RngCore>(config: ClientConfig12, server_name: &str, rng: &mut R) -> Self {
Self::new_with_offer(
config,
server_name,
rng,
&SUITES_12.iter().map(|p| p.suite).collect::<Vec<_>>(),
&[
NamedGroup::X25519,
NamedGroup::SECP256R1,
NamedGroup::SECP384R1,
],
)
}
pub(crate) fn new_with_offer<R: RngCore>(
config: ClientConfig12,
server_name: &str,
rng: &mut R,
suites: &[CipherSuite],
groups: &[NamedGroup],
) -> Self {
let x25519 = X25519PrivateKey::generate(rng);
let p256 = BoxedEcdhPrivateKey::generate(CurveId::P256, rng);
let p384 = BoxedEcdhPrivateKey::generate(CurveId::P384, rng);
let mut random: Random = [0u8; 32];
rng.fill_bytes(&mut random);
let offered_suites: Vec<CipherSuite> = suites
.iter()
.copied()
.filter(|s| lookup_suite_12(*s).is_some())
.collect();
let mut conn = ClientConnection12 {
config,
server_name: String::from(server_name),
state: State::WaitServerHello,
inbuf: Vec::new(),
outbuf: Vec::new(),
hs_pending: Vec::new(),
app_in: Vec::new(),
transcript: Transcript::new(),
ccs_window_open: true,
ccs_received: false,
x25519,
p256,
p384,
client_random: random,
server_random: None,
offered_suites: offered_suites.clone(),
offered_groups: groups.to_vec(),
suite: None,
cert_chain: Vec::new(),
leaf_key: None,
peer_share: None,
alpn_negotiated: None,
master: None,
client_crypter: None,
server_crypter: None,
pending_server_crypter: None,
cert_request_received: false,
received_ticket: None,
received_ticket_lifetime: 0,
resumed: false,
ems_offered: true,
ems_negotiated: false,
ems_session_hash: None,
server_echoed_ocsp_staple: false,
peer_ocsp_response: None,
};
let hello = conn.build_client_hello(&offered_suites, groups);
conn.transcript.update(&hello);
conn.write_plain_record(ContentType::Handshake, &hello);
conn
}
fn build_client_hello(&self, suites: &[CipherSuite], groups: &[NamedGroup]) -> Vec<u8> {
let mut extensions = alloc::vec![
ext::server_name(&self.server_name),
ext::supported_groups_list(groups),
ext::signature_algorithms(),
ext::ec_point_formats(),
ext::renegotiation_info_empty(),
ext::extended_master_secret_empty(),
];
if !self.config.alpn_protocols.is_empty() {
let protos: Vec<&[u8]> = self
.config
.alpn_protocols
.iter()
.map(|v| v.as_slice())
.collect();
extensions.push(ext::alpn_protocols(&protos));
}
if let Some(limit) = self.config.record_size_limit {
extensions.push(ext::record_size_limit(limit));
}
extensions.push(ext::status_request_ocsp());
let ticket_bytes: &[u8] = self
.config
.session
.as_ref()
.map(|s| s.ticket.as_slice())
.unwrap_or(&[]);
extensions.push(ext::session_ticket(ticket_bytes));
let mut cipher_suites_wire: Vec<CipherSuite> = Vec::with_capacity(suites.len() + 1);
if self.config.send_fallback_scsv {
cipher_suites_wire.push(CipherSuite(TLS_FALLBACK_SCSV));
}
cipher_suites_wire.extend_from_slice(suites);
ClientHello {
legacy_version: 0x0303,
random: self.client_random,
session_id: Vec::new(),
cipher_suites: cipher_suites_wire,
extensions,
}
.encode()
}
pub fn negotiated_cipher_suite(&self) -> Option<u16> {
self.suite.map(|s| s.suite.0)
}
pub fn protocol_version(&self) -> Option<&'static str> {
self.suite.map(|_| "TLSv1.2")
}
pub fn peer_certificates(&self) -> &[Vec<u8>] {
&self.cert_chain
}
pub fn alpn_protocol(&self) -> Option<&[u8]> {
self.alpn_negotiated.as_deref()
}
pub fn client_random(&self) -> [u8; 32] {
self.client_random
}
pub fn master_secret(&self) -> Option<[u8; 48]> {
self.master
}
pub fn take_session(&mut self) -> Option<StoredSession12> {
let ticket = self.received_ticket.take()?;
let suite = self.suite?;
let master = self.master?;
Some(StoredSession12 {
ticket,
master_secret: master,
cipher_suite: suite.suite.0,
alpn: self.alpn_negotiated.clone(),
received_at: self.config.verification_time.clone().or_else(system_now),
ems_used: self.ems_negotiated,
})
}
pub fn did_resume(&self) -> bool {
self.resumed
}
pub fn peer_ocsp_response(&self) -> Option<&[u8]> {
self.peer_ocsp_response.as_deref()
}
pub fn ems_negotiated(&self) -> bool {
self.ems_negotiated
}
pub fn tls_exporter(
&self,
label: &[u8],
context: Option<&[u8]>,
out: &mut [u8],
) -> Result<(), Error> {
let master = self.master.as_ref().ok_or(Error::InappropriateState)?;
let suite = self.suite.ok_or(Error::InappropriateState)?;
let sr = self.server_random.ok_or(Error::InappropriateState)?;
tls12_exporter(
suite.hash,
master,
label,
&self.client_random,
&sr,
context,
out,
);
Ok(())
}
pub fn read_tls(&mut self, bytes: &[u8]) {
self.inbuf.extend_from_slice(bytes);
}
pub fn write_tls(&mut self) -> Vec<u8> {
core::mem::take(&mut self.outbuf)
}
pub fn wants_write(&self) -> bool {
!self.outbuf.is_empty()
}
pub fn is_handshaking(&self) -> bool {
!matches!(self.state, State::Connected | State::Closed)
}
pub fn send_application_data(&mut self, data: &[u8]) -> Result<(), Error> {
if self.state != State::Connected {
return Err(Error::InappropriateState);
}
const CAP: usize = 1 << 14;
if data.len() <= CAP {
self.emit_encrypted(ContentType::ApplicationData, data)?;
} else {
for chunk in data.chunks(CAP) {
self.emit_encrypted(ContentType::ApplicationData, chunk)?;
}
}
Ok(())
}
pub fn take_received_plaintext(&mut self) -> Vec<u8> {
core::mem::take(&mut self.app_in)
}
pub fn send_close_notify(&mut self) {
let body = [1u8, AlertDescription::CloseNotify.as_u8()]; let _ = self.emit_alert(&body);
}
pub fn process_new_packets(&mut self) -> Result<(), Error> {
loop {
match self.next_message() {
Ok(Some(Incoming::Handshake(msg))) => {
if let Err(e) = self.handle_handshake(msg) {
self.fail(&e);
return Err(e);
}
}
Ok(Some(Incoming::ApplicationData)) => {
if self.state != State::Connected {
let e = Error::UnexpectedMessage;
self.fail(&e);
return Err(e);
}
}
Ok(Some(Incoming::Alert(alert))) => {
match alert.description {
AlertDescription::CloseNotify => {
self.state = State::Closed;
return Ok(());
}
AlertDescription::UserCanceled | AlertDescription::NoRenegotiation => {
continue;
}
_ => return Err(Error::AlertReceived(alert.description)),
}
}
Ok(None) => return Ok(()),
Err(e) => {
self.fail(&e);
return Err(e);
}
}
}
}
fn fail(&mut self, error: &Error) {
let body = [2u8, alert_for(error).as_u8()]; let _ = self.emit_alert(&body);
self.state = State::Closed;
}
fn write_plain_record(&mut self, ct: ContentType, payload: &[u8]) {
write_record(&mut self.outbuf, ct, ProtocolVersion::TLSv1_2, payload);
}
fn emit_encrypted(&mut self, ct: ContentType, payload: &[u8]) -> Result<(), Error> {
let crypter = self
.client_crypter
.as_mut()
.ok_or(Error::InappropriateState)?;
let fragment = crypter.encrypt(ct, payload)?;
write_record(&mut self.outbuf, ct, ProtocolVersion::TLSv1_2, &fragment);
Ok(())
}
fn emit_alert(&mut self, body: &[u8; 2]) -> Result<(), Error> {
if self.client_crypter.is_some() {
self.emit_encrypted(ContentType::Alert, body)
} else {
self.write_plain_record(ContentType::Alert, body);
Ok(())
}
}
fn next_message(&mut self) -> Result<Option<Incoming>, Error> {
loop {
if let Some(msg) = self.pop_handshake() {
return Ok(Some(Incoming::Handshake(msg)));
}
let Some(ParsedRecord {
content_type,
version,
fragment,
len,
}) = read_record(&self.inbuf)?
else {
return Ok(None);
};
if !is_legal_record_version(version) {
return Err(Error::UnsupportedVersion);
}
let mut header = [0u8; 5];
header.copy_from_slice(&self.inbuf[..5]);
let fragment = fragment.to_vec();
self.inbuf.drain(..len);
match content_type {
ContentType::ChangeCipherSpec => {
if !self.ccs_window_open || fragment.as_slice() != [0x01] {
return Err(Error::UnexpectedMessage);
}
if self.ccs_received {
return Err(Error::UnexpectedMessage);
}
match self.state {
State::WaitServerFinished | State::WaitResumedServerFinished => {
let crypter = self
.pending_server_crypter
.take()
.ok_or(Error::UnexpectedMessage)?;
self.server_crypter = Some(crypter);
}
_ => return Err(Error::UnexpectedMessage),
}
self.ccs_received = true;
continue;
}
ContentType::Handshake => {
if let Some(c) = self.server_crypter.as_mut() {
let (_ct, plain) = c.decrypt(&header, &fragment)?;
if plain.is_empty() {
return Err(Error::UnexpectedMessage);
}
self.hs_pending.extend_from_slice(&plain);
} else {
self.hs_pending.extend_from_slice(&fragment);
}
}
ContentType::ApplicationData => {
let c = self
.server_crypter
.as_mut()
.ok_or(Error::UnexpectedMessage)?;
let (_ct, plain) = c.decrypt(&header, &fragment)?;
self.app_in.extend_from_slice(&plain);
return Ok(Some(Incoming::ApplicationData));
}
ContentType::Alert => {
let payload: Vec<u8> = if let Some(c) = self.server_crypter.as_mut() {
let (_ct, plain) = c.decrypt(&header, &fragment)?;
plain
} else {
fragment
};
return Ok(Some(parse_alert(&payload)?));
}
_ => return Err(Error::UnexpectedMessage),
}
}
}
fn pop_handshake(&mut self) -> Option<Vec<u8>> {
if self.hs_pending.len() < 4 {
return None;
}
let len = ((self.hs_pending[1] as usize) << 16)
| ((self.hs_pending[2] as usize) << 8)
| self.hs_pending[3] as usize;
let total = 4 + len;
if self.hs_pending.len() < total {
return None;
}
Some(self.hs_pending.drain(..total).collect())
}
fn handle_handshake(&mut self, msg: Vec<u8>) -> Result<(), Error> {
let mut c = ReadCursor::new(&msg);
let (msg_type, body) = read_handshake(&mut c)?;
if msg_type == hs_type::HELLO_REQUEST {
return Err(Error::UnexpectedMessage);
}
match self.state {
State::WaitServerHello => self.on_server_hello(msg_type, body, &msg),
State::WaitCertificate => self.on_certificate(msg_type, body, &msg),
State::WaitCertificateStatus => self.on_certificate_status(msg_type, body, &msg),
State::WaitServerKeyExchange => self.on_server_key_exchange(msg_type, body, &msg),
State::WaitServerHelloDone => self.on_server_hello_done(msg_type, body, &msg),
State::WaitServerFinished | State::WaitResumedServerFinished => {
self.on_server_finished(msg_type, body, &msg)
}
State::Connected => Err(Error::UnexpectedMessage),
State::Closed => Err(Error::UnexpectedMessage),
}
}
fn on_server_hello(&mut self, msg_type: u8, body: &[u8], raw: &[u8]) -> Result<(), Error> {
if msg_type != hs_type::SERVER_HELLO {
return Err(Error::UnexpectedMessage);
}
let sh = ServerHello::decode(body)?;
if ext::find(&sh.extensions, ExtensionType::SUPPORTED_VERSIONS).is_some() {
return Err(Error::UnsupportedVersion);
}
let tail: &[u8] = &sh.random[24..];
let sentinel_seen =
tail == DOWNGRADE_SENTINEL_TLS12 || tail == DOWNGRADE_SENTINEL_TLS11_OR_BELOW;
if sentinel_seen && !self.config.accept_downgrade_sentinel {
return Err(Error::IllegalParameter);
}
if !self.offered_suites.contains(&sh.cipher_suite) {
return Err(Error::HandshakeFailure);
}
let suite = lookup_suite_12(sh.cipher_suite).ok_or(Error::HandshakeFailure)?;
let reneg = ext::find(&sh.extensions, ExtensionType::RENEGOTIATION_INFO)
.ok_or(Error::HandshakeFailure)?;
let inner = ext::parse_renegotiation_info(reneg)?;
if !inner.is_empty() {
return Err(Error::HandshakeFailure);
}
if let Some(alpn_body) = ext::find(&sh.extensions, ExtensionType::ALPN) {
let names = ext::parse_alpn(alpn_body)?;
if names.len() != 1 {
return Err(Error::IllegalParameter);
}
if !self.config.alpn_protocols.iter().any(|p| p == &names[0]) {
return Err(Error::IllegalParameter);
}
self.alpn_negotiated = Some(names.into_iter().next().unwrap());
}
if let Some(rsl) = ext::find(&sh.extensions, ExtensionType::RECORD_SIZE_LIMIT) {
let _limit = ext::parse_record_size_limit(rsl)?;
}
if let Some(sr_body) = ext::find(&sh.extensions, ExtensionType::STATUS_REQUEST) {
ext::parse_status_request_sh_ack(sr_body)?;
self.server_echoed_ocsp_staple = true;
}
let server_will_issue_ticket =
ext::find(&sh.extensions, ExtensionType::SESSION_TICKET).is_some();
if let Some(ems_body) = ext::find(&sh.extensions, ExtensionType::EXTENDED_MASTER_SECRET) {
ext::parse_extended_master_secret(ems_body)?;
if !self.ems_offered {
return Err(Error::IllegalParameter);
}
self.ems_negotiated = true;
} else {
self.ems_negotiated = false;
if self.config.require_ems && self.ems_offered {
return Err(Error::HandshakeFailure);
}
}
self.transcript.set_alg(suite.hash);
self.transcript.update(raw);
self.suite = Some(suite);
self.server_random = Some(sh.random);
let resume = self
.config
.session
.as_ref()
.filter(|s| s.cipher_suite == sh.cipher_suite.0)
.filter(|_| !server_will_issue_ticket)
.cloned();
if let Some(stored) = resume {
if stored.ems_used != self.ems_negotiated {
return Err(Error::IllegalParameter);
}
let cr = self.client_random;
let sr = sh.random;
self.master = Some(stored.master_secret);
self.resumed = true;
if let Some(kl) = self.config.key_log.as_ref() {
kl.log("CLIENT_RANDOM", &cr, &stored.master_secret);
}
let kb_len = 2 * suite.key_len + 8;
let mut kb = alloc::vec![0u8; kb_len];
key_block(suite.hash, &stored.master_secret, &sr, &cr, &mut kb);
let (_c_key, rest) = kb.split_at(suite.key_len);
let (s_key, ivs) = rest.split_at(suite.key_len);
let mut s_salt = [0u8; 4];
s_salt.copy_from_slice(&ivs[4..8]);
self.pending_server_crypter = Some(RecordCrypter12::new(suite.aead, s_key, s_salt));
self.state = State::WaitResumedServerFinished;
} else {
self.state = State::WaitCertificate;
}
Ok(())
}
fn on_certificate(&mut self, msg_type: u8, body: &[u8], raw: &[u8]) -> Result<(), Error> {
if msg_type != hs_type::CERTIFICATE {
return Err(Error::UnexpectedMessage);
}
let chain = parse_certificate_list_12(body)?;
if chain.is_empty() {
return Err(Error::BadCertificate);
}
let leaf = Certificate::from_der(chain[0].clone()).map_err(|_| Error::BadCertificate)?;
leaf.check_well_formed()
.map_err(|_| Error::BadCertificate)?;
let leaf_key = if self.config.verify_certificates {
let now = self.config.verification_time.clone().or_else(system_now);
let key = verify_chain_with_crls(
&self.config.roots,
&self.config.crls,
&chain,
now.as_ref(),
&self.config.signature_policy,
)?;
verify_hostname(&leaf, &self.server_name)?;
key
} else {
leaf.subject_public_key()
.map_err(|_| Error::BadCertificate)?
};
let suite = self.suite.expect("suite set in on_server_hello");
if !key_matches_sig_kind(&leaf_key, suite.sig_kind) {
return Err(Error::HandshakeFailure);
}
self.cert_chain = chain;
self.leaf_key = Some(leaf_key);
self.transcript.update(raw);
self.state = if self.server_echoed_ocsp_staple {
State::WaitCertificateStatus
} else {
State::WaitServerKeyExchange
};
Ok(())
}
fn on_certificate_status(
&mut self,
msg_type: u8,
body: &[u8],
raw: &[u8],
) -> Result<(), Error> {
if msg_type != hs_type::CERTIFICATE_STATUS {
return Err(Error::UnexpectedMessage);
}
let ocsp = ext::parse_certificate_status(body)?;
if self.config.verify_certificates && self.cert_chain.len() >= 2 {
let leaf = crate::x509::Certificate::from_der(self.cert_chain[0].clone())
.map_err(|_| Error::BadCertificate)?;
let issuer = crate::x509::Certificate::from_der(self.cert_chain[1].clone())
.map_err(|_| Error::BadCertificate)?;
let now = self.config.verification_time.clone().or_else(system_now);
let resp = crate::x509::OcspResponse::from_der(ocsp.clone())
.map_err(|_| Error::OcspResponseInvalid)?;
match resp
.check_for_cert_with_options(
&leaf,
&issuer,
&crate::x509::OcspCheckOptions::new(&self.config.signature_policy)
.with_time(now.as_ref()),
)
.map_err(|_| Error::OcspResponseInvalid)?
{
crate::x509::OcspCertStatus::Good => {}
crate::x509::OcspCertStatus::Revoked { .. } => {
return Err(Error::CertificateRevoked);
}
crate::x509::OcspCertStatus::Unknown => return Err(Error::OcspResponseInvalid),
}
}
self.peer_ocsp_response = Some(ocsp);
self.transcript.update(raw);
self.state = State::WaitServerKeyExchange;
Ok(())
}
fn on_server_key_exchange(
&mut self,
msg_type: u8,
body: &[u8],
raw: &[u8],
) -> Result<(), Error> {
if msg_type != hs_type::SERVER_KEY_EXCHANGE {
return Err(Error::UnexpectedMessage);
}
let ske = ServerKeyExchange::decode(body)?;
if !self.offered_groups.contains(&ske.group) {
return Err(Error::IllegalParameter);
}
let cr = self.client_random;
let sr = self.server_random.expect("server_random set");
let msg = signed_message(&cr, &sr, ske.group, &ske.point);
let leaf_key = self
.leaf_key
.as_ref()
.ok_or(Error::InappropriateState)?
.clone();
verify_signature(
ske.scheme,
&leaf_key,
&msg,
&ske.signature,
&self.config.signature_policy,
)?;
self.peer_share = Some((ske.group, ske.point.clone()));
self.transcript.update(raw);
self.state = State::WaitServerHelloDone;
Ok(())
}
fn on_server_hello_done(&mut self, msg_type: u8, body: &[u8], raw: &[u8]) -> Result<(), Error> {
if msg_type == hs_type::CERTIFICATE_REQUEST {
let _cr = CertificateRequest12::decode(body)?;
self.cert_request_received = true;
self.transcript.update(raw);
return Ok(());
}
if msg_type != hs_type::SERVER_HELLO_DONE {
return Err(Error::UnexpectedMessage);
}
let _ = ServerHelloDone::decode(body)?;
self.transcript.update(raw);
let (group, peer_point) = self
.peer_share
.as_ref()
.cloned()
.ok_or(Error::InappropriateState)?;
let (premaster, our_point) = self.ecdhe(group, &peer_point)?;
let suite = self.suite.expect("suite set");
if self.cert_request_received {
self.send_client_certificate();
}
let cke = ClientKeyExchange { point: our_point }.encode();
self.transcript.update(&cke);
if self.ems_negotiated {
self.ems_session_hash = Some(self.transcript.current_hash().as_slice().to_vec());
}
self.write_plain_record(ContentType::Handshake, &cke);
let cr = self.client_random;
let sr = self.server_random.expect("server_random set");
let master = if self.ems_negotiated {
let session_hash = self
.ems_session_hash
.as_ref()
.expect("EMS session_hash snapshot taken just above");
extended_master_secret(suite.hash, &premaster, session_hash)
} else {
master_secret(suite.hash, &premaster, &cr, &sr)
};
if let Some(kl) = self.config.key_log.as_ref() {
kl.log("CLIENT_RANDOM", &cr, &master);
}
let kb_len = 2 * suite.key_len + 8;
let mut kb = alloc::vec![0u8; kb_len];
key_block(suite.hash, &master, &sr, &cr, &mut kb);
let (c_key, rest) = kb.split_at(suite.key_len);
let (s_key, rest) = rest.split_at(suite.key_len);
let mut c_salt = [0u8; 4];
c_salt.copy_from_slice(&rest[..4]);
let mut s_salt = [0u8; 4];
s_salt.copy_from_slice(&rest[4..8]);
let client_crypter = RecordCrypter12::new(suite.aead, c_key, c_salt);
let server_crypter = RecordCrypter12::new(suite.aead, s_key, s_salt);
if self.cert_request_received && self.config.client_cert.is_some() {
self.send_client_certificate_verify()?;
}
self.write_plain_record(ContentType::ChangeCipherSpec, &[0x01]);
self.client_crypter = Some(client_crypter);
self.pending_server_crypter = Some(server_crypter);
self.master = Some(master);
let th = self.transcript.current_hash();
let verify_data =
finished_verify_data(suite.hash, &master, b"client finished", th.as_slice());
let finished = build_finished(&verify_data);
self.transcript.update(&finished);
self.emit_encrypted(ContentType::Handshake, &finished)?;
self.state = State::WaitServerFinished;
Ok(())
}
fn send_client_certificate(&mut self) {
let mut msg = alloc::vec![hs_type::CERTIFICATE];
with_len_u24(&mut msg, |b| {
with_len_u24(b, |list| {
if let Some(cc) = self.config.client_cert.as_ref() {
for cert in cc.chain() {
with_len_u24(list, |c| c.extend_from_slice(cert));
}
}
});
});
self.transcript.update(&msg);
self.write_plain_record(ContentType::Handshake, &msg);
}
fn send_client_certificate_verify(&mut self) -> Result<(), Error> {
let cc = self
.config
.client_cert
.as_ref()
.ok_or(Error::InappropriateState)?;
let to_sign = self.transcript.buffered_bytes().to_vec();
let scheme = ClientCertConfig::signature_scheme_for(cc.key());
let signature: Vec<u8> = match cc.key() {
ClientKey::Rsa(_) => {
return Err(Error::HandshakeFailure);
}
ClientKey::Ecdsa(k) => {
let sig = match k.curve() {
CurveId::P384 => k.sign::<Sha384>(&to_sign),
CurveId::P521 => k.sign::<Sha512>(&to_sign),
_ => k.sign::<Sha256>(&to_sign),
}
.map_err(|_| Error::HandshakeFailure)?;
sig.to_der(k.curve())
}
ClientKey::Ed25519(k) => k.sign(&to_sign).to_bytes().to_vec(),
ClientKey::Ed448(k) => k.sign(&to_sign).to_bytes().to_vec(),
ClientKey::MlDsa44(k) => k
.sign_deterministic(&to_sign, b"")
.map_err(|_| Error::HandshakeFailure)?,
ClientKey::MlDsa65(k) => k
.sign_deterministic(&to_sign, b"")
.map_err(|_| Error::HandshakeFailure)?,
ClientKey::MlDsa87(k) => k
.sign_deterministic(&to_sign, b"")
.map_err(|_| Error::HandshakeFailure)?,
};
let mut msg = alloc::vec![hs_type::CERTIFICATE_VERIFY];
with_len_u24(&mut msg, |b| {
b.extend_from_slice(&scheme.0.to_be_bytes());
with_len_u16(b, |s| s.extend_from_slice(&signature));
});
self.transcript.update(&msg);
self.write_plain_record(ContentType::Handshake, &msg);
Ok(())
}
fn on_server_finished(&mut self, msg_type: u8, body: &[u8], raw: &[u8]) -> Result<(), Error> {
if msg_type == hs_type::NEW_SESSION_TICKET {
let nst = NewSessionTicket12::decode(body)?;
if !nst.ticket.is_empty() {
self.received_ticket = Some(nst.ticket);
self.received_ticket_lifetime = nst.lifetime;
}
self.transcript.update(raw);
return Ok(());
}
if msg_type != hs_type::FINISHED {
return Err(Error::UnexpectedMessage);
}
if body.len() != 12 {
return Err(Error::Decode);
}
let suite = self.suite.expect("suite set");
let master = self.master.expect("master set");
let th = self.transcript.current_hash();
let expected = finished_verify_data(suite.hash, &master, b"server finished", th.as_slice());
if !bool::from(expected.as_slice().ct_eq(body)) {
return Err(Error::HandshakeFailure);
}
self.transcript.update(raw);
if self.state == State::WaitResumedServerFinished {
self.write_plain_record(ContentType::ChangeCipherSpec, &[0x01]);
let cr = self.client_random;
let sr = self.server_random.expect("server_random set");
let kb_len = 2 * suite.key_len + 8;
let mut kb = alloc::vec![0u8; kb_len];
key_block(suite.hash, &master, &sr, &cr, &mut kb);
let (c_key, rest) = kb.split_at(suite.key_len);
let (_s_key, ivs) = rest.split_at(suite.key_len);
let mut c_salt = [0u8; 4];
c_salt.copy_from_slice(&ivs[..4]);
self.client_crypter = Some(RecordCrypter12::new(suite.aead, c_key, c_salt));
let th_cf = self.transcript.current_hash();
let verify_data =
finished_verify_data(suite.hash, &master, b"client finished", th_cf.as_slice());
let finished = build_finished(&verify_data);
self.transcript.update(&finished);
self.emit_encrypted(ContentType::Handshake, &finished)?;
}
self.ccs_window_open = false;
self.state = State::Connected;
Ok(())
}
fn ecdhe(&self, group: NamedGroup, peer_point: &[u8]) -> Result<(Vec<u8>, Vec<u8>), Error> {
match group {
NamedGroup::X25519 => {
let peer: [u8; 32] = peer_point.try_into().map_err(|_| Error::Decode)?;
let ss = self
.x25519
.diffie_hellman(&peer)
.map_err(|_| Error::IllegalParameter)?;
Ok((ss.to_vec(), self.x25519.public_key().to_vec()))
}
NamedGroup::SECP256R1 => {
let peer = BoxedEcdsaPublicKey::from_sec1(CurveId::P256, peer_point)
.map_err(|_| Error::Decode)?;
let ss = self
.p256
.diffie_hellman(&peer)
.map_err(|_| Error::PeerMisbehaved)?;
Ok((ss, self.p256.public_key().to_sec1()))
}
NamedGroup::SECP384R1 => {
let peer = BoxedEcdsaPublicKey::from_sec1(CurveId::P384, peer_point)
.map_err(|_| Error::Decode)?;
let ss = self
.p384
.diffie_hellman(&peer)
.map_err(|_| Error::PeerMisbehaved)?;
Ok((ss, self.p384.public_key().to_sec1()))
}
_ => Err(Error::HandshakeFailure),
}
}
}
enum Incoming {
Handshake(Vec<u8>),
ApplicationData,
Alert(Alert),
}
fn parse_alert(body: &[u8]) -> Result<Incoming, Error> {
if body.len() != 2 {
return Err(Error::Decode);
}
Ok(Incoming::Alert(Alert {
fatal: body[0] == 2,
description: AlertDescription::from_u8(body[1]),
}))
}
pub(super) fn parse_certificate_list_12(body: &[u8]) -> Result<Vec<Vec<u8>>, Error> {
let mut c = ReadCursor::new(body);
let list = c.vec_u24()?;
c.expect_empty()?;
let mut entries = ReadCursor::new(list);
let mut certs = Vec::new();
while !entries.is_empty() {
let cert = entries.vec_u24()?.to_vec();
if cert.is_empty() {
return Err(Error::BadCertificate);
}
certs.push(cert);
}
Ok(certs)
}
fn build_finished(verify_data: &[u8; 12]) -> Vec<u8> {
let mut out = Vec::with_capacity(4 + 12);
out.push(hs_type::FINISHED);
out.extend_from_slice(&[0, 0, 12]); out.extend_from_slice(verify_data);
out
}
fn alert_for(error: &Error) -> AlertDescription {
match error {
Error::Decode => AlertDescription::DecodeError,
Error::UnexpectedMessage => AlertDescription::UnexpectedMessage,
Error::BadRecordMac => AlertDescription::BadRecordMac,
Error::BadCertificate => AlertDescription::BadCertificate,
Error::UnsupportedVersion => AlertDescription::ProtocolVersion,
Error::PeerMisbehaved | Error::InappropriateState | Error::IllegalParameter => {
AlertDescription::IllegalParameter
}
Error::RecordOverflow => AlertDescription::RecordOverflow,
Error::TooManyRecords => AlertDescription::InternalError,
Error::NoApplicationProtocol => AlertDescription::NoApplicationProtocol,
Error::DecryptError => AlertDescription::DecryptError,
Error::CertificateRequired => AlertDescription::CertificateRequired,
Error::CertificateRevoked | Error::OcspResponseInvalid => AlertDescription::BadCertificate,
#[cfg(feature = "ech")]
Error::EchDecryptionFailed => AlertDescription::DecryptError,
#[cfg(feature = "ech")]
Error::EchDecodeError => AlertDescription::IllegalParameter,
#[cfg(feature = "cert-compression")]
Error::CertDecompressionFailed => AlertDescription::BadCertificate,
_ => AlertDescription::HandshakeFailure,
}
}
#[cfg(feature = "std")]
impl super::stream::ConnectionIo for ClientConnection12 {
fn read_tls(&mut self, bytes: &[u8]) {
ClientConnection12::read_tls(self, bytes)
}
fn write_tls(&mut self) -> Vec<u8> {
ClientConnection12::write_tls(self)
}
fn process_new_packets(&mut self) -> Result<(), Error> {
ClientConnection12::process_new_packets(self)
}
fn is_handshaking(&self) -> bool {
ClientConnection12::is_handshaking(self)
}
fn send_application_data(&mut self, data: &[u8]) -> Result<(), Error> {
ClientConnection12::send_application_data(self, data)
}
fn take_received_plaintext(&mut self) -> Vec<u8> {
ClientConnection12::take_received_plaintext(self)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::hash::Sha256;
use crate::rng::HmacDrbg;
use crate::tls::codec::{ClientHello, ExtensionType, hs_type, read_record};
#[test]
fn client12_build_client_hello() {
let mut rng = HmacDrbg::<Sha256>::new(b"c12-ch", b"nonce", &[]);
let cfg = ClientConfig12::new(RootCertStore::new());
let mut c = ClientConnection12::new(cfg, "example.com", &mut rng);
assert!(c.is_handshaking());
let out = c.write_tls();
let rec = read_record(&out).unwrap().unwrap();
assert_eq!(rec.content_type, ContentType::Handshake);
assert_eq!(rec.len, out.len());
let mut cur = ReadCursor::new(rec.fragment);
assert_eq!(cur.u8().unwrap(), hs_type::CLIENT_HELLO);
let body = cur.vec_u24().unwrap();
let ch = ClientHello::decode(body).unwrap();
assert_eq!(ch.cipher_suites.len(), 6);
assert!(ch.session_id.is_empty());
for ty in [
ExtensionType::SERVER_NAME,
ExtensionType::SUPPORTED_GROUPS,
ExtensionType::SIGNATURE_ALGORITHMS,
ExtensionType::EC_POINT_FORMATS,
ExtensionType::RENEGOTIATION_INFO,
ExtensionType::EXTENDED_MASTER_SECRET,
] {
assert!(
ext::find(&ch.extensions, ty).is_some(),
"missing extension {ty:?}",
);
}
let ems_body = ext::find(&ch.extensions, ExtensionType::EXTENDED_MASTER_SECRET).unwrap();
assert!(ems_body.is_empty(), "EMS body must be empty");
assert!(ext::find(&ch.extensions, ExtensionType::SUPPORTED_VERSIONS).is_none());
let r = ext::find(&ch.extensions, ExtensionType::RENEGOTIATION_INFO).unwrap();
assert_eq!(r, &[0u8]);
}
fn synth_sh_record(suite: CipherSuite, exts: Vec<(ExtensionType, Vec<u8>)>) -> Vec<u8> {
use crate::tls::codec::write_record;
let sh = crate::tls::codec::ServerHello {
random: [0x11u8; 32],
session_id: Vec::new(),
cipher_suite: suite,
extensions: exts,
};
let body = sh.encode();
let mut rec = Vec::new();
write_record(
&mut rec,
ContentType::Handshake,
ProtocolVersion::TLSv1_2,
&body,
);
rec
}
#[test]
fn client12_rejects_unknown_suite() {
let mut rng = HmacDrbg::<Sha256>::new(b"c12-bad-suite", b"nonce", &[]);
let mut c = ClientConnection12::new(
ClientConfig12::new(RootCertStore::new()),
"example.com",
&mut rng,
);
let _ = c.write_tls();
let exts = alloc::vec![(ExtensionType::RENEGOTIATION_INFO, alloc::vec![0u8])];
let sh = synth_sh_record(CipherSuite(0xFEFE), exts);
c.read_tls(&sh);
assert!(matches!(
c.process_new_packets(),
Err(Error::HandshakeFailure)
));
}
#[test]
fn client12_rejects_missing_renegotiation_info() {
let mut rng = HmacDrbg::<Sha256>::new(b"c12-no-reneg", b"nonce", &[]);
let mut c = ClientConnection12::new(
ClientConfig12::new(RootCertStore::new()),
"example.com",
&mut rng,
);
let _ = c.write_tls();
let sh = synth_sh_record(
CipherSuite::TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
Vec::new(),
);
c.read_tls(&sh);
assert!(matches!(
c.process_new_packets(),
Err(Error::HandshakeFailure)
));
}
#[test]
fn client12_rejects_server_omitting_extended_master_secret_by_default() {
let mut rng = HmacDrbg::<Sha256>::new(b"c12-noems", b"nonce", &[]);
let mut c = ClientConnection12::new(
ClientConfig12::new(RootCertStore::new()),
"example.com",
&mut rng,
);
let _ = c.write_tls();
let exts = alloc::vec![(ExtensionType::RENEGOTIATION_INFO, alloc::vec![0u8])];
let sh = synth_sh_record(CipherSuite::TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, exts);
c.read_tls(&sh);
assert!(matches!(
c.process_new_packets(),
Err(Error::HandshakeFailure)
));
}
#[test]
fn client12_accepts_no_ems_when_requirement_disabled() {
let mut rng = HmacDrbg::<Sha256>::new(b"c12-noems-opt", b"nonce", &[]);
let mut c = ClientConnection12::new(
ClientConfig12::new(RootCertStore::new()).with_require_ems(false),
"example.com",
&mut rng,
);
let _ = c.write_tls();
let exts = alloc::vec![(ExtensionType::RENEGOTIATION_INFO, alloc::vec![0u8])];
let sh = synth_sh_record(CipherSuite::TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, exts);
c.read_tls(&sh);
let r = c.process_new_packets();
assert!(
!matches!(r, Err(Error::HandshakeFailure)),
"EMS opt-out should not trigger HandshakeFailure, got {:?}",
r
);
}
#[test]
fn client12_rejects_tls13_supported_versions() {
let mut rng = HmacDrbg::<Sha256>::new(b"c12-1.3-downgrade", b"nonce", &[]);
let mut c = ClientConnection12::new(
ClientConfig12::new(RootCertStore::new()),
"example.com",
&mut rng,
);
let _ = c.write_tls();
let exts = alloc::vec![
(ExtensionType::RENEGOTIATION_INFO, alloc::vec![0u8]),
(
ExtensionType::SUPPORTED_VERSIONS,
alloc::vec![0x03u8, 0x04u8],
),
];
let sh = synth_sh_record(CipherSuite::TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, exts);
c.read_tls(&sh);
assert!(matches!(
c.process_new_packets(),
Err(Error::UnsupportedVersion)
));
}
}