summaryrefslogtreecommitdiff
path: root/packet-detector/src
diff options
context:
space:
mode:
authorroot <root@sg2.noml.ch>2025-12-29 22:18:04 +0800
committerroot <root@sg2.noml.ch>2025-12-29 22:18:04 +0800
commit1951b063d7ec6d6e8db8a0b5074c73f887749208 (patch)
tree6ece8dfce605fbff6eca6be4bbeb5d7904417bbb /packet-detector/src
initial commitmain
Diffstat (limited to 'packet-detector/src')
-rw-r--r--packet-detector/src/bin/tls_client.rs51
-rw-r--r--packet-detector/src/bin/tls_server.rs295
-rw-r--r--packet-detector/src/lib.rs6
-rw-r--r--packet-detector/src/main.rs150
-rw-r--r--packet-detector/src/tls_util.rs73
-rw-r--r--packet-detector/src/validator.rs64
6 files changed, 639 insertions, 0 deletions
diff --git a/packet-detector/src/bin/tls_client.rs b/packet-detector/src/bin/tls_client.rs
new file mode 100644
index 0000000..6098172
--- /dev/null
+++ b/packet-detector/src/bin/tls_client.rs
@@ -0,0 +1,51 @@
1//! mTLS test client
2
3use std::sync::Arc;
4use packet_detector::tls_util::LoggingVerifier;
5use rustls::pki_types::{CertificateDer, PrivateKeyDer, ServerName};
6use rustls::version::TLS12;
7use rustls::ClientConfig;
8use tokio::io::{AsyncReadExt, AsyncWriteExt};
9use tokio::net::TcpStream;
10use tokio_rustls::TlsConnector;
11
12const CERT: &str = "client_cert.pem";
13const KEY: &str = "client_key.pem";
14
15fn load_creds() -> Result<(Vec<CertificateDer<'static>>, PrivateKeyDer<'static>), Box<dyn std::error::Error>> {
16 let cert_pem = std::fs::read_to_string(CERT)?;
17 let key_pem = std::fs::read_to_string(KEY)?;
18 let certs = rustls_pemfile::certs(&mut cert_pem.as_bytes()).collect::<Result<Vec<_>, _>>()?;
19 let key = rustls_pemfile::private_key(&mut key_pem.as_bytes())?.ok_or("No key")?;
20 Ok((certs, key))
21}
22
23#[tokio::main]
24async fn main() -> Result<(), Box<dyn std::error::Error>> {
25 rustls::crypto::ring::default_provider().install_default().ok();
26
27 let host = std::env::args().nth(1).unwrap_or_else(|| "127.0.0.1".into());
28 let port: u16 = std::env::args().nth(2).and_then(|s| s.parse().ok()).unwrap_or(8443);
29
30 let (certs, key) = load_creds()?;
31 println!("Connecting to {}:{} with client cert", host, port);
32
33 let config = ClientConfig::builder_with_protocol_versions(&[&TLS12])
34 .dangerous()
35 .with_custom_certificate_verifier(Arc::new(LoggingVerifier))
36 .with_client_auth_cert(certs, key)?;
37
38 let stream = TcpStream::connect(format!("{}:{}", host, port)).await?;
39 let mut tls = TlsConnector::from(Arc::new(config))
40 .connect(ServerName::try_from(host.clone())?, stream).await?;
41 println!("TLS handshake complete!");
42
43 let req = format!("GET / HTTP/1.1\r\nHost: {}\r\nConnection: close\r\n\r\n", host);
44 tls.write_all(req.as_bytes()).await?;
45
46 let mut resp = Vec::new();
47 tls.read_to_end(&mut resp).await?;
48 println!("\n{}", String::from_utf8_lossy(&resp));
49
50 Ok(())
51}
diff --git a/packet-detector/src/bin/tls_server.rs b/packet-detector/src/bin/tls_server.rs
new file mode 100644
index 0000000..7f68062
--- /dev/null
+++ b/packet-detector/src/bin/tls_server.rs
@@ -0,0 +1,295 @@
1//! TLS 1.2 Test Server with Client Tracking (mTLS Enabled)
2//!
3//! A mutual TLS (mTLS) HTTPS server for testing the eBPF TLS certificate validator.
4//! Requires client certificates signed by the CA for authentication.
5//! Tracks connected clients and displays their status.
6
7use std::collections::HashMap;
8use std::env;
9use std::fs;
10use std::net::SocketAddr;
11use std::path::Path;
12use std::sync::Arc;
13
14use packet_detector::tls_util::{dn, parse_pem};
15use rcgen::{BasicConstraints, CertificateParams, IsCa, Issuer, KeyPair, KeyUsagePurpose, SanType};
16use rustls::pki_types::{CertificateDer, PrivateKeyDer};
17use rustls::server::{ServerConfig, WebPkiClientVerifier};
18use rustls::version::TLS12;
19use rustls::RootCertStore;
20use tokio::io::{AsyncReadExt, AsyncWriteExt};
21use tokio::net::TcpListener;
22use tokio::sync::RwLock;
23use tokio_rustls::TlsAcceptor;
24
25const DEFAULT_PORT: u16 = 8443;
26const CA_CERT_PATH: &str = "ca_cert.pem";
27const CA_KEY_PATH: &str = "ca_key.pem";
28const SERVER_CERT_PATH: &str = "server_cert.pem";
29const SERVER_KEY_PATH: &str = "server_key.pem";
30const CLIENT_CERT_PATH: &str = "client_cert.pem";
31const CLIENT_KEY_PATH: &str = "client_key.pem";
32
33#[derive(Clone, Debug, serde::Serialize)]
34struct Client {
35 ip: String,
36 connected_at: chrono::DateTime<chrono::Utc>,
37 #[serde(skip)]
38 last_seen: chrono::DateTime<chrono::Utc>,
39 requests: u64,
40}
41
42#[derive(Default)]
43struct State {
44 clients: HashMap<String, Client>,
45 connections: u64,
46 requests: u64,
47}
48
49/// Generate a CA certificate for signing server and client certificates
50fn generate_ca_certificate() -> Result<(String, String, KeyPair), Box<dyn std::error::Error>> {
51 println!("Generating CA certificate...");
52
53 let mut params = CertificateParams::default();
54 params.distinguished_name = dn("eBPF Test CA", "Zero Trust Network");
55 params.is_ca = IsCa::Ca(BasicConstraints::Unconstrained);
56 params.key_usages = vec![
57 KeyUsagePurpose::KeyCertSign,
58 KeyUsagePurpose::CrlSign,
59 ];
60
61 let key_pair = KeyPair::generate()?;
62 let cert = params.self_signed(&key_pair)?;
63
64 Ok((cert.pem(), key_pair.serialize_pem(), key_pair))
65}
66
67/// Generate a server certificate signed by the CA
68fn generate_server_certificate(
69 ca_cert_pem: &str,
70 ca_key_pem: &str,
71) -> Result<(String, String), Box<dyn std::error::Error>> {
72 println!("Generating server certificate signed by CA...");
73
74 let ca_key = KeyPair::from_pem(ca_key_pem)?;
75 let issuer = Issuer::from_ca_cert_pem(ca_cert_pem, ca_key)?;
76
77 let mut params = CertificateParams::default();
78 params.distinguished_name = dn("localhost", "eBPF Test Server");
79 params.subject_alt_names = vec![
80 SanType::DnsName("localhost".try_into()?),
81 SanType::IpAddress(std::net::IpAddr::V4(std::net::Ipv4Addr::new(127, 0, 0, 1))),
82 ];
83 params.key_usages = vec![
84 KeyUsagePurpose::DigitalSignature,
85 KeyUsagePurpose::KeyEncipherment,
86 ];
87
88 let key_pair = KeyPair::generate()?;
89 let cert = params.signed_by(&key_pair, &issuer)?;
90
91 Ok((cert.pem(), key_pair.serialize_pem()))
92}
93
94/// Generate a client certificate signed by the CA
95fn generate_client_certificate(
96 client_name: &str,
97 ca_cert_pem: &str,
98 ca_key_pem: &str,
99) -> Result<(String, String), Box<dyn std::error::Error>> {
100 println!("Generating client certificate for: {}", client_name);
101
102 let ca_key = KeyPair::from_pem(ca_key_pem)?;
103 let issuer = Issuer::from_ca_cert_pem(ca_cert_pem, ca_key)?;
104
105 let mut params = CertificateParams::default();
106 params.distinguished_name = dn(client_name, "eBPF Test Client");
107 params.key_usages = vec![
108 KeyUsagePurpose::DigitalSignature,
109 ];
110
111 let key_pair = KeyPair::generate()?;
112 let cert = params.signed_by(&key_pair, &issuer)?;
113
114 Ok((cert.pem(), key_pair.serialize_pem()))
115}
116
117/// PKI setup result
118struct PkiSetup {
119 ca_cert_pem: String,
120 server_cert: Vec<CertificateDer<'static>>,
121 server_key: PrivateKeyDer<'static>,
122}
123
124/// Load or generate the full PKI (CA, server cert, client cert)
125fn setup_pki() -> Result<PkiSetup, Box<dyn std::error::Error>> {
126 let ca_cert_pem: String;
127 let ca_key_pem: String;
128
129 // Load or generate CA
130 if Path::new(CA_CERT_PATH).exists() && Path::new(CA_KEY_PATH).exists() {
131 println!("Loading existing CA from {} and {}", CA_CERT_PATH, CA_KEY_PATH);
132 ca_cert_pem = fs::read_to_string(CA_CERT_PATH)?;
133 ca_key_pem = fs::read_to_string(CA_KEY_PATH)?;
134 } else {
135 println!("Generating new PKI infrastructure...");
136 let (cert, key, _) = generate_ca_certificate()?;
137 fs::write(CA_CERT_PATH, &cert)?;
138 fs::write(CA_KEY_PATH, &key)?;
139 println!("Saved CA to {} and {}", CA_CERT_PATH, CA_KEY_PATH);
140 ca_cert_pem = cert;
141 ca_key_pem = key;
142 }
143
144 // Load or generate server certificate
145 let server_cert_pem: String;
146 let server_key_pem: String;
147
148 if Path::new(SERVER_CERT_PATH).exists() && Path::new(SERVER_KEY_PATH).exists() {
149 println!("Loading existing server certificate...");
150 server_cert_pem = fs::read_to_string(SERVER_CERT_PATH)?;
151 server_key_pem = fs::read_to_string(SERVER_KEY_PATH)?;
152 } else {
153 let (cert, key) = generate_server_certificate(&ca_cert_pem, &ca_key_pem)?;
154 fs::write(SERVER_CERT_PATH, &cert)?;
155 fs::write(SERVER_KEY_PATH, &key)?;
156 println!("Saved server cert to {} and {}", SERVER_CERT_PATH, SERVER_KEY_PATH);
157 server_cert_pem = cert;
158 server_key_pem = key;
159 }
160
161 // Load or generate client certificate
162 if !Path::new(CLIENT_CERT_PATH).exists() || !Path::new(CLIENT_KEY_PATH).exists() {
163 let (cert, key) = generate_client_certificate("test-client", &ca_cert_pem, &ca_key_pem)?;
164 fs::write(CLIENT_CERT_PATH, &cert)?;
165 fs::write(CLIENT_KEY_PATH, &key)?;
166 println!("Saved client cert to {} and {}", CLIENT_CERT_PATH, CLIENT_KEY_PATH);
167 }
168
169 // Parse certificates
170 let server_certs = parse_pem(&server_cert_pem)?;
171
172 let server_key = rustls_pemfile::private_key(&mut server_key_pem.as_bytes())?
173 .ok_or("No server private key found")?;
174
175 Ok(PkiSetup {
176 ca_cert_pem,
177 server_cert: server_certs,
178 server_key,
179 })
180}
181
182fn parse_request(data: &[u8]) -> (&str, Option<String>) {
183 let req = std::str::from_utf8(data).unwrap_or("");
184 let first_line = req.lines().next().unwrap_or("");
185
186 // Check X-Client-Name header
187 let client = req.lines()
188 .find(|l| l.to_lowercase().starts_with("x-client-name:"))
189 .map(|l| l.split(':').nth(1).unwrap_or("").trim().to_string())
190 .or_else(|| {
191 // Check ?client= query param
192 first_line.find("client=").map(|i| {
193 let rest = &first_line[i + 7..];
194 rest[..rest.find(|c| c == '&' || c == ' ').unwrap_or(rest.len())].to_string()
195 })
196 });
197
198 (first_line, client)
199}
200
201async fn handle_connection(
202 mut stream: tokio_rustls::server::TlsStream<tokio::net::TcpStream>,
203 peer: SocketAddr,
204 state: Arc<RwLock<State>>,
205) {
206 state.write().await.connections += 1;
207
208 let mut buf = vec![0u8; 4096];
209 let n = match stream.read(&mut buf).await {
210 Ok(0) => return,
211 Ok(n) => n,
212 Err(_) => return,
213 };
214
215 let (path, client_name) = parse_request(&buf[..n]);
216
217 // Track client if name provided
218 if let Some(name) = &client_name {
219 let mut s = state.write().await;
220 s.requests += 1;
221 let now = chrono::Utc::now();
222 s.clients.entry(name.clone())
223 .and_modify(|c| { c.last_seen = now; c.requests += 1; })
224 .or_insert(Client {
225 ip: peer.ip().to_string(),
226 connected_at: now,
227 last_seen: now,
228 requests: 1,
229 });
230 }
231
232 // Route request
233 let (ctype, body) = if path.contains("/status") || path.contains("/clients") {
234 let s = state.read().await;
235 ("application/json", serde_json::json!({
236 "clients": s.clients.len(),
237 "connections": s.connections,
238 "requests": s.requests,
239 "list": &s.clients
240 }).to_string())
241 } else if path.contains("/register") {
242 ("application/json", serde_json::json!({
243 "status": "ok",
244 "client": client_name.as_deref().unwrap_or("unknown")
245 }).to_string())
246 } else {
247 ("text/plain", format!("TLS Server OK - {} clients", state.read().await.clients.len()))
248 };
249
250 let resp = format!(
251 "HTTP/1.1 200 OK\r\nContent-Type: {}\r\nContent-Length: {}\r\nConnection: close\r\n\r\n{}",
252 ctype, body.len(), body
253 );
254 let _ = stream.write_all(resp.as_bytes()).await;
255 let _ = stream.shutdown().await;
256}
257
258
259#[tokio::main]
260async fn main() -> Result<(), Box<dyn std::error::Error>> {
261 rustls::crypto::ring::default_provider().install_default().ok();
262
263 let port: u16 = env::args().nth(1).and_then(|s| s.parse().ok()).unwrap_or(DEFAULT_PORT);
264
265 let pki = setup_pki()?;
266
267 // Build root store with CA
268 let mut root_store = RootCertStore::empty();
269 for cert in parse_pem(&pki.ca_cert_pem)? {
270 root_store.add(cert)?;
271 }
272
273 let verifier = WebPkiClientVerifier::builder(Arc::new(root_store)).build()?;
274 let config = ServerConfig::builder_with_protocol_versions(&[&TLS12])
275 .with_client_cert_verifier(verifier)
276 .with_single_cert(pki.server_cert, pki.server_key)?;
277
278 let acceptor = TlsAcceptor::from(Arc::new(config));
279 let listener = TcpListener::bind(SocketAddr::from(([0, 0, 0, 0], port))).await?;
280 let state = Arc::new(RwLock::new(State::default()));
281
282 println!("mTLS server on :{} (endpoints: /, /status, /register)", port);
283 println!("Test: curl -k --tlsv1.2 --cert {} --key {} https://127.0.0.1:{}", CLIENT_CERT_PATH, CLIENT_KEY_PATH, port);
284
285 loop {
286 let Ok((stream, peer)) = listener.accept().await else { continue };
287 let acceptor = acceptor.clone();
288 let state = state.clone();
289 tokio::spawn(async move {
290 if let Ok(tls) = acceptor.accept(stream).await {
291 handle_connection(tls, peer, state).await;
292 }
293 });
294 }
295}
diff --git a/packet-detector/src/lib.rs b/packet-detector/src/lib.rs
new file mode 100644
index 0000000..a3a8ac1
--- /dev/null
+++ b/packet-detector/src/lib.rs
@@ -0,0 +1,6 @@
1//! Packet Detector Library
2//!
3//! Shared utilities for TLS certificate validation.
4
5pub mod tls_util;
6pub mod validator;
diff --git a/packet-detector/src/main.rs b/packet-detector/src/main.rs
new file mode 100644
index 0000000..69cccec
--- /dev/null
+++ b/packet-detector/src/main.rs
@@ -0,0 +1,150 @@
1//! TLS Certificate Validator / UDP Magic Detector - eBPF-based
2
3use std::collections::HashSet;
4use std::mem::size_of;
5use std::net::Ipv4Addr;
6
7use anyhow::{Context, Result};
8use aya::maps::{HashMap as AyaHashMap, RingBuf};
9use aya::programs::{Xdp, XdpFlags};
10use aya::{include_bytes_aligned, Bpf};
11use log::{info, warn};
12use tls_parser::{parse_tls_plaintext, TlsMessage, TlsMessageHandshake};
13use tokio::signal;
14
15use packet_detector::validator::CertValidator;
16
17// this has to be the exact same as the struct in kernelspace
18#[repr(C)]
19#[derive(Clone, Copy, PartialEq, Eq, Hash)]
20struct ConnKey {
21 port_lo: u16,
22 port_hi: u16,
23}
24
25unsafe impl aya::Pod for ConnKey {}
26
27fn make_conn_key(src_port: u16, dst_port: u16) -> ConnKey {
28 if src_port < dst_port {
29 ConnKey { port_lo: src_port, port_hi: dst_port }
30 } else {
31 ConnKey { port_lo: dst_port, port_hi: src_port }
32 }
33}
34
35// this has to be the exact same as the struct in kernelspace
36#[repr(C)]
37#[derive(Clone, Copy)]
38struct Event {
39 src_ip: u32,
40 dst_ip: u32,
41 src_port: u16,
42 dst_port: u16,
43 tls_len: u16,
44 _pad: u16,
45}
46
47unsafe impl aya::Pod for Event {}
48
49const EVENT_SIZE: usize = size_of::<Event>();
50
51fn ip(n: u32) -> Ipv4Addr {
52 Ipv4Addr::from(n.to_be_bytes())
53}
54
55fn extract_certs(tls_data: &[u8]) -> Option<Vec<Vec<u8>>> {
56 if tls_data.len() < 6 || tls_data[0] != 0x16 || tls_data[5] != 0x0B { return None; }
57 let (_, rec) = parse_tls_plaintext(tls_data).ok()?;
58 for msg in &rec.msg {
59 if let TlsMessage::Handshake(TlsMessageHandshake::Certificate(c)) = msg {
60 return Some(c.cert_chain.iter().map(|x| x.data.to_vec()).collect());
61 }
62 }
63 None
64}
65
66enum Decision {
67 Allow(ConnKey),
68 Block(ConnKey),
69 Skip,
70}
71
72fn handle_event(data: &[u8], validator: Option<&CertValidator>) -> Decision {
73 if data.len() < EVENT_SIZE { return Decision::Skip; }
74 let ev: Event = unsafe { std::ptr::read(data.as_ptr() as *const _) };
75 let addr = format!("{}:{} -> {}:{}", ip(ev.src_ip), ev.src_port, ip(ev.dst_ip), ev.dst_port);
76 let conn_key = make_conn_key(ev.src_port, ev.dst_port);
77
78 if ev.tls_len == 0 {
79 info!("UDP magic from {}", addr);
80 return Decision::Allow(conn_key);
81 }
82
83 let Some(v) = validator else { return Decision::Skip };
84 let end = EVENT_SIZE + ev.tls_len as usize;
85 if end > data.len() { return Decision::Skip; }
86 let Some(certs) = extract_certs(&data[EVENT_SIZE..end]) else { return Decision::Skip };
87 let result = v.validate(&certs);
88 info!("{}: {}", addr, result.subject);
89
90 if result.valid {
91 info!("ALLOW conn {}:{} (signed by {})", conn_key.port_lo, conn_key.port_hi, result.issuer);
92 Decision::Allow(conn_key)
93 } else {
94 warn!("BLOCK conn {}:{} - {}", conn_key.port_lo, conn_key.port_hi, result.error.unwrap_or_default());
95 Decision::Block(conn_key)
96 }
97}
98
99#[tokio::main]
100async fn main() -> Result<()> {
101 rustls::crypto::ring::default_provider().install_default().ok();
102 env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("info")).init();
103
104 let args: Vec<String> = std::env::args().collect();
105 if args.len() < 2 {
106 eprintln!("Usage: {} <interface> [ca-cert.pem]", args[0]);
107 std::process::exit(1);
108 }
109
110 let iface = &args[1];
111 let validator = args.get(2).map(|p| CertValidator::with_ca_file(p)).transpose()?;
112 info!("Mode: {}", if validator.is_some() { "TLS cert validation" } else { "UDP magic detection" });
113
114 let mut bpf = Bpf::load(include_bytes_aligned!("../../target/bpfel-unknown-none/release/packet-detector"))?;
115 let program: &mut Xdp = bpf.program_mut("packet_detector").unwrap().try_into()?;
116 program.load()?;
117 program.attach(iface, XdpFlags::default()).context("XDP attach failed")?;
118 info!("XDP attached to {}", iface);
119
120 let mut allowed: AyaHashMap<_, ConnKey, u8> = AyaHashMap::try_from(bpf.take_map("ALLOWED_CONNS").unwrap())?;
121 let mut blocked: AyaHashMap<_, ConnKey, u8> = AyaHashMap::try_from(bpf.take_map("BLOCKED_CONNS").unwrap())?;
122 let mut ring: RingBuf<_> = RingBuf::try_from(bpf.take_map("TLS_EVENTS").unwrap())?;
123 let mut allowed_count = 0u32;
124 let mut blocked_count = 0u32;
125
126 println!("\nRunning on {} - Ctrl+C to stop\n", iface);
127
128 loop {
129 tokio::select! {
130 _ = signal::ctrl_c() => break,
131 _ = tokio::time::sleep(tokio::time::Duration::from_millis(10)) => {
132 while let Some(item) = ring.next() {
133 match handle_event(item.as_ref(), validator.as_ref()) {
134 Decision::Allow(key) => {
135 allowed.insert(key, 1, 0)?;
136 allowed_count += 1;
137 }
138 Decision::Block(key) => {
139 blocked.insert(key, 1, 0)?;
140 blocked_count += 1;
141 }
142 Decision::Skip => {}
143 }
144 }
145 }
146 }
147 }
148 println!("\nAllowed: {}, Blocked: {}", allowed_count, blocked_count);
149 Ok(())
150}
diff --git a/packet-detector/src/tls_util.rs b/packet-detector/src/tls_util.rs
new file mode 100644
index 0000000..456991b
--- /dev/null
+++ b/packet-detector/src/tls_util.rs
@@ -0,0 +1,73 @@
1use rcgen::{DistinguishedName, DnType};
2use rustls::client::danger::{HandshakeSignatureValid, ServerCertVerified, ServerCertVerifier};
3use rustls::pki_types::{CertificateDer, ServerName, UnixTime};
4use rustls::{DigitallySignedStruct, Error, SignatureScheme};
5use sha2::{Digest, Sha256};
6use x509_parser::prelude::*;
7
8/// SHA256 fingerprint (truncated to 16 bytes by default)
9pub fn fingerprint(cert: &CertificateDer<'_>, full: bool) -> String {
10 let hash = Sha256::digest(cert.as_ref());
11 if full { hex::encode(hash) } else { hex::encode(&hash[..16]) }
12}
13
14pub fn dn(cn: &str, org: &str) -> DistinguishedName {
15 let mut d = DistinguishedName::new();
16 d.push(DnType::CommonName, cn);
17 d.push(DnType::OrganizationName, org);
18 d.push(DnType::CountryName, "US");
19 d
20}
21
22/// Parse certs from PEM
23pub fn parse_pem(pem: &str) -> Result<Vec<CertificateDer<'static>>, std::io::Error> {
24 rustls_pemfile::certs(&mut pem.as_bytes()).collect()
25}
26
27fn schemes() -> Vec<SignatureScheme> {
28 rustls::crypto::ring::default_provider()
29 .signature_verification_algorithms
30 .supported_schemes()
31}
32
33/// Macro to implement the boilerplate verifier methods
34macro_rules! impl_verifier_base {
35 () => {
36 fn verify_tls12_signature(&self, _: &[u8], _: &CertificateDer<'_>, _: &DigitallySignedStruct) -> Result<HandshakeSignatureValid, Error> {
37 Ok(HandshakeSignatureValid::assertion())
38 }
39 fn verify_tls13_signature(&self, _: &[u8], _: &CertificateDer<'_>, _: &DigitallySignedStruct) -> Result<HandshakeSignatureValid, Error> {
40 Ok(HandshakeSignatureValid::assertion())
41 }
42 fn supported_verify_schemes(&self) -> Vec<SignatureScheme> {
43 schemes()
44 }
45 };
46}
47
48/// Accepts all certificates, logs info
49#[derive(Debug)]
50pub struct LoggingVerifier;
51
52impl ServerCertVerifier for LoggingVerifier {
53 fn verify_server_cert(&self, cert: &CertificateDer<'_>, intermediates: &[CertificateDer<'_>], _: &ServerName<'_>, _: &[u8], _: UnixTime) -> Result<ServerCertVerified, Error> {
54 println!("\n=== Server Certificate ===");
55 match X509Certificate::from_der(cert.as_ref()) {
56 Ok((_, x)) => {
57 println!("Subject: {}", x.subject());
58 println!("Issuer: {}", x.issuer());
59 println!("SHA256: {}", fingerprint(cert, true));
60 if x.subject() == x.issuer() { println!("Type: Self-Signed"); }
61 }
62 Err(e) => println!("Parse failed: {}", e),
63 }
64 for (i, c) in intermediates.iter().enumerate() {
65 if let Ok((_, x)) = X509Certificate::from_der(c.as_ref()) {
66 println!("Intermediate #{}: {}", i + 1, x.subject());
67 }
68 }
69 println!("Chain length: {}\n", 1 + intermediates.len());
70 Ok(ServerCertVerified::assertion())
71 }
72 impl_verifier_base!();
73}
diff --git a/packet-detector/src/validator.rs b/packet-detector/src/validator.rs
new file mode 100644
index 0000000..92e64d7
--- /dev/null
+++ b/packet-detector/src/validator.rs
@@ -0,0 +1,64 @@
1//! Certificate chain validation - signature only
2
3use anyhow::{anyhow, Result};
4use rustls::pki_types::CertificateDer;
5use x509_parser::prelude::*;
6
7pub struct ValidationResult {
8 pub valid: bool,
9 pub subject: String,
10 pub issuer: String,
11 pub error: Option<String>,
12}
13
14impl ValidationResult {
15 fn fail(subject: String, issuer: String, err: impl ToString) -> Self {
16 Self { valid: false, subject, issuer, error: Some(err.to_string()) }
17 }
18}
19
20pub struct CertValidator {
21 ca_der: Vec<u8>,
22}
23
24impl CertValidator {
25 pub fn with_ca_file(path: &str) -> Result<Self> {
26 let pem = std::fs::read_to_string(path)?;
27 let der = rustls_pemfile::certs(&mut pem.as_bytes())
28 .next()
29 .ok_or_else(|| anyhow!("No cert in PEM"))??;
30 Ok(Self { ca_der: der.to_vec() })
31 }
32
33 pub fn validate(&self, chain: &[Vec<u8>]) -> ValidationResult {
34 let Some(ee_der) = chain.first() else {
35 return ValidationResult::fail(String::new(), String::new(), "Empty chain");
36 };
37
38 let (subject, issuer) = match X509Certificate::from_der(ee_der) {
39 Ok((_, c)) => (c.subject().to_string(), c.issuer().to_string()),
40 Err(e) => return ValidationResult::fail(String::new(), String::new(), format!("{e:?}")),
41 };
42
43 let ca = CertificateDer::from(self.ca_der.clone());
44 let anchor = match webpki::anchor_from_trusted_cert(&ca) {
45 Ok(a) => a,
46 Err(e) => return ValidationResult::fail(subject, issuer, format!("CA: {e:?}")),
47 };
48
49 let cert = CertificateDer::from(ee_der.clone());
50 let ee = match webpki::EndEntityCert::try_from(&cert) {
51 Ok(c) => c,
52 Err(e) => return ValidationResult::fail(subject, issuer, format!("{e:?}")),
53 };
54
55 let intermediates: Vec<_> = chain[1..].iter().map(|c| CertificateDer::from(c.clone())).collect();
56 let algos = webpki::ALL_VERIFICATION_ALGS;
57 let time = webpki::types::UnixTime::since_unix_epoch(std::time::Duration::from_secs(4102444800)); // 2100
58
59 match ee.verify_for_usage(algos, &[anchor], &intermediates, time, webpki::KeyUsage::client_auth(), None, None) {
60 Ok(_) => ValidationResult { valid: true, subject, issuer, error: None },
61 Err(e) => ValidationResult::fail(subject, issuer, format!("{e:?}")),
62 }
63 }
64}