feat: JA3 fingerprinting, SSL correlation fix, ML pipeline overhaul, E2E test infra
ja4ebpf: - Add JA3 raw + MD5 hash fingerprinting (ComputeJA3 in TLS parser) - Fix accept4 port double-swap bug (__builtin_bswap16 on already-host-order value) - Fix scheme override bug in ClickHouse writer (HTTP block clearing HTTPS) - Add HTTP/2 passive fingerprinting (Akamai H2 FP, SETTINGS, pseudo-header order) - Enrich ClickHouse schema with IP/TCP metadata, H2 settings, Sec-* headers - Ensure maximum data completeness: all available L3/L4, TLS, HTTP fields emitted bot-detector: - Replace logistic regression with MLP fusion classifier - Replace KS drift detection with ADWIN online learning - Replace NetworkX/Louvain with PyTorch Geometric GraphSAGE for fleet detection - Replace autoencoder with RealNVP normalizing flow + SessionTransformer embeddings infra: - Add distributed E2E test infrastructure (4 VMs: endpoints + analysis) - Add Vagrant provisioning for analysis VM, e2e Makefile targets, run scripts docs: - Restructure thesis into chapter files with corrected references - Add E2E testing documentation - Update architecture, schema, deployment, service docs Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@ -31,6 +31,8 @@ type L3L4 struct {
|
||||
type TLSInfo struct {
|
||||
ClientHelloRaw []byte // payload ClientHello brut
|
||||
JA4Hash string // empreinte JA4 calculée
|
||||
JA3Raw string // empreinte JA3 brute (format: version,ciphers,exts,groups,ecfmts)
|
||||
JA3Hash string // empreinte JA3 hash MD5
|
||||
SNI string // Server Name Indication
|
||||
ALPN []string // protocoles Application-Layer Protocol Negotiation
|
||||
TLSVersion uint16 // version TLS la plus haute annoncée
|
||||
|
||||
@ -3,6 +3,7 @@
|
||||
package parser
|
||||
|
||||
import (
|
||||
"crypto/md5"
|
||||
"crypto/sha256"
|
||||
"encoding/binary"
|
||||
"encoding/hex"
|
||||
@ -356,3 +357,81 @@ func ComputeJA4(ch *ClientHello) string {
|
||||
|
||||
return part1 + "_" + part2 + "_" + part3
|
||||
}
|
||||
|
||||
// ComputeJA3 calcule l'empreinte JA3 selon la spécification Salesforce.
|
||||
//
|
||||
// Format : TLSVersion,CipherSuites,Extensions,EllipticCurves,EllipticPointFormats
|
||||
// Chaque segment est une liste de valeurs décimales séparées par des tirets.
|
||||
// Le JA3 hash est le MD5 hex de cette chaîne.
|
||||
func ComputeJA3(ch *ClientHello) (ja3Raw string, ja3Hash string) {
|
||||
// --- Version TLS ---
|
||||
var tlsVer uint16
|
||||
for _, v := range ch.SupportedVersions {
|
||||
if !IsGREASE(v) && v > tlsVer {
|
||||
tlsVer = v
|
||||
}
|
||||
}
|
||||
if tlsVer == 0 {
|
||||
tlsVer = ch.HandshakeVersion
|
||||
}
|
||||
|
||||
// --- Cipher suites (sans GREASE) ---
|
||||
var ciphers []string
|
||||
for _, cs := range ch.CipherSuites {
|
||||
if !IsGREASE(cs) {
|
||||
ciphers = append(ciphers, fmt.Sprintf("%d", cs))
|
||||
}
|
||||
}
|
||||
|
||||
// --- Extensions (sans GREASE, sans SNI 0x0000) ---
|
||||
var exts []string
|
||||
for _, e := range ch.Extensions {
|
||||
if IsGREASE(e.Type) {
|
||||
continue
|
||||
}
|
||||
// SNI (0x0000) est inclus dans JA3
|
||||
exts = append(exts, fmt.Sprintf("%d", e.Type))
|
||||
}
|
||||
|
||||
// --- Groupes elliptiques (extension supported_groups 0x000a) ---
|
||||
var groups []string
|
||||
for _, e := range ch.Extensions {
|
||||
if e.Type == 0x000a && len(e.Data) >= 4 {
|
||||
// Format : longueur (2 octets) + liste de groupes (2 octets chacun)
|
||||
groupLen := int(binary.BigEndian.Uint16(e.Data[:2]))
|
||||
for i := 2; i+1 < len(e.Data) && i < groupLen+2; i += 2 {
|
||||
g := binary.BigEndian.Uint16(e.Data[i : i+2])
|
||||
if !IsGREASE(g) {
|
||||
groups = append(groups, fmt.Sprintf("%d", g))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// --- Formats de points elliptiques (extension ec_point_formats 0x000b) ---
|
||||
var ecPointFormats []string
|
||||
for _, e := range ch.Extensions {
|
||||
if e.Type == 0x000b && len(e.Data) >= 2 {
|
||||
fmtLen := int(e.Data[0])
|
||||
for i := 1; i < len(e.Data) && i <= fmtLen; i++ {
|
||||
ecPointFormats = append(ecPointFormats, fmt.Sprintf("%d", e.Data[i]))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// --- Assemblage JA3 raw ---
|
||||
parts := []string{
|
||||
fmt.Sprintf("%d", tlsVer),
|
||||
strings.Join(ciphers, "-"),
|
||||
strings.Join(exts, "-"),
|
||||
strings.Join(groups, "-"),
|
||||
strings.Join(ecPointFormats, "-"),
|
||||
}
|
||||
ja3Raw = strings.Join(parts, ",")
|
||||
|
||||
// --- JA3 hash = MD5 du raw ---
|
||||
md5Hash := md5.Sum([]byte(ja3Raw))
|
||||
ja3Hash = hex.EncodeToString(md5Hash[:])
|
||||
|
||||
return ja3Raw, ja3Hash
|
||||
}
|
||||
|
||||
@ -47,6 +47,8 @@ type sessionRecord struct {
|
||||
|
||||
// TLS (noms attendus par le MV)
|
||||
JA4Hash string `json:"ja4,omitempty"`
|
||||
JA3Raw string `json:"ja3,omitempty"`
|
||||
JA3Hash string `json:"ja3_hash,omitempty"`
|
||||
TLSSNI string `json:"tls_sni,omitempty"`
|
||||
TLSALPN string `json:"tls_alpn,omitempty"`
|
||||
TLSVersion string `json:"tls_version,omitempty"`
|
||||
@ -242,6 +244,8 @@ func sessionToRecord(s *correlation.SessionState) sessionRecord {
|
||||
// Champs TLS
|
||||
if s.TLS != nil {
|
||||
rec.JA4Hash = s.TLS.JA4Hash
|
||||
rec.JA3Raw = s.TLS.JA3Raw
|
||||
rec.JA3Hash = s.TLS.JA3Hash
|
||||
rec.TLSSNI = s.TLS.SNI
|
||||
rec.TLSALPN = strings.Join(s.TLS.ALPN, ",")
|
||||
rec.TLSVersion = formatTLSVersion(s.TLS.TLSVersion)
|
||||
@ -262,7 +266,9 @@ func sessionToRecord(s *correlation.SessionState) sessionRecord {
|
||||
rec.Path = last.Path
|
||||
rec.QueryString = last.QueryString
|
||||
rec.Host = last.Host
|
||||
rec.Scheme = "" // sera rempli par le dispatcher si TLS
|
||||
if last.Host != "" && s.TLS != nil {
|
||||
rec.Scheme = "https"
|
||||
}
|
||||
rec.HTTPVersion = last.HTTPVersion
|
||||
rec.StatusCode = &last.StatusCode
|
||||
rec.ResponseSize = &last.ResponseSize
|
||||
|
||||
Reference in New Issue
Block a user