use crate::protocol::FlowKey;
#[derive(Debug, Clone, Default, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct TlsFingerprint {
pub sni: Option<String>,
pub alpn: Option<String>,
pub ja3: Option<String>,
pub ja4: Option<String>,
pub ja4s: Option<String>,
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
pub key: Option<FlowKey>,
}
impl TlsFingerprint {
pub(crate) fn from_handshake(hs: &flowscope::tls::TlsHandshake, key: Option<FlowKey>) -> Self {
Self {
sni: hs.sni.clone(),
alpn: hs
.server_alpn
.clone()
.or_else(|| hs.client_alpn.first().cloned()),
ja3: hs.ja3.clone(),
ja4: hs.ja4.clone(),
ja4s: hs.ja4s.clone(),
key,
}
}
pub fn has_fingerprint(&self) -> bool {
self.ja3.is_some() || self.ja4.is_some() || self.ja4s.is_some()
}
}
#[cfg(test)]
mod tests {
use super::*;
use flowscope::tls::TlsHandshake;
#[test]
fn bundles_handshake_fields_and_prefers_server_alpn() {
let mut hs = TlsHandshake::default();
hs.sni = Some("example.com".to_string());
hs.client_alpn = vec!["h2".to_string(), "http/1.1".to_string()];
hs.server_alpn = Some("h2".to_string());
hs.ja3 = Some("abc".to_string());
hs.ja4 = Some("t13d…".to_string());
hs.ja4s = Some("t130200_1301_…".to_string());
let fp = TlsFingerprint::from_handshake(&hs, None);
assert_eq!(fp.sni.as_deref(), Some("example.com"));
assert_eq!(fp.alpn.as_deref(), Some("h2")); assert_eq!(fp.ja4s.as_deref(), Some("t130200_1301_…"));
assert!(fp.has_fingerprint());
}
#[test]
fn falls_back_to_first_client_alpn_when_server_did_not_choose() {
let mut hs = TlsHandshake::default();
hs.client_alpn = vec!["h2".to_string()];
hs.server_alpn = None;
let fp = TlsFingerprint::from_handshake(&hs, None);
assert_eq!(fp.alpn.as_deref(), Some("h2"));
assert!(!fp.has_fingerprint());
}
#[cfg(feature = "tokio")]
#[test]
fn on_fingerprint_builds_a_valid_monitor_and_doesnt_double_register() {
let m = crate::monitor::Monitor::builder()
.interface("lo")
.on_fingerprint(|_fp, _ctx| Ok(()))
.build();
assert!(m.is_ok(), "auto-register build failed: {:?}", m.err());
let m = crate::monitor::Monitor::builder()
.interface("lo")
.protocol::<crate::protocol::builtin::TlsHandshake>()
.on_fingerprint(|_fp, _ctx| Ok(()))
.build();
assert!(m.is_ok(), "explicit-protocol build failed: {:?}", m.err());
}
}