use super::key_schedule::{CipherSuite, TrafficKeys};
use super::TlsError;
use alloc::vec::Vec;
pub(crate) const CONTENT_CHANGE_CIPHER_SPEC: u8 = 20;
pub(crate) const CONTENT_ALERT: u8 = 21;
pub(crate) const CONTENT_HANDSHAKE: u8 = 22;
pub(crate) const CONTENT_APPLICATION_DATA: u8 = 23;
pub(crate) const MAX_RECORD_PAYLOAD: usize = 16_384 + 256;
#[derive(Debug, Clone, Copy, Default)]
pub(crate) struct Sequence {
value: u64,
}
impl Sequence {
pub(crate) fn next(&mut self) -> Result<u64, TlsError> {
let v = self.value;
if self.value == u64::MAX {
return Err(TlsError::Protocol(
"record sequence number exhausted".into(),
));
}
self.value += 1;
Ok(v)
}
}
fn build_nonce(iv: &[u8; 12], seq: u64) -> [u8; 12] {
let mut nonce = *iv;
let seq_bytes = seq.to_be_bytes();
for i in 0..8 {
nonce[4 + i] ^= seq_bytes[i];
}
nonce
}
pub(crate) fn seal_record(
suite: CipherSuite,
keys: &TrafficKeys,
seq: u64,
content_type: u8,
plaintext: &[u8],
) -> Result<Vec<u8>, TlsError> {
if plaintext.len() + 1 > MAX_RECORD_PAYLOAD {
return Err(TlsError::Protocol("record too large".into()));
}
let mut inner = Vec::with_capacity(plaintext.len() + 1);
inner.extend_from_slice(plaintext);
inner.push(content_type);
let nonce = build_nonce(&keys.iv, seq);
let tag_len = 16usize;
let ct_len = inner.len() + tag_len;
if ct_len > u16::MAX as usize {
return Err(TlsError::Protocol("record too large".into()));
}
let header = [
CONTENT_APPLICATION_DATA,
0x03,
0x03,
(ct_len >> 8) as u8,
ct_len as u8,
];
let encrypted = suite
.seal(&keys.key[..suite.key_len()], &nonce, &header, &inner)
.ok_or_else(|| TlsError::Internal("AEAD seal failed".into()))?;
let mut out = Vec::with_capacity(5 + encrypted.len());
out.extend_from_slice(&header);
out.extend_from_slice(&encrypted);
Ok(out)
}
pub(crate) fn open_record(
suite: CipherSuite,
keys: &TrafficKeys,
seq: u64,
header: &[u8; 5],
encrypted: &[u8],
) -> Result<(u8, Vec<u8>), TlsError> {
if header[0] != CONTENT_APPLICATION_DATA {
return Err(TlsError::Protocol("unexpected record type".into()));
}
if encrypted.len() < 16 || encrypted.len() > MAX_RECORD_PAYLOAD + 16 {
return Err(TlsError::Protocol("bad record length".into()));
}
let nonce = build_nonce(&keys.iv, seq);
let mut inner = suite
.open(&keys.key[..suite.key_len()], &nonce, header, encrypted)
.ok_or(TlsError::Alert {
level: 2, description: 20, })?;
if inner.is_empty() {
return Err(TlsError::Protocol("record too short".into()));
}
let mut n = inner.len();
let content_type;
loop {
if n == 0 {
return Err(TlsError::Alert {
level: 2,
description: 20, });
}
n -= 1;
if inner[n] != 0 {
content_type = inner[n];
break;
}
}
if !matches!(
content_type,
CONTENT_CHANGE_CIPHER_SPEC | CONTENT_ALERT | CONTENT_HANDSHAKE | CONTENT_APPLICATION_DATA
) {
return Err(TlsError::Alert {
level: 2,
description: 10, });
}
inner.truncate(n);
Ok((content_type, inner))
}
#[cfg(test)]
mod tests {
use super::*;
use crate::courierust_tls::key_schedule::CipherSuite;
#[test]
fn record_roundtrip_all_suites() {
for suite in [
CipherSuite::TlsAes128GcmSha256,
CipherSuite::TlsAes256GcmSha384,
CipherSuite::TlsChaCha20Poly1305Sha256,
] {
let keys = TrafficKeys {
key: [0x42; 32],
iv: [0x24; 12],
};
let plaintext = b"hello TLS 1.3 record layer";
for seq in 0..3u64 {
let rec =
seal_record(suite, &keys, seq, CONTENT_HANDSHAKE, plaintext).expect("seal");
let mut header = [0u8; 5];
header.copy_from_slice(&rec[..5]);
let encrypted = &rec[5..];
let (ct, out) = open_record(suite, &keys, seq, &header, encrypted).expect("open");
assert_eq!(ct, CONTENT_HANDSHAKE);
assert_eq!(out, plaintext);
}
}
}
#[test]
fn record_wrong_sequence_fails() {
let suite = CipherSuite::TlsChaCha20Poly1305Sha256;
let keys = TrafficKeys {
key: [0x42; 32],
iv: [0x24; 12],
};
let rec = seal_record(suite, &keys, 0, CONTENT_APPLICATION_DATA, b"x").unwrap();
let mut header = [0u8; 5];
header.copy_from_slice(&rec[..5]);
let mut bad = rec[5..].to_vec();
bad[0] ^= 1;
assert!(open_record(suite, &keys, 0, &header, &bad).is_err());
assert!(open_record(suite, &keys, 1, &header, &rec[5..]).is_err());
}
#[test]
fn record_tampered_aad_fails() {
let suite = CipherSuite::TlsAes128GcmSha256;
let keys = TrafficKeys {
key: [0x42; 32],
iv: [0x24; 12],
};
let rec = seal_record(suite, &keys, 0, CONTENT_APPLICATION_DATA, b"payload").unwrap();
let mut header = [0u8; 5];
header.copy_from_slice(&rec[..5]);
header[4] ^= 1;
assert!(open_record(suite, &keys, 0, &header, &rec[5..]).is_err());
}
#[test]
fn record_bad_inner_type_fails() {
let suite = CipherSuite::TlsAes256GcmSha384;
let keys = TrafficKeys {
key: [0x42; 32],
iv: [0x24; 12],
};
let nonce = [0u8; 12];
let mut iv = keys.iv;
for i in 0..8 {
iv[4 + i] ^= 0;
}
let _ = nonce;
let inner: Vec<u8> = vec![0x01, 0x02, 0x03, 0x00, 0x99]; let ct_len = inner.len() + 16;
let header = [
CONTENT_APPLICATION_DATA,
0x03,
0x03,
(ct_len >> 8) as u8,
ct_len as u8,
];
let enc = suite
.seal(&keys.key[..suite.key_len()], &iv, &header, &inner)
.unwrap();
assert!(open_record(suite, &keys, 0, &header, &enc).is_err());
}
}