use super::{
error::TlsParseError,
extensions::extension::TlsExtensions,
types::{TlsCipherSuite, TlsCompressionMethod, TlsExtensionType, TlsProtocolVersion},
};
pub const HELLO_RETRY_REQUEST_RANDOM: [u8; 32] = [
0xcf, 0x21, 0xad, 0x74, 0xe5, 0x9a, 0x61, 0x11, 0xbe, 0x1d, 0x8c, 0x02, 0x1e, 0x65, 0xb8, 0x91,
0xc2, 0xa2, 0x11, 0x16, 0x7a, 0xbb, 0x8c, 0x5e, 0x07, 0x9e, 0x09, 0xe2, 0xc8, 0xa8, 0x33, 0x9c,
];
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct ClientHello<'a> {
bytes: &'a [u8],
session_id: &'a [u8],
cipher_suites: &'a [u8],
compression_methods: &'a [u8],
extensions: &'a [u8],
}
impl<'a> ClientHello<'a> {
pub fn parse(bytes: &'a [u8]) -> Result<Self, TlsParseError> {
need(bytes, 34)?;
let mut offset = 34;
let session_id = take_u8_vector(bytes, &mut offset)?;
if session_id.len() > 32 {
return Err(TlsParseError::InvalidValue);
}
let cipher_suites = take_u16_vector(bytes, &mut offset)?;
if cipher_suites.is_empty() || !cipher_suites.len().is_multiple_of(2) {
return Err(TlsParseError::InvalidValue);
}
let compression_methods = take_u8_vector(bytes, &mut offset)?;
if compression_methods.is_empty() {
return Err(TlsParseError::InvalidValue);
}
let extensions = if offset == bytes.len() {
&[]
} else {
take_u16_vector(bytes, &mut offset)?
};
if offset != bytes.len() {
return Err(TlsParseError::TrailingBytes);
}
Ok(Self {
bytes,
session_id,
cipher_suites,
compression_methods,
extensions,
})
}
pub fn legacy_version(&self) -> TlsProtocolVersion {
TlsProtocolVersion::new(u16::from_be_bytes([self.bytes[0], self.bytes[1]]))
}
pub fn random(&self) -> &'a [u8; 32] {
self.bytes[2..34]
.try_into()
.expect("validated ClientHello random")
}
pub fn session_id(&self) -> &'a [u8] {
self.session_id
}
pub fn cipher_suites(&self) -> impl Iterator<Item = TlsCipherSuite> + 'a {
self.cipher_suites
.chunks_exact(2)
.map(|bytes| TlsCipherSuite::new(u16::from_be_bytes([bytes[0], bytes[1]])))
}
pub fn compression_methods(&self) -> impl Iterator<Item = TlsCompressionMethod> + 'a {
self.compression_methods
.iter()
.copied()
.map(TlsCompressionMethod::new)
}
pub fn extensions(&self) -> TlsExtensions<'a> {
TlsExtensions::new(self.extensions)
}
pub fn offers_tls13(&self) -> bool {
self.extensions()
.filter_map(Result::ok)
.filter(|extension| extension.extension_type() == TlsExtensionType::SUPPORTED_VERSIONS)
.filter_map(|extension| extension.client_supported_versions().ok())
.any(|versions| {
versions
.iter()
.any(|version| version == TlsProtocolVersion::TLS13)
})
}
pub fn as_bytes(&self) -> &'a [u8] {
self.bytes
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct ServerHello<'a> {
bytes: &'a [u8],
session_id: &'a [u8],
cipher_suite: TlsCipherSuite,
compression_method: TlsCompressionMethod,
extensions: &'a [u8],
}
impl<'a> ServerHello<'a> {
pub fn parse(bytes: &'a [u8]) -> Result<Self, TlsParseError> {
need(bytes, 38)?;
let mut offset = 34;
let session_id = take_u8_vector(bytes, &mut offset)?;
if session_id.len() > 32 {
return Err(TlsParseError::InvalidValue);
}
need(&bytes[offset..], 3)?;
let cipher_suite =
TlsCipherSuite::new(u16::from_be_bytes([bytes[offset], bytes[offset + 1]]));
let compression_method = TlsCompressionMethod::new(bytes[offset + 2]);
offset += 3;
let extensions = if offset == bytes.len() {
&[]
} else {
take_u16_vector(bytes, &mut offset)?
};
if offset != bytes.len() {
return Err(TlsParseError::TrailingBytes);
}
Ok(Self {
bytes,
session_id,
cipher_suite,
compression_method,
extensions,
})
}
pub fn legacy_version(&self) -> TlsProtocolVersion {
TlsProtocolVersion::new(u16::from_be_bytes([self.bytes[0], self.bytes[1]]))
}
pub fn random(&self) -> &'a [u8; 32] {
self.bytes[2..34]
.try_into()
.expect("validated ServerHello random")
}
pub fn session_id(&self) -> &'a [u8] {
self.session_id
}
pub fn cipher_suite(&self) -> TlsCipherSuite {
self.cipher_suite
}
pub fn compression_method(&self) -> TlsCompressionMethod {
self.compression_method
}
pub fn is_hello_retry_request(&self) -> bool {
self.random() == &HELLO_RETRY_REQUEST_RANDOM
}
pub fn extensions(&self) -> TlsExtensions<'a> {
TlsExtensions::new(self.extensions)
}
pub fn selected_version(&self) -> Result<Option<TlsProtocolVersion>, TlsParseError> {
for extension in self.extensions() {
let extension = extension?;
if extension.extension_type() == TlsExtensionType::SUPPORTED_VERSIONS {
return extension.server_supported_version().map(Some);
}
}
Ok(None)
}
pub fn as_bytes(&self) -> &'a [u8] {
self.bytes
}
}
fn need(bytes: &[u8], required: usize) -> Result<(), TlsParseError> {
if bytes.len() < required {
Err(TlsParseError::Incomplete {
required,
available: bytes.len(),
})
} else {
Ok(())
}
}
fn take_u8_vector<'a>(bytes: &'a [u8], offset: &mut usize) -> Result<&'a [u8], TlsParseError> {
need(&bytes[*offset..], 1)?;
let length = usize::from(bytes[*offset]);
*offset += 1;
take(bytes, offset, length)
}
fn take_u16_vector<'a>(bytes: &'a [u8], offset: &mut usize) -> Result<&'a [u8], TlsParseError> {
need(&bytes[*offset..], 2)?;
let length = usize::from(u16::from_be_bytes([bytes[*offset], bytes[*offset + 1]]));
*offset += 2;
take(bytes, offset, length)
}
fn take<'a>(bytes: &'a [u8], offset: &mut usize, length: usize) -> Result<&'a [u8], TlsParseError> {
let end = offset
.checked_add(length)
.ok_or(TlsParseError::InvalidValue)?;
need(&bytes[*offset..], length)?;
let result = &bytes[*offset..end];
*offset = end;
Ok(result)
}