use crate::{
error::Error,
primitives::U24,
tls::{
HANDSHAKE_HEADER_LEN, MAX_TLS_PLAINTEXT_LEN, TLS_RECORD_HEADER_LEN, TlsCipherSuite,
TlsContentType, TlsExtension, TlsHandshakeType, TlsVersion,
},
utils::random_32_bytes,
};
#[derive(Debug)]
pub struct TlsClientHello {
tls_version: TlsVersion,
record_version: TlsVersion,
random: [u8; 32],
session_id: [u8; 32],
cipher_suites: Vec<TlsCipherSuite>,
extensions: Vec<TlsExtension>,
}
impl TlsClientHello {
pub fn new(
version: TlsVersion,
record_version: TlsVersion,
cipher_suites: Vec<TlsCipherSuite>,
extensions: Vec<TlsExtension>,
) -> Result<Self, Error> {
if cipher_suites.is_empty() {
return Err(Error::Malformed(
"ClientHello must contain at least one cipher suite",
));
}
let cipher_suites_length =
cipher_suites
.len()
.checked_mul(2)
.ok_or(Error::LengthOverflow {
max: u16::MAX as usize,
actual: usize::MAX,
})?;
u16::try_from(cipher_suites_length).map_err(|_| Error::LengthOverflow {
max: u16::MAX as usize,
actual: cipher_suites_length,
})?;
Ok(Self {
tls_version: version,
record_version,
random: random_32_bytes(),
session_id: random_32_bytes(),
cipher_suites,
extensions,
})
}
pub fn to_bytes(&self) -> Result<Vec<u8>, Error> {
let mut hello_body = Vec::new();
hello_body.extend_from_slice(&self.tls_version.legacy_version().to_be_bytes());
hello_body.extend_from_slice(&self.random);
hello_body.push(self.session_id.len() as u8);
hello_body.extend_from_slice(&self.session_id);
let cipher_suites_length = (self.cipher_suites.len() * 2) as u16;
hello_body.extend_from_slice(&cipher_suites_length.to_be_bytes());
for cipher_suite in &self.cipher_suites {
hello_body.extend_from_slice(&cipher_suite.to_be_bytes());
}
hello_body.extend_from_slice(&[1, 0]);
let mut encoded_extensions = Vec::new();
for extension in &self.extensions {
encoded_extensions.extend_from_slice(&extension.to_bytes());
}
let extensions_length =
u16::try_from(encoded_extensions.len()).map_err(|_| Error::LengthOverflow {
max: u16::MAX as usize,
actual: encoded_extensions.len(),
})?;
hello_body.extend_from_slice(&extensions_length.to_be_bytes());
hello_body.extend_from_slice(&encoded_extensions);
let hello_body_length = hello_body.len();
let mut handshake_message = Vec::with_capacity(HANDSHAKE_HEADER_LEN + hello_body_length);
let handshake_body_length = U24::new(hello_body_length)?;
handshake_message.push(TlsHandshakeType::CLIENT_HELLO.as_u8());
handshake_message.extend_from_slice(&handshake_body_length.to_be_bytes());
handshake_message.extend_from_slice(&hello_body);
let handshake_message_length = handshake_message.len();
if handshake_message_length > MAX_TLS_PLAINTEXT_LEN {
return Err(Error::LengthOverflow {
max: MAX_TLS_PLAINTEXT_LEN,
actual: handshake_message_length,
});
}
let mut encoded_record =
Vec::with_capacity(TLS_RECORD_HEADER_LEN + handshake_message_length);
let record_payload_length = handshake_message_length as u16;
encoded_record.push(TlsContentType::HANDSHAKE.as_u8());
encoded_record.extend_from_slice(&self.record_version.to_be_bytes());
encoded_record.extend_from_slice(&record_payload_length.to_be_bytes());
encoded_record.extend_from_slice(&handshake_message);
Ok(encoded_record)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::tls::{TlsExtensionType, TlsReader, TlsRecord};
#[test]
fn encodes_a_complete_client_hello_record() {
let extension = TlsExtension::new(TlsExtensionType::SERVER_NAME, vec![0, 1, 2]).unwrap();
let hello = TlsClientHello::new(
TlsVersion::TLS13,
TlsVersion::TLS10,
vec![TlsCipherSuite::TLS_AES_128_GCM_SHA256],
vec![extension],
)
.unwrap();
let encoded = hello.to_bytes().unwrap();
let (record, consumed) = TlsRecord::try_from_bytes(&encoded).unwrap();
assert_eq!(consumed, encoded.len());
assert_eq!(record.content_type(), &TlsContentType::HANDSHAKE);
assert_eq!(record.legacy_version(), &TlsVersion::TLS10);
let mut handshake = TlsReader::new(record.payload());
assert_eq!(
handshake.read_u8().unwrap(),
TlsHandshakeType::CLIENT_HELLO.as_u8()
);
assert_eq!(handshake.read_u24().unwrap(), handshake.remaining());
assert_eq!(handshake.read_u16().unwrap(), TlsVersion::TLS12.as_u16());
handshake.read_array::<32>().unwrap();
assert_eq!(handshake.read_u8().unwrap(), 32);
handshake.read_array::<32>().unwrap();
assert_eq!(handshake.read_u16().unwrap(), 2);
assert_eq!(
handshake.read_u16().unwrap(),
TlsCipherSuite::TLS_AES_128_GCM_SHA256.as_u16()
);
assert_eq!(handshake.read_bytes(2).unwrap(), &[1, 0]);
assert_eq!(handshake.read_u16().unwrap(), 7);
assert_eq!(
handshake.read_u16().unwrap(),
TlsExtensionType::SERVER_NAME.as_u16()
);
assert_eq!(handshake.read_u16().unwrap(), 3);
assert_eq!(handshake.read_bytes(3).unwrap(), &[0, 1, 2]);
assert!(handshake.is_empty());
}
#[test]
fn rejects_an_empty_cipher_suite_list() {
let error =
TlsClientHello::new(TlsVersion::TLS12, TlsVersion::TLS12, vec![], vec![]).unwrap_err();
assert!(matches!(error, Error::Malformed(_)));
}
}