From 9ab43b4ea45b0fb5752874f225afcfec5e0d9704 Mon Sep 17 00:00:00 2001 From: anti Date: Tue, 28 Apr 2026 11:23:34 -0400 Subject: [PATCH] feat(prober-cert): UI for active TLS certificate captures - FpCertificate renders the new cert_sha256 field (truncated, with full hash on hover) and a FROM line carrying the prober-side target_ip/port so the source is visible. - tls_certificate payloads split on target_ip presence: prober certs land under ACTIVE PROBES, sniffer certs under PASSIVE FINGERPRINTS. Two synthetic fpType keys (tls_certificate_active / tls_certificate_passive) drive the bucketing without disturbing the on-the-wire fingerprint_type. --- decnet_web/src/components/AttackerDetail.tsx | 39 +++++++++++++++++--- 1 file changed, 34 insertions(+), 5 deletions(-) diff --git a/decnet_web/src/components/AttackerDetail.tsx b/decnet_web/src/components/AttackerDetail.tsx index b37e1626..124bbf57 100644 --- a/decnet_web/src/components/AttackerDetail.tsx +++ b/decnet_web/src/components/AttackerDetail.tsx @@ -104,6 +104,8 @@ const fpTypeLabel: Record = { ja4l: 'LATENCY (JA4L)', tls_resumption: 'SESSION RESUMPTION', tls_certificate: 'CERTIFICATE', + tls_certificate_active: 'CERTIFICATE (ACTIVE PROBE)', + tls_certificate_passive: 'CERTIFICATE', http_useragent: 'HTTP USER-AGENT', http_quirks: 'HTTP HEADER QUIRKS', spoofed_source: 'SPOOFED SOURCE IP', @@ -118,6 +120,8 @@ const fpTypeIcon: Record = { ja4l: , tls_resumption: , tls_certificate: , + tls_certificate_active: , + tls_certificate_passive: , http_useragent: , http_quirks: , spoofed_source: , @@ -252,6 +256,22 @@ const FpCertificate: React.FC<{ p: any }> = ({ p }) => ( ))} )} + {p.cert_sha256 && ( +
+ SHA-256: + + {p.cert_sha256.slice(0, 16)}…{p.cert_sha256.slice(-8)} + +
+ )} + {p.target_ip && ( +
+ FROM: + + {p.target_ip}{p.target_port ? `:${p.target_port}` : ''} + +
+ )} ); @@ -506,7 +526,10 @@ const FingerprintGroup: React.FC<{ fpType: string; items: any[] }> = ({ fpType, case 'ja3': return ; case 'ja4l': return ; case 'tls_resumption': return ; - case 'tls_certificate': return ; + case 'tls_certificate': + case 'tls_certificate_active': + case 'tls_certificate_passive': + return ; case 'jarm': return ; case 'hassh_server': return ; case 'tcpfp': return ; @@ -1805,18 +1828,24 @@ const AttackerDetail: React.FC = () => { }) : attacker.fingerprints; - // Group fingerprints by type + // Group fingerprints by type. tls_certificate is split on the + // presence of target_ip — prober payloads carry it, sniffer + // payloads do not — so each source ends up under the right + // active/passive bucket below. const groups: Record = {}; filteredFps.forEach((fp) => { const p = getPayload(fp); - const fpType: string = p.fingerprint_type || 'unknown'; + let fpType: string = p.fingerprint_type || 'unknown'; + if (fpType === 'tls_certificate') { + fpType = p.target_ip ? 'tls_certificate_active' : 'tls_certificate_passive'; + } if (!groups[fpType]) groups[fpType] = []; groups[fpType].push(fp); }); // Active probes first, then passive, then unknown - const activeTypes = ['jarm', 'hassh_server', 'tcpfp']; - const passiveTypes = ['ja3', 'ja4l', 'tls_resumption', 'tls_certificate', 'http_useragent', 'http_quirks', 'spoofed_source', 'vnc_client_version']; + const activeTypes = ['jarm', 'hassh_server', 'tcpfp', 'tls_certificate_active']; + const passiveTypes = ['ja3', 'ja4l', 'tls_resumption', 'tls_certificate_passive', 'http_useragent', 'http_quirks', 'spoofed_source', 'vnc_client_version']; const knownTypes = [...activeTypes, ...passiveTypes]; const unknownTypes = Object.keys(groups).filter((t) => !knownTypes.includes(t));