Skip to main content

huginn_net_tls/output/
mod.rs

1use crate::fingerprint::ObservableTlsClient;
2use std::fmt;
3use std::fmt::Formatter;
4
5#[derive(Debug, Clone, PartialEq)]
6#[cfg_attr(feature = "json", derive(serde::Serialize))]
7pub struct IpPort {
8    pub ip: std::net::IpAddr,
9    pub port: u16,
10}
11
12impl IpPort {
13    pub fn new(ip: std::net::IpAddr, port: u16) -> Self {
14        Self { ip, port }
15    }
16}
17
18/// Holds information derived from analyzing TLS ClientHello packets.
19///
20/// This structure contains details about the TLS client based on its ClientHello packet,
21/// including the JA4 Payload and extracted TLS parameters.
22#[derive(Debug)]
23#[cfg_attr(feature = "json", derive(serde::Serialize))]
24pub struct TlsClientOutput {
25    /// The source IP address and port of the client sending the ClientHello.
26    pub source: IpPort,
27    /// The destination IP address and port of the server receiving the ClientHello.
28    pub destination: IpPort,
29    /// The raw TLS signature extracted from the ClientHello packet.
30    #[cfg_attr(feature = "json", serde(serialize_with = "serialize_tls_client"))]
31    pub sig: ObservableTlsClient,
32}
33
34#[cfg(feature = "json")]
35fn serialize_tls_client<S: serde::Serializer>(
36    val: &ObservableTlsClient,
37    s: S,
38) -> Result<S::Ok, S::Error> {
39    use serde::ser::SerializeMap;
40    let mut map = s.serialize_map(None)?;
41    map.serialize_entry("sni", &val.sni)?;
42    map.serialize_entry("version", &val.version.to_string())?;
43    map.serialize_entry("alpn", &val.alpn)?;
44    map.serialize_entry("ja4", val.ja4.full.value())?;
45    map.serialize_entry("ja4_r", val.ja4.raw.value())?;
46    map.serialize_entry("ja4_o", val.ja4_original.full.value())?;
47    map.serialize_entry("ja4_or", val.ja4_original.raw.value())?;
48    #[cfg(feature = "stable-v1")]
49    map.serialize_entry("ja4_s1", val.ja4_stable_v1.full.value())?;
50    #[cfg(feature = "stable-v1")]
51    map.serialize_entry("ja4_s1r", val.ja4_stable_v1.raw.value())?;
52    map.end()
53}
54
55impl fmt::Display for TlsClientOutput {
56    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
57        write!(
58            f,
59            "[TLS Client] {}:{} → {}:{}\n\
60              SNI:     {}\n\
61              Version: TLS {}\n\
62              JA4:     {}\n\
63              JA4_r:   {}\n\
64              JA4_o:   {}\n\
65              JA4_or:  {}\n",
66            self.source.ip,
67            self.source.port,
68            self.destination.ip,
69            self.destination.port,
70            self.sig.sni.as_deref().unwrap_or("none"),
71            self.sig.version,
72            self.sig.ja4.full.value(),
73            self.sig.ja4.raw.value(),
74            self.sig.ja4_original.full.value(),
75            self.sig.ja4_original.raw.value(),
76        )?;
77        #[cfg(feature = "stable-v1")]
78        write!(
79            f,
80            "JA4_s1:  {}\n\
81              JA4_s1r: {}\n",
82            self.sig.ja4_stable_v1.full.value(),
83            self.sig.ja4_stable_v1.raw.value(),
84        )?;
85        Ok(())
86    }
87}