use base64::{Engine as _, engine::general_purpose};
use serde::{Deserialize, Serialize};
pub struct HelloExporter;
impl HelloExporter {
pub fn export_client_hello(hello: &[u8], format: ExportFormat) -> String {
Self::export_bytes(hello, format)
}
pub fn export_server_hello(hello: &[u8], format: ExportFormat) -> String {
Self::export_bytes(hello, format)
}
fn export_bytes(data: &[u8], format: ExportFormat) -> String {
match format {
ExportFormat::Hex => hex::encode(data),
ExportFormat::Base64 => general_purpose::STANDARD.encode(data),
ExportFormat::HexDump => Self::hex_dump(data),
ExportFormat::Binary => {
String::from_utf8_lossy(data).to_string()
}
}
}
fn hex_dump(data: &[u8]) -> String {
let mut output = String::new();
let mut offset = 0;
while offset < data.len() {
output.push_str(&format!("{:08x}: ", offset));
let end = (offset + 16).min(data.len());
for (i, byte) in data.iter().enumerate().take(end).skip(offset) {
output.push_str(&format!("{:02x} ", byte));
if i == offset + 7 {
output.push(' '); }
}
let padding = 16 - (end - offset);
for _ in 0..padding {
output.push_str(" ");
}
if padding > 8 {
output.push(' ');
}
output.push_str(" |");
for byte in data.iter().take(end).skip(offset) {
let c = *byte;
if (32..=126).contains(&c) {
output.push(c as char);
} else {
output.push('.');
}
}
output.push_str("|\n");
offset = end;
}
output
}
pub fn export_handshake(
client_hello: &[u8],
server_hello: &[u8],
format: ExportFormat,
) -> HandshakeExport {
HandshakeExport {
client_hello: ClientHelloExport {
hex: hex::encode(client_hello),
base64: general_purpose::STANDARD.encode(client_hello),
length: client_hello.len(),
format: format.clone(),
},
server_hello: ServerHelloExport {
hex: hex::encode(server_hello),
base64: general_purpose::STANDARD.encode(server_hello),
length: server_hello.len(),
format: format.clone(),
},
}
}
pub fn identify_record_type(data: &[u8]) -> Option<TlsRecordType> {
if data.is_empty() {
return None;
}
match data[0] {
0x14 => Some(TlsRecordType::ChangeCipherSpec),
0x15 => Some(TlsRecordType::Alert),
0x16 => Some(TlsRecordType::Handshake),
0x17 => Some(TlsRecordType::ApplicationData),
_ => Some(TlsRecordType::Unknown(data[0])),
}
}
pub fn identify_handshake_type(data: &[u8]) -> Option<HandshakeType> {
if data.len() < 6 {
return None;
}
match data[5] {
0x00 => Some(HandshakeType::HelloRequest),
0x01 => Some(HandshakeType::ClientHello),
0x02 => Some(HandshakeType::ServerHello),
0x0b => Some(HandshakeType::Certificate),
0x0c => Some(HandshakeType::ServerKeyExchange),
0x0d => Some(HandshakeType::CertificateRequest),
0x0e => Some(HandshakeType::ServerHelloDone),
0x0f => Some(HandshakeType::CertificateVerify),
0x10 => Some(HandshakeType::ClientKeyExchange),
0x14 => Some(HandshakeType::Finished),
_ => Some(HandshakeType::Unknown(data[5])),
}
}
pub fn extract_tls_version(data: &[u8]) -> Option<(u8, u8)> {
if data.len() < 3 {
return None;
}
Some((data[1], data[2]))
}
pub fn version_string(major: u8, minor: u8) -> String {
match (major, minor) {
(3, 0) => "SSL 3.0".to_string(),
(3, 1) => "TLS 1.0".to_string(),
(3, 2) => "TLS 1.1".to_string(),
(3, 3) => "TLS 1.2".to_string(),
(3, 4) => "TLS 1.3".to_string(),
_ => format!("Unknown ({}.{})", major, minor),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum ExportFormat {
Hex,
Base64,
HexDump,
Binary,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct HandshakeExport {
pub client_hello: ClientHelloExport,
pub server_hello: ServerHelloExport,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ClientHelloExport {
pub hex: String,
pub base64: String,
pub length: usize,
pub format: ExportFormat,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ServerHelloExport {
pub hex: String,
pub base64: String,
pub length: usize,
pub format: ExportFormat,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum TlsRecordType {
ChangeCipherSpec,
Alert,
Handshake,
ApplicationData,
Unknown(u8),
}
impl std::fmt::Display for TlsRecordType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
TlsRecordType::ChangeCipherSpec => write!(f, "ChangeCipherSpec"),
TlsRecordType::Alert => write!(f, "Alert"),
TlsRecordType::Handshake => write!(f, "Handshake"),
TlsRecordType::ApplicationData => write!(f, "ApplicationData"),
TlsRecordType::Unknown(val) => write!(f, "Unknown(0x{:02x})", val),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum HandshakeType {
HelloRequest,
ClientHello,
ServerHello,
Certificate,
ServerKeyExchange,
CertificateRequest,
ServerHelloDone,
CertificateVerify,
ClientKeyExchange,
Finished,
Unknown(u8),
}
impl std::fmt::Display for HandshakeType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
HandshakeType::HelloRequest => write!(f, "HelloRequest"),
HandshakeType::ClientHello => write!(f, "ClientHello"),
HandshakeType::ServerHello => write!(f, "ServerHello"),
HandshakeType::Certificate => write!(f, "Certificate"),
HandshakeType::ServerKeyExchange => write!(f, "ServerKeyExchange"),
HandshakeType::CertificateRequest => write!(f, "CertificateRequest"),
HandshakeType::ServerHelloDone => write!(f, "ServerHelloDone"),
HandshakeType::CertificateVerify => write!(f, "CertificateVerify"),
HandshakeType::ClientKeyExchange => write!(f, "ClientKeyExchange"),
HandshakeType::Finished => write!(f, "Finished"),
HandshakeType::Unknown(val) => write!(f, "Unknown(0x{:02x})", val),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_export_hex() {
let data = vec![0x16, 0x03, 0x01, 0x00, 0x05];
let hex = HelloExporter::export_client_hello(&data, ExportFormat::Hex);
assert_eq!(hex, "1603010005");
}
#[test]
fn test_export_base64() {
let data = vec![0x16, 0x03, 0x01, 0x00, 0x05];
let base64 = HelloExporter::export_client_hello(&data, ExportFormat::Base64);
assert!(!base64.is_empty());
}
#[test]
fn test_hex_dump() {
let data = vec![0x16, 0x03, 0x01, 0x00, 0x05, 0x01, 0x00, 0x00, 0x01, 0x03];
let dump = HelloExporter::export_client_hello(&data, ExportFormat::HexDump);
assert!(dump.contains("00000000:"));
assert!(dump.contains("16 03 01"));
}
#[test]
fn test_identify_record_type() {
let handshake = vec![0x16, 0x03, 0x01];
assert_eq!(
HelloExporter::identify_record_type(&handshake),
Some(TlsRecordType::Handshake)
);
let alert = vec![0x15, 0x03, 0x01];
assert_eq!(
HelloExporter::identify_record_type(&alert),
Some(TlsRecordType::Alert)
);
}
#[test]
fn test_extract_tls_version() {
let data = vec![0x16, 0x03, 0x03]; let version = HelloExporter::extract_tls_version(&data);
assert_eq!(version, Some((3, 3)));
let version_str = HelloExporter::version_string(3, 3);
assert_eq!(version_str, "TLS 1.2");
}
#[test]
fn test_version_strings() {
assert_eq!(HelloExporter::version_string(3, 0), "SSL 3.0");
assert_eq!(HelloExporter::version_string(3, 1), "TLS 1.0");
assert_eq!(HelloExporter::version_string(3, 2), "TLS 1.1");
assert_eq!(HelloExporter::version_string(3, 3), "TLS 1.2");
assert_eq!(HelloExporter::version_string(3, 4), "TLS 1.3");
}
#[test]
fn test_export_handshake() {
let client = vec![0x16, 0x03, 0x01, 0x00, 0x05];
let server = vec![0x16, 0x03, 0x03, 0x00, 0x39];
let export = HelloExporter::export_handshake(&client, &server, ExportFormat::Hex);
assert_eq!(export.client_hello.length, 5);
assert_eq!(export.server_hello.length, 5);
assert!(!export.client_hello.hex.is_empty());
assert!(!export.server_hello.hex.is_empty());
}
}