use super::{
CipherSuite, ExtensionType, Random, ReadCursor, put_u8, put_u16, put_u32, with_len_u8,
with_len_u16, with_len_u24,
};
use crate::tls::Error;
use alloc::vec::Vec;
pub(crate) mod hs_type {
#[allow(dead_code)]
pub(crate) const HELLO_REQUEST: u8 = 0;
pub(crate) const CLIENT_HELLO: u8 = 1;
pub(crate) const SERVER_HELLO: u8 = 2;
pub(crate) const NEW_SESSION_TICKET: u8 = 4;
pub(crate) const END_OF_EARLY_DATA: u8 = 5;
pub(crate) const ENCRYPTED_EXTENSIONS: u8 = 8;
pub(crate) const CERTIFICATE: u8 = 11;
#[allow(dead_code)]
pub(crate) const SERVER_KEY_EXCHANGE: u8 = 12;
pub(crate) const CERTIFICATE_REQUEST: u8 = 13;
#[allow(dead_code)]
pub(crate) const SERVER_HELLO_DONE: u8 = 14;
pub(crate) const CERTIFICATE_VERIFY: u8 = 15;
#[allow(dead_code)]
pub(crate) const CLIENT_KEY_EXCHANGE: u8 = 16;
pub(crate) const FINISHED: u8 = 20;
pub(crate) const KEY_UPDATE: u8 = 24;
}
pub(crate) type RawExtension = (ExtensionType, Vec<u8>);
pub(crate) fn read_handshake<'a>(cursor: &mut ReadCursor<'a>) -> Result<(u8, &'a [u8]), Error> {
let msg_type = cursor.u8()?;
let body = cursor.vec_u24()?;
Ok((msg_type, body))
}
fn encode_extensions(out: &mut Vec<u8>, extensions: &[RawExtension]) {
with_len_u16(out, |b| {
for (ty, data) in extensions {
put_u16(b, ty.0);
with_len_u16(b, |b| b.extend_from_slice(data));
}
});
}
fn parse_extensions(bytes: &[u8]) -> Result<Vec<RawExtension>, Error> {
let mut c = ReadCursor::new(bytes);
let mut out: Vec<RawExtension> = Vec::new();
while !c.is_empty() {
let ty = ExtensionType(c.u16()?);
let data = c.vec_u16()?;
if out.iter().any(|(t, _)| *t == ty) {
return Err(Error::IllegalParameter);
}
out.push((ty, data.to_vec()));
}
Ok(out)
}
fn read_random(c: &mut ReadCursor<'_>) -> Result<Random, Error> {
let mut r = [0u8; 32];
r.copy_from_slice(c.take(32)?);
Ok(r)
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub(crate) struct ClientHello {
pub(crate) legacy_version: u16,
pub(crate) random: Random,
pub(crate) session_id: Vec<u8>,
pub(crate) cipher_suites: Vec<CipherSuite>,
pub(crate) extensions: Vec<RawExtension>,
}
impl ClientHello {
pub(crate) fn encode(&self) -> Vec<u8> {
let mut out = Vec::new();
put_u8(&mut out, hs_type::CLIENT_HELLO);
with_len_u24(&mut out, |b| {
put_u16(b, self.legacy_version);
b.extend_from_slice(&self.random);
with_len_u8(b, |b| b.extend_from_slice(&self.session_id));
with_len_u16(b, |b| {
for cs in &self.cipher_suites {
put_u16(b, cs.0);
}
});
with_len_u8(b, |b| b.push(0)); encode_extensions(b, &self.extensions);
});
out
}
pub(crate) fn decode(body: &[u8]) -> Result<Self, Error> {
let mut c = ReadCursor::new(body);
let legacy_version = c.u16()?;
let random = read_random(&mut c)?;
let session_id = c.vec_u8()?.to_vec();
let cs_bytes = c.vec_u16()?;
let mut cs = ReadCursor::new(cs_bytes);
let mut cipher_suites = Vec::new();
while !cs.is_empty() {
cipher_suites.push(CipherSuite(cs.u16()?));
}
let _compression = c.vec_u8()?;
let extensions = parse_extensions(c.vec_u16()?)?;
c.expect_empty()?;
Ok(ClientHello {
legacy_version,
random,
session_id,
cipher_suites,
extensions,
})
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub(crate) struct ServerHello {
pub(crate) random: Random,
pub(crate) session_id: Vec<u8>,
pub(crate) cipher_suite: CipherSuite,
pub(crate) extensions: Vec<RawExtension>,
}
impl ServerHello {
pub(crate) fn encode(&self) -> Vec<u8> {
let mut out = Vec::new();
put_u8(&mut out, hs_type::SERVER_HELLO);
with_len_u24(&mut out, |b| {
put_u16(b, 0x0303); b.extend_from_slice(&self.random);
with_len_u8(b, |b| b.extend_from_slice(&self.session_id));
put_u16(b, self.cipher_suite.0);
put_u8(b, 0); encode_extensions(b, &self.extensions);
});
out
}
pub(crate) fn decode(body: &[u8]) -> Result<Self, Error> {
let mut c = ReadCursor::new(body);
let legacy_version = c.u16()?;
if legacy_version != 0x0303 {
return Err(Error::Decode);
}
let random = read_random(&mut c)?;
let session_id = c.vec_u8()?.to_vec();
let cipher_suite = CipherSuite(c.u16()?);
let compression = c.u8()?;
if compression != 0 {
return Err(Error::Decode);
}
let extensions = parse_extensions(c.vec_u16()?)?;
c.expect_empty()?;
Ok(ServerHello {
random,
session_id,
cipher_suite,
extensions,
})
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(crate) struct KeyUpdate {
pub(crate) request_update: bool,
}
impl KeyUpdate {
pub(crate) fn encode(&self) -> Vec<u8> {
let mut out = Vec::new();
put_u8(&mut out, hs_type::KEY_UPDATE);
with_len_u24(&mut out, |b| {
put_u8(b, if self.request_update { 1 } else { 0 })
});
out
}
pub(crate) fn decode(body: &[u8]) -> Result<Self, Error> {
let mut c = ReadCursor::new(body);
let request_update = match c.u8()? {
0 => false,
1 => true,
_ => return Err(Error::IllegalParameter),
};
c.expect_empty()?;
Ok(KeyUpdate { request_update })
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub(crate) struct NewSessionTicket {
pub(crate) ticket_lifetime: u32,
pub(crate) ticket_age_add: u32,
pub(crate) ticket_nonce: Vec<u8>,
pub(crate) ticket: Vec<u8>,
pub(crate) extensions: Vec<RawExtension>,
}
impl NewSessionTicket {
#[allow(dead_code)]
pub(crate) fn encode(&self) -> Vec<u8> {
let mut out = Vec::new();
put_u8(&mut out, hs_type::NEW_SESSION_TICKET);
with_len_u24(&mut out, |b| {
put_u32(b, self.ticket_lifetime);
put_u32(b, self.ticket_age_add);
with_len_u8(b, |b| b.extend_from_slice(&self.ticket_nonce));
with_len_u16(b, |b| b.extend_from_slice(&self.ticket));
encode_extensions(b, &self.extensions);
});
out
}
pub(crate) fn decode(body: &[u8]) -> Result<Self, Error> {
let mut c = ReadCursor::new(body);
let ticket_lifetime = c.u32()?;
let ticket_age_add = c.u32()?;
let ticket_nonce = c.vec_u8()?.to_vec();
let ticket = c.vec_u16()?.to_vec();
if ticket.is_empty() {
return Err(Error::Decode);
}
let extensions = parse_extensions(c.vec_u16()?)?;
c.expect_empty()?;
Ok(NewSessionTicket {
ticket_lifetime,
ticket_age_add,
ticket_nonce,
ticket,
extensions,
})
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn client_hello_roundtrip() {
let ch = ClientHello {
legacy_version: 0x0303,
random: [0x11; 32],
session_id: alloc::vec![0xab; 32],
cipher_suites: alloc::vec![
CipherSuite::AES_128_GCM_SHA256,
CipherSuite::AES_256_GCM_SHA384,
],
extensions: alloc::vec![
(
ExtensionType::SUPPORTED_VERSIONS,
alloc::vec![0x02, 0x03, 0x04]
),
(ExtensionType::KEY_SHARE, alloc::vec![1, 2, 3, 4]),
],
};
let bytes = ch.encode();
assert_eq!(bytes[0], hs_type::CLIENT_HELLO);
let mut c = ReadCursor::new(&bytes);
let (ty, body) = read_handshake(&mut c).unwrap();
assert_eq!(ty, hs_type::CLIENT_HELLO);
assert_eq!(ClientHello::decode(body).unwrap(), ch);
}
#[test]
fn server_hello_roundtrip() {
let sh = ServerHello {
random: [0x22; 32],
session_id: alloc::vec![0xcd; 32],
cipher_suite: CipherSuite::AES_256_GCM_SHA384,
extensions: alloc::vec![(ExtensionType::SUPPORTED_VERSIONS, alloc::vec![0x03, 0x04])],
};
let bytes = sh.encode();
let mut c = ReadCursor::new(&bytes);
let (ty, body) = read_handshake(&mut c).unwrap();
assert_eq!(ty, hs_type::SERVER_HELLO);
assert_eq!(ServerHello::decode(body).unwrap(), sh);
}
#[test]
fn rejects_truncated() {
let mut c = ReadCursor::new(&[1, 0, 0, 5, 0x03]); assert!(read_handshake(&mut c).is_err());
}
#[test]
fn new_session_ticket_roundtrip() {
let nst = NewSessionTicket {
ticket_lifetime: 7200,
ticket_age_add: 0x12345678,
ticket_nonce: alloc::vec![0xa1, 0xa2],
ticket: alloc::vec![0xb0; 48],
extensions: Vec::new(),
};
let bytes = nst.encode();
assert_eq!(bytes[0], hs_type::NEW_SESSION_TICKET);
let mut c = ReadCursor::new(&bytes);
let (ty, body) = read_handshake(&mut c).unwrap();
assert_eq!(ty, hs_type::NEW_SESSION_TICKET);
assert_eq!(NewSessionTicket::decode(body).unwrap(), nst);
}
#[test]
fn new_session_ticket_rejects_empty_ticket() {
let body = [0u8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
assert!(NewSessionTicket::decode(&body).is_err());
}
#[test]
fn new_session_ticket_with_early_data_ext() {
let nst = NewSessionTicket {
ticket_lifetime: 3600,
ticket_age_add: 0xdeadbeef,
ticket_nonce: alloc::vec![1, 2, 3, 4],
ticket: alloc::vec![0xcc; 100],
extensions: alloc::vec![(
ExtensionType(0x002a), alloc::vec![0x00, 0x00, 0x40, 0x00], )],
};
let body = &nst.encode()[4..]; assert_eq!(NewSessionTicket::decode(body).unwrap(), nst);
}
}