mic~ proxy for JA4-TLS fingerprinting evasion |
| git clone https://github.com/djnnvx/mic.git |
| log | files | refs | archive | README | LICENSE |
ja4s_capture_test.go (1441B)
1 //go:build integration 2 3 package proxy 4 5 import ( 6 "bytes" 7 "net" 8 "sync" 9 10 "github.com/djnnvx/mic/fingerprint" 11 ) 12 13 // teeWriteConn tees every Write into a buffer and emits the JA4S once the 14 // buffer holds a complete ServerHello. 15 type teeWriteConn struct { 16 net.Conn 17 mu sync.Mutex 18 buf bytes.Buffer 19 ch chan<- string 20 sent bool 21 } 22 23 func (t *teeWriteConn) Write(p []byte) (int, error) { 24 t.mu.Lock() 25 if !t.sent { 26 // In client-front MitM the proxy writes a plaintext "HTTP/1.1 200" line on 27 // this conn first, so only collect from the first TLS handshake record. 28 if t.buf.Len() > 0 || (len(p) >= 2 && p[0] == 0x16 && p[1] == 0x03) { 29 t.buf.Write(p) 30 if ja4s, err := captureToJA4S(t.buf.Bytes()); err == nil { 31 select { 32 case t.ch <- ja4s: 33 default: 34 } 35 t.sent = true 36 } 37 } 38 } 39 t.mu.Unlock() 40 return t.Conn.Write(p) 41 } 42 43 func captureToJA4S(raw []byte) (string, error) { 44 sh, err := fingerprint.ParseServerHello(raw) 45 if err != nil { 46 return "", err 47 } 48 return fingerprint.ComputeJA4S(sh), nil 49 } 50 51 // captureJA4SListener wraps each accepted conn so the ServerHello mic writes 52 // back is captured and emitted on the returned channel. 53 func captureJA4SListener(ln net.Listener, p *Proxy, h Handler) <-chan string { 54 ch := make(chan string, 16) 55 go func() { 56 for { 57 conn, err := ln.Accept() 58 if err != nil { 59 return 60 } 61 wrapped := &teeWriteConn{Conn: conn, ch: ch} 62 go h(wrapped, p) 63 } 64 }() 65 return ch 66 }