use crate::tls_reader::TlsReader;
use tokio::net::TcpStream;
const TLS_HEADER_LEN: usize = 5;
const CONTENT_TYPE_HANDSHAKE: u8 = 0x16;
const HANDSHAKE_TYPE_CLIENT_HELLO: u8 = 0x01;
const EXT_SERVER_NAME: u16 = 0x0000;
const EXT_ALPN: u16 = 0x0010;
#[derive(Debug)]
pub struct ParsedClientHello {
pub server_name: Option<String>,
pub alpn_protocols: Vec<String>,
}
pub async fn parse_client_hello(
reader: &mut TlsReader,
stream: &mut TcpStream,
) -> std::io::Result<ParsedClientHello> {
reader.ensure_bytes(stream, TLS_HEADER_LEN).await?;
let content_type = reader.read_u8()?;
if content_type != CONTENT_TYPE_HANDSHAKE {
return Err(std::io::Error::new(
std::io::ErrorKind::InvalidData,
format!("expected handshake (0x16), got 0x{:02x}", content_type),
));
}
let legacy_version_major = reader.read_u8()?;
let legacy_version_minor = reader.read_u8()?;
if legacy_version_major != 3 || legacy_version_minor > 4 {
return Err(std::io::Error::new(
std::io::ErrorKind::InvalidData,
format!(
"unexpected TLS record version {}.{}",
legacy_version_major, legacy_version_minor
),
));
}
let payload_len = reader.read_u16_be()? as usize;
reader.ensure_bytes(stream, payload_len).await?;
let handshake_type = reader.read_u8()?;
if handshake_type != HANDSHAKE_TYPE_CLIENT_HELLO {
return Err(std::io::Error::new(
std::io::ErrorKind::InvalidData,
format!("expected ClientHello (0x01), got 0x{:02x}", handshake_type),
));
}
let _handshake_len = reader.read_u24_be()?;
let _client_version_major = reader.read_u8()?;
let _client_version_minor = reader.read_u8()?;
reader.skip(32)?;
let session_id_len = reader.read_u8()? as usize;
reader.skip(session_id_len)?;
let cipher_suites_len = reader.read_u16_be()? as usize;
reader.skip(cipher_suites_len)?;
let compression_methods_len = reader.read_u8()? as usize;
reader.skip(compression_methods_len)?;
let mut server_name: Option<String> = None;
let mut alpn_protocols: Vec<String> = Vec::new();
if !reader.is_consumed() {
let extensions_len = reader.read_u16_be()? as usize;
let extensions_end = reader.position() + extensions_len;
while reader.position() < extensions_end {
let ext_type = reader.read_u16_be()?;
let ext_len = reader.read_u16_be()? as usize;
let ext_data_start = reader.position();
match ext_type {
EXT_SERVER_NAME => {
if server_name.is_some() {
return Err(std::io::Error::new(
std::io::ErrorKind::InvalidData,
"duplicate SNI extension",
));
}
let _list_len = reader.read_u16_be()?;
let name_type = reader.read_u8()?;
if name_type != 0 {
return Err(std::io::Error::new(
std::io::ErrorKind::InvalidData,
format!("expected hostname type (0), got {}", name_type),
));
}
let name_len = reader.read_u16_be()? as usize;
let name_str = reader.read_str(name_len)?;
server_name = Some(name_str.to_string());
}
EXT_ALPN => {
let alpn_list_len = reader.read_u16_be()? as usize;
let alpn_list_end = reader.position() + alpn_list_len;
while reader.position() < alpn_list_end {
let proto_len = reader.read_u8()? as usize;
let proto_str = reader.read_str(proto_len)?;
alpn_protocols.push(proto_str.to_string());
}
}
_ => {
reader.skip(ext_len)?;
}
}
let consumed = reader.position() - ext_data_start;
if consumed < ext_len {
reader.skip(ext_len - consumed)?;
} else if consumed > ext_len {
return Err(std::io::Error::new(
std::io::ErrorKind::InvalidData,
"extension parsing overrun",
));
}
}
}
Ok(ParsedClientHello {
server_name,
alpn_protocols,
})
}