feat(ui): add renderers for ja4h, http2/3 settings, ja4-quic fingerprints
FingerprintGroup switch fell through to FpGeneric (raw JSON dump) for all four new fingerprint_type values the ingester now produces. Add FpJa4h, FpHttpSettings, FpJa4Quic components and wire them into the dispatcher; also register their labels and icons in fpTypeLabel/fpTypeIcon.
This commit is contained in:
@@ -6,12 +6,16 @@ import {
|
||||
export const fpTypeLabel: Record<string, string> = {
|
||||
ja3: 'TLS FINGERPRINT',
|
||||
ja4l: 'LATENCY (JA4L)',
|
||||
ja4h: 'JA4H (HTTP)',
|
||||
ja4_quic: 'JA4-QUIC',
|
||||
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',
|
||||
http2_settings: 'HTTP/2 SETTINGS',
|
||||
http3_settings: 'HTTP/3 SETTINGS',
|
||||
spoofed_source: 'SPOOFED SOURCE IP',
|
||||
vnc_client_version: 'VNC CLIENT',
|
||||
jarm: 'JARM',
|
||||
@@ -22,12 +26,16 @@ export const fpTypeLabel: Record<string, string> = {
|
||||
export const fpTypeIcon: Record<string, React.ReactNode> = {
|
||||
ja3: <Fingerprint size={14} />,
|
||||
ja4l: <Clock size={14} />,
|
||||
ja4h: <Fingerprint size={14} />,
|
||||
ja4_quic: <Crosshair size={14} />,
|
||||
tls_resumption: <Wifi size={14} />,
|
||||
tls_certificate: <FileKey size={14} />,
|
||||
tls_certificate_active: <FileKey size={14} />,
|
||||
tls_certificate_passive: <FileKey size={14} />,
|
||||
http_useragent: <Shield size={14} />,
|
||||
http_quirks: <Fingerprint size={14} />,
|
||||
http2_settings: <Wifi size={14} />,
|
||||
http3_settings: <Wifi size={14} />,
|
||||
spoofed_source: <Crosshair size={14} />,
|
||||
vnc_client_version: <Lock size={14} />,
|
||||
jarm: <Crosshair size={14} />,
|
||||
|
||||
@@ -207,6 +207,77 @@ export const FpTcpStack: React.FC<{ p: any }> = ({ p }) => (
|
||||
</div>
|
||||
);
|
||||
|
||||
export const FpJa4h: React.FC<{ p: any }> = ({ p }) => (
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: '6px' }}>
|
||||
<HashRow label="JA4H" value={String(p.ja4h ?? '')} />
|
||||
<div style={{ display: 'flex', gap: '8px', flexWrap: 'wrap' }}>
|
||||
{p.protocol && <Tag>{String(p.protocol).toUpperCase()}</Tag>}
|
||||
{p.method && <Tag color="var(--accent-color)">{String(p.method).toUpperCase()}</Tag>}
|
||||
{p.path && <Tag>{String(p.path)}</Tag>}
|
||||
{p.remote_port && <Tag>:{p.remote_port}</Tag>}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
export const FpHttpSettings: React.FC<{ p: any }> = ({ p }) => {
|
||||
let entries: [string, unknown][] = [];
|
||||
if (p.settings) {
|
||||
try {
|
||||
const parsed = typeof p.settings === 'string' ? JSON.parse(p.settings) : p.settings;
|
||||
entries = Object.entries(parsed as Record<string, unknown>);
|
||||
} catch { /* leave entries empty */ }
|
||||
}
|
||||
let frameOrder: string[] = [];
|
||||
if (p.frame_order) {
|
||||
try {
|
||||
const parsed = typeof p.frame_order === 'string' ? JSON.parse(p.frame_order) : p.frame_order;
|
||||
if (Array.isArray(parsed)) frameOrder = parsed.map(String);
|
||||
} catch { /* leave empty */ }
|
||||
}
|
||||
return (
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: '6px' }}>
|
||||
<div style={{ display: 'flex', gap: '8px', flexWrap: 'wrap', alignItems: 'center' }}>
|
||||
{p.protocol && <Tag>{String(p.protocol).toUpperCase()}</Tag>}
|
||||
{p.remote_port && <Tag>:{p.remote_port}</Tag>}
|
||||
</div>
|
||||
{entries.length > 0 && (
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: '3px' }}>
|
||||
{entries.map(([k, v]) => (
|
||||
<div key={k} style={{ display: 'flex', gap: '8px', alignItems: 'baseline' }}>
|
||||
<span className="dim" style={{ fontSize: '0.7rem', minWidth: '180px' }}>
|
||||
{k.replace(/_/g, ' ')}
|
||||
</span>
|
||||
<span className="matrix-text" style={{ fontFamily: 'monospace', fontSize: '0.8rem' }}>
|
||||
{String(v)}
|
||||
</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
{frameOrder.length > 0 && (
|
||||
<details>
|
||||
<summary className="dim" style={{ fontSize: '0.7rem', cursor: 'pointer', letterSpacing: '1px' }}>
|
||||
FRAME ORDER
|
||||
</summary>
|
||||
<div style={{ display: 'flex', gap: '4px', flexWrap: 'wrap', marginTop: '4px' }}>
|
||||
{frameOrder.map((f, i) => <Tag key={i}>{f}</Tag>)}
|
||||
</div>
|
||||
</details>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export const FpJa4Quic: React.FC<{ p: any }> = ({ p }) => (
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: '6px' }}>
|
||||
<HashRow label="JA4-QUIC" value={String(p.ja4_quic ?? '')} />
|
||||
<div style={{ display: 'flex', gap: '8px', flexWrap: 'wrap' }}>
|
||||
{p.sni && <Tag color="var(--accent-color)">SNI: {p.sni}</Tag>}
|
||||
{p.alpn && <Tag>ALPN: {p.alpn}</Tag>}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
export const FpGeneric: React.FC<{ p: any }> = ({ p }) => (
|
||||
<div>
|
||||
{p.value ? (
|
||||
@@ -353,6 +424,11 @@ export const FingerprintGroup: React.FC<{ fpType: string; items: any[] }> = ({ f
|
||||
case 'http_quirks': return <FpHttpQuirks key={i} p={p} />;
|
||||
case 'http_useragent': return <FpUserAgent key={i} p={p} />;
|
||||
case 'spoofed_source': return <FpSpoofedSource key={i} p={p} />;
|
||||
case 'ja4h': return <FpJa4h key={i} p={p} />;
|
||||
case 'http2_settings':
|
||||
case 'http3_settings':
|
||||
return <FpHttpSettings key={i} p={p} />;
|
||||
case 'ja4_quic': return <FpJa4Quic key={i} p={p} />;
|
||||
default: return <FpGeneric key={i} p={p} />;
|
||||
}
|
||||
})}
|
||||
|
||||
Reference in New Issue
Block a user