use alloc::boxed::Box;
use alloc::vec;
use alloc::vec::Vec;
use core::fmt;
use pki_types::FipsStatus;
use zeroize::Zeroizing;
use crate::common_state::{Protocol, Side};
use crate::conn::{ConnectionRandoms, Exporter};
use crate::crypto::cipher::{AeadKey, MessageDecrypter, MessageEncrypter, Tls12AeadAlgorithm};
use crate::crypto::kx::{ActiveKeyExchange, KeyExchangeAlgorithm};
use crate::crypto::tls12::PrfSecret;
use crate::crypto::{self, SignatureScheme, hash};
use crate::enums::ProtocolVersion;
use crate::error::{ApiMisuse, Error, InvalidMessage};
use crate::msgs::{Codec, HandshakeAlignedProof, KxDecode, Reader};
use crate::suites::{CipherSuiteCommon, PartiallyExtractedSecrets, Suite, SupportedCipherSuite};
use crate::version::Tls12Version;
#[expect(clippy::exhaustive_structs)]
pub struct Tls12CipherSuite {
pub common: CipherSuiteCommon,
pub protocol_version: &'static Tls12Version,
pub prf_provider: &'static dyn crypto::tls12::Prf,
pub kx: KeyExchangeAlgorithm,
pub sign: &'static [SignatureScheme],
pub aead_alg: &'static dyn Tls12AeadAlgorithm,
}
impl Tls12CipherSuite {
pub fn resolve_sig_schemes(&self, offered: &[SignatureScheme]) -> Vec<SignatureScheme> {
self.sign
.iter()
.filter(|pref| offered.contains(pref))
.copied()
.collect()
}
pub fn fips(&self) -> FipsStatus {
let status = Ord::min(self.common.fips(), self.prf_provider.fips());
Ord::min(status, self.aead_alg.fips())
}
}
impl Suite for Tls12CipherSuite {
fn client_handler(&self) -> &'static dyn crate::client::ClientHandler<Self> {
self.protocol_version.client
}
fn server_handler(&self) -> &'static dyn crate::server::ServerHandler<Self> {
self.protocol_version.server
}
fn usable_for_protocol(&self, proto: Protocol) -> bool {
matches!(proto, Protocol::Tcp)
}
fn usable_for_kx_algorithm(&self, kxa: KeyExchangeAlgorithm) -> bool {
self.kx == kxa
}
fn usable_for_signature_scheme(&self, scheme: SignatureScheme) -> bool {
self.sign
.iter()
.any(|s| s.algorithm() == scheme.algorithm())
}
fn common(&self) -> &CipherSuiteCommon {
&self.common
}
const VERSION: ProtocolVersion = ProtocolVersion::TLSv1_2;
}
impl From<&'static Tls12CipherSuite> for SupportedCipherSuite {
fn from(s: &'static Tls12CipherSuite) -> Self {
Self::Tls12(s)
}
}
impl PartialEq for Tls12CipherSuite {
fn eq(&self, other: &Self) -> bool {
self.common.suite == other.common.suite
}
}
impl fmt::Debug for Tls12CipherSuite {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Tls12CipherSuite")
.field("suite", &self.common.suite)
.finish_non_exhaustive()
}
}
pub(crate) struct ConnectionSecrets {
pub(crate) randoms: ConnectionRandoms,
suite: &'static Tls12CipherSuite,
master_secret: Zeroizing<[u8; 48]>,
master_secret_prf: Box<dyn PrfSecret>,
}
impl ConnectionSecrets {
pub(crate) fn from_key_exchange(
kx: Box<dyn ActiveKeyExchange>,
peer_pub_key: &[u8],
ems_seed: Option<hash::Output>,
randoms: ConnectionRandoms,
suite: &'static Tls12CipherSuite,
) -> Result<Self, Error> {
let (label, seed) = match ems_seed {
Some(seed) => ("extended master secret", Seed::Ems(seed)),
None => (
"master secret",
Seed::Randoms(join_randoms(&randoms.client, &randoms.server)),
),
};
let mut master_secret = [0u8; 48];
suite.prf_provider.for_key_exchange(
&mut master_secret,
kx,
peer_pub_key,
label.as_bytes(),
seed.as_ref(),
)?;
let master_secret = Zeroizing::new(master_secret);
let master_secret_prf = suite
.prf_provider
.new_secret(&master_secret);
Ok(Self {
randoms,
suite,
master_secret,
master_secret_prf,
})
}
pub(crate) fn new_resume(
randoms: ConnectionRandoms,
suite: &'static Tls12CipherSuite,
master_secret: &[u8; 48],
) -> Self {
Self {
randoms,
suite,
master_secret: Zeroizing::new(*master_secret),
master_secret_prf: suite
.prf_provider
.new_secret(master_secret),
}
}
pub(crate) fn make_cipher_pair(&self, side: Side) -> MessageCipherPair {
let key_block = self.make_key_block();
let shape = self.suite.aead_alg.key_block_shape();
let (client_write_key, key_block) = key_block.split_at(shape.enc_key_len);
let (server_write_key, key_block) = key_block.split_at(shape.enc_key_len);
let (client_write_iv, key_block) = key_block.split_at(shape.fixed_iv_len);
let (server_write_iv, extra) = key_block.split_at(shape.fixed_iv_len);
let (write_key, write_iv, read_key, read_iv) = match side {
Side::Client => (
client_write_key,
client_write_iv,
server_write_key,
server_write_iv,
),
Side::Server => (
server_write_key,
server_write_iv,
client_write_key,
client_write_iv,
),
};
(
self.suite
.aead_alg
.decrypter(AeadKey::new(read_key), read_iv),
self.suite
.aead_alg
.encrypter(AeadKey::new(write_key), write_iv, extra),
)
}
fn make_key_block(&self) -> Zeroizing<Vec<u8>> {
let shape = self.suite.aead_alg.key_block_shape();
let len = (shape.enc_key_len + shape.fixed_iv_len) * 2 + shape.explicit_nonce_len;
let mut out = vec![0u8; len];
let randoms = join_randoms(&self.randoms.server, &self.randoms.client);
self.master_secret_prf
.prf(&mut out, b"key expansion", &randoms);
Zeroizing::new(out)
}
pub(crate) fn suite(&self) -> &'static Tls12CipherSuite {
self.suite
}
pub(crate) fn master_secret(&self) -> &[u8; 48] {
&self.master_secret
}
fn make_verify_data(
&self,
handshake_hash: &hash::Output,
label: &[u8],
_proof: &HandshakeAlignedProof,
) -> [u8; 12] {
let mut out = [0u8; 12];
self.master_secret_prf
.prf(&mut out, label, handshake_hash.as_ref());
out
}
pub(crate) fn client_verify_data(
&self,
handshake_hash: &hash::Output,
proof: &HandshakeAlignedProof,
) -> [u8; 12] {
self.make_verify_data(handshake_hash, b"client finished", proof)
}
pub(crate) fn server_verify_data(
&self,
handshake_hash: &hash::Output,
proof: &HandshakeAlignedProof,
) -> [u8; 12] {
self.make_verify_data(handshake_hash, b"server finished", proof)
}
pub(crate) fn into_exporter(self) -> Box<dyn Exporter> {
let Self {
randoms,
master_secret_prf,
master_secret: _,
suite: _,
} = self;
Box::new(Tls12Exporter {
randoms,
master_secret_prf,
})
}
pub(crate) fn extract_secrets(&self, side: Side) -> Result<PartiallyExtractedSecrets, Error> {
let key_block = self.make_key_block();
let shape = self.suite.aead_alg.key_block_shape();
let (client_key, key_block) = key_block.split_at(shape.enc_key_len);
let (server_key, key_block) = key_block.split_at(shape.enc_key_len);
let (client_iv, key_block) = key_block.split_at(shape.fixed_iv_len);
let (server_iv, explicit_nonce) = key_block.split_at(shape.fixed_iv_len);
let client_secrets = self.suite.aead_alg.extract_keys(
AeadKey::new(client_key),
client_iv,
explicit_nonce,
)?;
let server_secrets = self.suite.aead_alg.extract_keys(
AeadKey::new(server_key),
server_iv,
explicit_nonce,
)?;
let (tx, rx) = match side {
Side::Client => (client_secrets, server_secrets),
Side::Server => (server_secrets, client_secrets),
};
Ok(PartiallyExtractedSecrets { tx, rx })
}
}
pub(crate) struct Tls12Exporter {
randoms: ConnectionRandoms,
master_secret_prf: Box<dyn PrfSecret>,
}
impl Exporter for Tls12Exporter {
fn derive(&self, label: &[u8], context: Option<&[u8]>, output: &mut [u8]) -> Result<(), Error> {
let mut randoms = Vec::with_capacity(
32 + 32
+ context
.as_ref()
.map(|c| 2 + c.len())
.unwrap_or_default(),
);
randoms.extend_from_slice(&self.randoms.client);
randoms.extend_from_slice(&self.randoms.server);
if let Some(context) = context {
let Ok(len) = u16::try_from(context.len()) else {
return Err(ApiMisuse::ExporterContextTooLong.into());
};
len.encode(&mut randoms);
randoms.extend_from_slice(context);
}
self.master_secret_prf
.prf(output, label, &randoms);
Ok(())
}
}
enum Seed {
Ems(hash::Output),
Randoms([u8; 64]),
}
impl AsRef<[u8]> for Seed {
fn as_ref(&self) -> &[u8] {
match self {
Self::Ems(seed) => seed.as_ref(),
Self::Randoms(randoms) => randoms.as_ref(),
}
}
}
fn join_randoms(first: &[u8; 32], second: &[u8; 32]) -> [u8; 64] {
let mut randoms = [0u8; 64];
randoms[..32].copy_from_slice(first);
randoms[32..].copy_from_slice(second);
randoms
}
type MessageCipherPair = (Box<dyn MessageDecrypter>, Box<dyn MessageEncrypter>);
pub(crate) fn decode_kx_params<'a, T: KxDecode<'a>>(
kx_algorithm: KeyExchangeAlgorithm,
kx_params: &'a [u8],
) -> Result<T, Error> {
let mut rd = Reader::new(kx_params);
let kx_params = T::decode(&mut rd, kx_algorithm)?;
match rd.any_left() {
false => Ok(kx_params),
true => Err(InvalidMessage::InvalidDhParams.into()),
}
}
pub(crate) const DOWNGRADE_SENTINEL: [u8; 8] = [0x44, 0x4f, 0x57, 0x4e, 0x47, 0x52, 0x44, 0x01];
#[cfg(test)]
mod tests {
use super::*;
use crate::crypto::TEST_PROVIDER;
use crate::crypto::kx::NamedGroup;
use crate::msgs::{ServerEcdhParams, ServerKeyExchangeParams};
#[test]
fn server_ecdhe_remaining_bytes() {
let Some(kx_group) =
TEST_PROVIDER.find_kx_group(NamedGroup::X25519, ProtocolVersion::TLSv1_3)
else {
return;
};
let key = kx_group.start().unwrap();
let server_params = ServerEcdhParams::new(&*key);
let mut server_buf = Vec::new();
server_params.encode(&mut server_buf);
server_buf.push(34);
assert!(
decode_kx_params::<ServerKeyExchangeParams>(KeyExchangeAlgorithm::ECDHE, &server_buf)
.is_err()
);
}
#[test]
fn client_ecdhe_invalid() {
assert!(
decode_kx_params::<ServerKeyExchangeParams>(KeyExchangeAlgorithm::ECDHE, &[34],)
.is_err()
);
}
}