use crate::Result;
use crate::error::TlsError;
use serde::{Deserialize, Serialize};
use std::io::{Cursor, Read};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Extension {
pub extension_type: u16,
pub data: Vec<u8>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ServerHelloCapture {
pub version: u16,
pub random: [u8; 32],
pub session_id: Vec<u8>,
pub cipher_suite: u16,
pub compression_method: u8,
pub extensions: Vec<Extension>,
}
impl ServerHelloCapture {
pub fn parse(data: &[u8]) -> Result<Self> {
let mut cursor = Cursor::new(data);
let mut record_header = [0u8; 5];
cursor
.read_exact(&mut record_header)
.map_err(|e| TlsError::ParseError {
message: format!("Failed to read record header: {}", e),
})?;
let content_type = record_header[0];
if content_type == 0x15 {
if data.len() >= 7 {
let alert_level = data[5];
let alert_description = data[6];
return Err(TlsError::ParseError {
message: format!(
"TLS Alert received (level: {}, description: {}): {}",
alert_level,
alert_description,
Self::get_alert_description(alert_description)
),
});
} else {
return Err(TlsError::ParseError {
message: "TLS Alert received from server".to_string(),
});
}
}
if content_type != 0x16 {
return Err(TlsError::ParseError {
message: format!(
"Invalid content type: expected 0x16 (handshake), got 0x{:02x}",
content_type
),
});
}
let mut handshake_header = [0u8; 4];
cursor
.read_exact(&mut handshake_header)
.map_err(|e| TlsError::ParseError {
message: format!("Failed to read handshake header: {}", e),
})?;
let handshake_type = handshake_header[0];
if handshake_type != 0x02 {
return Err(TlsError::ParseError {
message: format!(
"Invalid handshake type: expected 0x02 (ServerHello), got 0x{:02x}",
handshake_type
),
});
}
let mut version_bytes = [0u8; 2];
cursor
.read_exact(&mut version_bytes)
.map_err(|e| TlsError::ParseError {
message: format!("Failed to read version: {}", e),
})?;
let version = u16::from_be_bytes(version_bytes);
let mut random = [0u8; 32];
cursor
.read_exact(&mut random)
.map_err(|e| TlsError::ParseError {
message: format!("Failed to read random: {}", e),
})?;
let mut session_id_len = [0u8; 1];
cursor
.read_exact(&mut session_id_len)
.map_err(|e| TlsError::ParseError {
message: format!("Failed to read session ID length: {}", e),
})?;
let session_id_len = session_id_len[0] as usize;
let mut session_id = vec![0u8; session_id_len];
if session_id_len > 0 {
cursor
.read_exact(&mut session_id)
.map_err(|e| TlsError::ParseError {
message: format!("Failed to read session ID: {}", e),
})?;
}
let mut cipher_bytes = [0u8; 2];
cursor
.read_exact(&mut cipher_bytes)
.map_err(|e| TlsError::ParseError {
message: format!("Failed to read cipher suite: {}", e),
})?;
let cipher_suite = u16::from_be_bytes(cipher_bytes);
let mut compression = [0u8; 1];
cursor
.read_exact(&mut compression)
.map_err(|e| TlsError::ParseError {
message: format!("Failed to read compression: {}", e),
})?;
let compression_method = compression[0];
let mut extensions = Vec::new();
let position = cursor.position() as usize;
if position < data.len() - 1 {
let mut ext_len_bytes = [0u8; 2];
if cursor.read_exact(&mut ext_len_bytes).is_ok() {
let extensions_length = u16::from_be_bytes(ext_len_bytes) as usize;
if extensions_length > 0 {
let ext_start = cursor.position() as usize;
let ext_end = ext_start + extensions_length;
if ext_end <= data.len() {
while (cursor.position() as usize) < ext_end {
let mut ext_type_bytes = [0u8; 2];
if cursor.read_exact(&mut ext_type_bytes).is_err() {
break;
}
let ext_type = u16::from_be_bytes(ext_type_bytes);
let mut ext_data_len_bytes = [0u8; 2];
if cursor.read_exact(&mut ext_data_len_bytes).is_err() {
break;
}
let ext_data_len = u16::from_be_bytes(ext_data_len_bytes) as usize;
let mut ext_data = vec![0u8; ext_data_len];
if ext_data_len > 0 && cursor.read_exact(&mut ext_data).is_err() {
break;
}
extensions.push(Extension {
extension_type: ext_type,
data: ext_data,
});
}
}
}
}
}
Ok(ServerHelloCapture {
version,
random,
session_id,
cipher_suite,
compression_method,
extensions,
})
}
pub fn get_extension_ids(&self) -> Vec<u16> {
self.extensions
.iter()
.map(|ext| ext.extension_type)
.collect()
}
pub fn to_bytes(&self) -> Vec<u8> {
let mut bytes = Vec::new();
bytes.extend_from_slice(&self.version.to_be_bytes());
bytes.extend_from_slice(&self.random);
bytes.push(self.session_id.len() as u8);
bytes.extend_from_slice(&self.session_id);
bytes.extend_from_slice(&self.cipher_suite.to_be_bytes());
bytes.push(self.compression_method);
if !self.extensions.is_empty() {
let ext_total_len: u16 = self
.extensions
.iter()
.map(|e| 4 + e.data.len() as u16)
.sum();
bytes.extend_from_slice(&ext_total_len.to_be_bytes());
for ext in &self.extensions {
bytes.extend_from_slice(&ext.extension_type.to_be_bytes());
bytes.extend_from_slice(&(ext.data.len() as u16).to_be_bytes());
bytes.extend_from_slice(&ext.data);
}
}
bytes
}
fn get_alert_description(alert_code: u8) -> &'static str {
match alert_code {
0 => "close_notify",
10 => "unexpected_message",
20 => "bad_record_mac",
21 => "decryption_failed",
22 => "record_overflow",
30 => "decompression_failure",
40 => "handshake_failure",
41 => "no_certificate",
42 => "bad_certificate",
43 => "unsupported_certificate",
44 => "certificate_revoked",
45 => "certificate_expired",
46 => "certificate_unknown",
47 => "illegal_parameter",
48 => "unknown_ca",
49 => "access_denied",
50 => "decode_error",
51 => "decrypt_error",
60 => "export_restriction",
70 => "protocol_version",
71 => "insufficient_security",
80 => "internal_error",
86 => "inappropriate_fallback",
90 => "user_canceled",
100 => "no_renegotiation",
109 => "missing_extension",
110 => "unsupported_extension",
111 => "certificate_unobtainable",
112 => "unrecognized_name",
113 => "bad_certificate_status_response",
114 => "bad_certificate_hash_value",
115 => "unknown_psk_identity",
116 => "certificate_required",
120 => "no_application_protocol",
_ => "unknown_alert",
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_parse_serverhello_basic() {
let data = vec![
0x16, 0x03, 0x03, 0x00, 0x31, 0x02, 0x00, 0x00, 0x2D, 0x03, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2F, 0x00, ];
let result = ServerHelloCapture::parse(&data);
assert!(result.is_ok());
let server_hello = result.expect("test assertion should succeed");
assert_eq!(server_hello.version, 0x0303); assert_eq!(server_hello.cipher_suite, 0x002F);
assert_eq!(server_hello.compression_method, 0);
assert!(server_hello.extensions.is_empty());
}
#[test]
fn test_parse_serverhello_with_extensions() {
let data = vec![
0x16, 0x03, 0x03, 0x00, 0x3B, 0x02, 0x00, 0x00, 0x37, 0x03, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0xC0, 0x2F, 0x00, 0x00, 0x08, 0xFF, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, ];
let result = ServerHelloCapture::parse(&data);
assert!(result.is_ok());
let server_hello = result.expect("test assertion should succeed");
assert_eq!(server_hello.version, 0x0303);
assert_eq!(server_hello.cipher_suite, 0xC02F);
assert_eq!(server_hello.extensions.len(), 2);
assert_eq!(server_hello.extensions[0].extension_type, 0xFF01);
assert_eq!(server_hello.extensions[1].extension_type, 0x0000);
}
}