use crate::Timestamp;
use crate::session::SessionParser;
use super::TlsParser;
use super::session::TlsMessage;
use super::types::{TlsConfig, TlsVersion};
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[non_exhaustive]
pub enum HandshakeOutcome {
Completed,
AlertedByServer { description: u8 },
AlertedByClient { description: u8 },
Truncated,
}
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[non_exhaustive]
pub struct TlsHandshake {
pub sni: Option<String>,
pub client_alpn: Vec<String>,
pub server_alpn: Option<String>,
pub ja3: Option<String>,
pub ja4: Option<String>,
pub version: Option<TlsVersion>,
pub cipher_suite: Option<u16>,
pub resumption_attempted: bool,
pub outcome: HandshakeOutcome,
}
impl Default for TlsHandshake {
fn default() -> Self {
Self {
sni: None,
client_alpn: Vec::new(),
server_alpn: None,
ja3: None,
ja4: None,
version: None,
cipher_suite: None,
resumption_attempted: false,
outcome: HandshakeOutcome::Truncated,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum State {
AwaitingClientHello,
AwaitingServerHello,
Completed,
}
#[derive(Debug, Clone)]
pub struct TlsHandshakeParser {
inner: TlsParser,
state: State,
accumulator: TlsHandshake,
}
impl Default for TlsHandshakeParser {
fn default() -> Self {
Self::with_config(TlsConfig {
ja3: cfg!(feature = "ja3"),
ja4: cfg!(feature = "ja4"),
..Default::default()
})
}
}
impl TlsHandshakeParser {
pub fn with_config(config: TlsConfig) -> Self {
Self {
inner: TlsParser::with_config(config),
state: State::AwaitingClientHello,
accumulator: TlsHandshake::default(),
}
}
fn process(&mut self, msgs: Vec<TlsMessage>, out: &mut Vec<TlsHandshake>) {
for msg in msgs {
match msg {
TlsMessage::ClientHello(ch) => {
self.accumulator.sni = ch.sni.clone();
self.accumulator.client_alpn = ch.alpn.clone();
self.accumulator.resumption_attempted =
ch.extension_types.iter().any(|&e| e == 41 || e == 35);
self.state = State::AwaitingServerHello;
}
TlsMessage::ServerHello(sh) => {
self.accumulator.server_alpn = sh.alpn.clone();
self.accumulator.cipher_suite = Some(sh.cipher_suite);
self.accumulator.version =
Some(sh.supported_version.unwrap_or(sh.legacy_version));
self.accumulator.outcome = HandshakeOutcome::Completed;
let done = std::mem::take(&mut self.accumulator);
out.push(done);
self.state = State::Completed;
}
TlsMessage::Alert(a) => {
if a.level == super::types::TlsAlertLevel::Fatal {
let outcome = match self.state {
State::AwaitingServerHello => HandshakeOutcome::AlertedByServer {
description: a.description,
},
_ => HandshakeOutcome::AlertedByClient {
description: a.description,
},
};
self.accumulator.outcome = outcome;
let done = std::mem::take(&mut self.accumulator);
out.push(done);
self.state = State::AwaitingClientHello;
}
}
#[cfg(feature = "ja3")]
TlsMessage::Ja3 { hash, .. } => {
self.accumulator.ja3 = Some(hash);
}
#[cfg(feature = "ja4")]
TlsMessage::Ja4 { fingerprint } => {
self.accumulator.ja4 = Some(fingerprint);
}
}
}
}
}
impl SessionParser for TlsHandshakeParser {
type Message = TlsHandshake;
fn parser_kind(&self) -> &'static str {
"tls-handshake"
}
fn feed_initiator(&mut self, bytes: &[u8], ts: Timestamp) -> Vec<Self::Message> {
let inner_out = self.inner.feed_initiator(bytes, ts);
let mut out = Vec::new();
self.process(inner_out, &mut out);
out
}
fn feed_responder(&mut self, bytes: &[u8], ts: Timestamp) -> Vec<Self::Message> {
let inner_out = self.inner.feed_responder(bytes, ts);
let mut out = Vec::new();
self.process(inner_out, &mut out);
out
}
fn fin_initiator(&mut self) -> Vec<Self::Message> {
let inner_out = self.inner.fin_initiator();
let mut out = Vec::new();
self.process(inner_out, &mut out);
if matches!(self.state, State::AwaitingServerHello) {
self.accumulator.outcome = HandshakeOutcome::Truncated;
let done = std::mem::take(&mut self.accumulator);
out.push(done);
self.state = State::AwaitingClientHello;
}
out
}
fn fin_responder(&mut self) -> Vec<Self::Message> {
let inner_out = self.inner.fin_responder();
let mut out = Vec::new();
self.process(inner_out, &mut out);
out
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn default_has_session_parser_shape() {
fn assert_impls<P: SessionParser>() {}
assert_impls::<TlsHandshakeParser>();
}
#[test]
fn parser_kind_label() {
let p = TlsHandshakeParser::default();
assert_eq!(p.parser_kind(), "tls-handshake");
}
}