use bytes::{Buf, Bytes, BytesMut};
use super::client_hello;
use super::record::{
encode_record, CT_APPLICATION_DATA, CT_CHANGE_CIPHER_SPEC, CT_HANDSHAKE,
MAX_RECORD_FRAGMENT_WIRE, TLS_RECORD_HEADER_LEN, VER_TLS10, VER_TLS12,
};
use super::server_hello::{self, ParsedClientHello};
use crate::crypto::rng::RngProvider;
use crate::errors::CoreError;
const CHANGE_CIPHER_SPEC: [u8; 6] = [0x14, 0x03, 0x03, 0x00, 0x01, 0x01];
const OPAQUE_FLIGHT_MIN: usize = 1500;
const OPAQUE_FLIGHT_MAX: usize = 4000;
const TICKET_MIN: usize = 150;
const TICKET_MAX: usize = 300;
const FINISHED_MIN: usize = 36;
const FINISHED_MAX: usize = 64;
#[allow(dead_code)]
const CLOSE_NOTIFY_MIN: usize = 19;
#[allow(dead_code)]
const CLOSE_NOTIFY_MAX: usize = 31;
pub(crate) const SERVER_FLIGHT_TYPES: [u8; 4] = [
CT_HANDSHAKE, CT_CHANGE_CIPHER_SPEC,
CT_APPLICATION_DATA, CT_APPLICATION_DATA, ];
pub(crate) const CLIENT_FINISHED_TYPES: [u8; 2] = [CT_CHANGE_CIPHER_SPEC, CT_APPLICATION_DATA];
fn rand_len(rng: &dyn RngProvider, min: usize, max: usize) -> usize {
debug_assert!(max >= min);
min + (rng.next_u64() % (max - min + 1) as u64) as usize
}
fn opaque_record(
content_type: u8,
len: usize,
rng: &dyn RngProvider,
) -> Result<Vec<u8>, CoreError> {
let mut frag = vec![0u8; len];
rng.fill_bytes(&mut frag);
let mut out = Vec::with_capacity(TLS_RECORD_HEADER_LEN + len);
encode_record(content_type, VER_TLS12, &frag, &mut out)?;
Ok(out)
}
pub(crate) fn client_hello_record(sni: &str, rng: &dyn RngProvider) -> Result<Vec<u8>, CoreError> {
let ch = client_hello::build_client_hello(sni, rng)?;
let mut out = Vec::with_capacity(TLS_RECORD_HEADER_LEN + ch.len());
encode_record(CT_HANDSHAKE, VER_TLS10, &ch, &mut out)?;
Ok(out)
}
pub(crate) fn server_flight(
client: &ParsedClientHello,
rng: &dyn RngProvider,
) -> Result<Vec<u8>, CoreError> {
let sh = server_hello::build_server_hello(client, rng)?;
let mut out = Vec::new();
encode_record(CT_HANDSHAKE, VER_TLS12, &sh, &mut out)?;
out.extend_from_slice(&CHANGE_CIPHER_SPEC);
out.extend_from_slice(&opaque_record(
CT_APPLICATION_DATA,
rand_len(rng, OPAQUE_FLIGHT_MIN, OPAQUE_FLIGHT_MAX),
rng,
)?);
out.extend_from_slice(&opaque_record(
CT_APPLICATION_DATA,
rand_len(rng, TICKET_MIN, TICKET_MAX),
rng,
)?);
Ok(out)
}
pub(crate) fn client_finished_flight(rng: &dyn RngProvider) -> Result<Vec<u8>, CoreError> {
let mut out = Vec::new();
out.extend_from_slice(&CHANGE_CIPHER_SPEC);
out.extend_from_slice(&opaque_record(
CT_APPLICATION_DATA,
rand_len(rng, FINISHED_MIN, FINISHED_MAX),
rng,
)?);
Ok(out)
}
#[allow(dead_code)]
pub(crate) fn close_notify_record(rng: &dyn RngProvider) -> Result<Vec<u8>, CoreError> {
opaque_record(
CT_APPLICATION_DATA,
rand_len(rng, CLOSE_NOTIFY_MIN, CLOSE_NOTIFY_MAX),
rng,
)
}
pub(crate) fn take_record(
buf: &mut BytesMut,
expected_type: u8,
) -> Result<Option<Bytes>, CoreError> {
if buf.len() < TLS_RECORD_HEADER_LEN {
return Ok(None);
}
let content_type = buf[0];
let frag_len = u16::from_be_bytes([buf[3], buf[4]]) as usize;
if frag_len > MAX_RECORD_FRAGMENT_WIRE {
return Err(CoreError::NetworkError(format!(
"prelude record too large: {frag_len} > {MAX_RECORD_FRAGMENT_WIRE}"
)));
}
if content_type != expected_type {
return Err(CoreError::NetworkError(format!(
"unexpected prelude record type: 0x{content_type:02x}, expected 0x{expected_type:02x}"
)));
}
if buf.len() < TLS_RECORD_HEADER_LEN + frag_len {
return Ok(None);
}
buf.advance(TLS_RECORD_HEADER_LEN);
Ok(Some(buf.split_to(frag_len).freeze()))
}
#[cfg(test)]
mod tests {
use super::*;
use crate::crypto::rng::OsRng;
fn record_type(buf: &[u8]) -> u8 {
buf[0]
}
fn record_total_len(buf: &[u8]) -> usize {
TLS_RECORD_HEADER_LEN + u16::from_be_bytes([buf[3], buf[4]]) as usize
}
#[test]
fn full_prelude_round_trip() {
let ch_rec = client_hello_record("round.trip.test", &OsRng).expect("ch record");
assert_eq!(record_type(&ch_rec), CT_HANDSHAKE);
assert_eq!(&ch_rec[1..3], &[0x03, 0x01], "CH record version is 0x0301");
let mut server_in = BytesMut::from(&ch_rec[..]);
let ch_frag = take_record(&mut server_in, CT_HANDSHAKE)
.expect("take")
.expect("complete CH");
assert!(server_in.is_empty());
let parsed = server_hello::parse_client_hello(&ch_frag).expect("parse CH");
let flight = server_flight(&parsed, &OsRng).expect("server flight");
let mut client_in = BytesMut::from(&flight[..]);
let sh_frag = take_record(&mut client_in, CT_HANDSHAKE)
.expect("take SH")
.expect("SH complete");
for t in &SERVER_FLIGHT_TYPES[1..] {
take_record(&mut client_in, *t)
.expect("take flight record")
.expect("flight record complete");
}
assert!(
client_in.is_empty(),
"exactly the four flight records consumed"
);
assert_eq!(record_type(&flight), CT_HANDSHAKE);
assert_eq!(sh_frag[0], 0x02, "ServerHello handshake type");
let fin = client_finished_flight(&OsRng).expect("client finished");
let mut server_in2 = BytesMut::from(&fin[..]);
for t in &CLIENT_FINISHED_TYPES {
take_record(&mut server_in2, *t)
.expect("take finished record")
.expect("finished record complete");
}
assert!(server_in2.is_empty());
}
#[test]
fn change_cipher_spec_is_the_canonical_six_bytes() {
let fin = client_finished_flight(&OsRng).expect("finished");
assert_eq!(&fin[..6], &[0x14, 0x03, 0x03, 0x00, 0x01, 0x01]);
}
#[test]
fn server_flight_record_types_and_versions() {
let parsed = ParsedClientHello {
legacy_session_id: vec![0u8; 32],
cipher_suites: vec![0x1301],
supported_groups: vec![0x001d],
key_share_groups: vec![0x001d],
};
let flight = server_flight(&parsed, &OsRng).expect("flight");
let mut buf = &flight[..];
let mut seen = Vec::new();
while buf.len() >= TLS_RECORD_HEADER_LEN {
seen.push(record_type(buf));
assert_eq!(&buf[1..3], &[0x03, 0x03], "flight record version 0x0303");
let total = record_total_len(buf);
buf = &buf[total..];
}
assert_eq!(seen, SERVER_FLIGHT_TYPES);
}
#[test]
fn take_record_waits_for_a_full_record() {
let rec = close_notify_record(&OsRng).expect("rec");
let mut buf = BytesMut::new();
buf.extend_from_slice(&rec[..rec.len() - 1]);
assert!(take_record(&mut buf, CT_APPLICATION_DATA)
.expect("no error mid-dribble")
.is_none());
buf.extend_from_slice(&rec[rec.len() - 1..]);
assert!(take_record(&mut buf, CT_APPLICATION_DATA)
.expect("parse")
.is_some());
}
#[test]
fn take_record_rejects_wrong_type() {
let ch_rec = client_hello_record("x.test", &OsRng).expect("ch");
let mut buf = BytesMut::from(&ch_rec[..]);
assert!(take_record(&mut buf, CT_APPLICATION_DATA).is_err());
}
#[test]
fn take_record_rejects_oversized_declared() {
let mut buf = BytesMut::from(&[CT_HANDSHAKE, 0x03, 0x01, 0xFF, 0xFF, 0x00][..]);
assert!(take_record(&mut buf, CT_HANDSHAKE).is_err());
}
#[test]
fn opaque_record_sizes_are_in_range() {
let parsed = ParsedClientHello {
legacy_session_id: vec![0u8; 32],
cipher_suites: vec![0x1301],
supported_groups: vec![0x001d],
key_share_groups: vec![0x001d],
};
for _ in 0..32 {
let flight = server_flight(&parsed, &OsRng).expect("flight");
let mut buf = &flight[..];
buf = &buf[record_total_len(buf)..];
assert_eq!(record_type(buf), CT_CHANGE_CIPHER_SPEC);
buf = &buf[record_total_len(buf)..];
let opaque_len = record_total_len(buf) - TLS_RECORD_HEADER_LEN;
assert!((OPAQUE_FLIGHT_MIN..=OPAQUE_FLIGHT_MAX).contains(&opaque_len));
buf = &buf[record_total_len(buf)..];
let ticket_len = record_total_len(buf) - TLS_RECORD_HEADER_LEN;
assert!((TICKET_MIN..=TICKET_MAX).contains(&ticket_len));
}
}
#[test]
fn rand_len_handles_min_equals_max() {
assert_eq!(rand_len(&OsRng, 42, 42), 42);
}
}