mic~ proxy for JA4-TLS fingerprinting evasion |
| git clone https://github.com/djnnvx/mic.git |
| log | files | refs | archive | README | LICENSE |
proxy_test.go (4876B)
1 package proxy 2 3 import ( 4 "crypto/ecdsa" 5 "crypto/elliptic" 6 "crypto/rand" 7 "crypto/tls" 8 "crypto/x509" 9 "crypto/x509/pkix" 10 "errors" 11 "io" 12 "math/big" 13 "net" 14 "strings" 15 "testing" 16 "time" 17 18 utls "github.com/bogdanfinn/utls" 19 "github.com/djnnvx/mic/fingerprint" 20 ) 21 22 func generateSelfSignedCert(t *testing.T) (*x509.CertPool, tls.Certificate) { 23 t.Helper() 24 25 key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) 26 if err != nil { 27 t.Fatalf("generating key: %v", err) 28 } 29 30 tmpl := &x509.Certificate{ 31 SerialNumber: big.NewInt(1), 32 Subject: pkix.Name{CommonName: "localhost"}, 33 NotBefore: time.Now().Add(-time.Hour), 34 NotAfter: time.Now().Add(time.Hour), 35 DNSNames: []string{"localhost"}, 36 IPAddresses: []net.IP{net.ParseIP("127.0.0.1")}, 37 } 38 39 certDER, err := x509.CreateCertificate(rand.Reader, tmpl, tmpl, &key.PublicKey, key) 40 if err != nil { 41 t.Fatalf("creating certificate: %v", err) 42 } 43 44 cert, err := x509.ParseCertificate(certDER) 45 if err != nil { 46 t.Fatalf("parsing certificate: %v", err) 47 } 48 49 pool := x509.NewCertPool() 50 pool.AddCert(cert) 51 52 tlsCert := tls.Certificate{ 53 Certificate: [][]byte{certDER}, 54 PrivateKey: key, 55 } 56 57 return pool, tlsCert 58 } 59 60 func TestDialTarget_HandshakeSucceeds(t *testing.T) { 61 pool, tlsCert := generateSelfSignedCert(t) 62 63 ln, err := tls.Listen("tcp", "127.0.0.1:0", &tls.Config{ 64 Certificates: []tls.Certificate{tlsCert}, 65 }) 66 if err != nil { 67 t.Fatalf("tls.Listen: %v", err) 68 } 69 defer ln.Close() 70 71 go func() { 72 conn, err := ln.Accept() 73 if err != nil { 74 return 75 } 76 defer conn.Close() 77 io.Copy(io.Discard, conn) 78 }() 79 80 fp, err := fingerprint.ByName("chrome-120") 81 if err != nil { 82 t.Fatalf("fingerprint.ByName: %v", err) 83 } 84 85 p := &Proxy{ 86 ListenAddr: "127.0.0.1:0", 87 CAPool: pool, 88 Fingerprint: fp, 89 } 90 91 conn, err := p.dialTarget(ln.Addr().String()) 92 if err != nil { 93 t.Fatalf("dialTarget(%q): %v", ln.Addr().String(), err) 94 } 95 defer conn.Close() 96 97 if _, err := conn.Write([]byte("hello\n")); err != nil { 98 t.Fatalf("writing to connection: %v", err) 99 } 100 } 101 102 func TestDialTarget_FallbackToRandomized(t *testing.T) { 103 pool, tlsCert := generateSelfSignedCert(t) 104 105 ln, err := tls.Listen("tcp", "127.0.0.1:0", &tls.Config{ 106 Certificates: []tls.Certificate{tlsCert}, 107 }) 108 if err != nil { 109 t.Fatalf("tls.Listen: %v", err) 110 } 111 defer ln.Close() 112 113 go func() { 114 conn, err := ln.Accept() 115 if err != nil { 116 return 117 } 118 defer conn.Close() 119 io.Copy(io.Discard, conn) 120 }() 121 122 p := &Proxy{ 123 ListenAddr: "127.0.0.1:0", 124 CAPool: pool, 125 } 126 127 conn, err := p.dialTarget(ln.Addr().String()) 128 if err != nil { 129 // HelloRandomized sometimes picks a curve the local TLS stack rejects. 130 if !strings.Contains(err.Error(), "CurvePreferences includes unsupported curve") { 131 t.Fatalf("dialTarget with randomized preset: %v", err) 132 } 133 return 134 } 135 defer conn.Close() 136 137 if got := conn.ClientHelloID.Str(); got != utls.HelloRandomized.Str() { 138 t.Fatalf("ClientHelloID: got %q, want %q", got, utls.HelloRandomized.Str()) 139 } 140 } 141 142 func TestServe_ReturnsWhenListenerClosed(t *testing.T) { 143 ln, err := net.Listen("tcp", "127.0.0.1:0") 144 if err != nil { 145 t.Fatalf("net.Listen: %v", err) 146 } 147 148 p := &Proxy{Handler: func(conn net.Conn, _ *Proxy) { conn.Close() }} 149 150 done := make(chan error, 1) 151 go func() { done <- p.Serve(ln) }() 152 153 time.Sleep(50 * time.Millisecond) 154 ln.Close() 155 156 select { 157 case err := <-done: 158 if !errors.Is(err, net.ErrClosed) { 159 t.Fatalf("Serve returned %v, want net.ErrClosed", err) 160 } 161 case <-time.After(3 * time.Second): 162 t.Fatal("Serve did not return after the listener was closed") 163 } 164 } 165 166 func TestPipe_HalfCloseDeliversResponse(t *testing.T) { 167 backend, err := net.Listen("tcp", "127.0.0.1:0") 168 if err != nil { 169 t.Fatalf("net.Listen backend: %v", err) 170 } 171 defer backend.Close() 172 173 go func() { 174 conn, err := backend.Accept() 175 if err != nil { 176 return 177 } 178 defer conn.Close() 179 io.Copy(io.Discard, conn) 180 time.Sleep(100 * time.Millisecond) 181 conn.Write([]byte("PONG")) 182 }() 183 184 front, err := net.Listen("tcp", "127.0.0.1:0") 185 if err != nil { 186 t.Fatalf("net.Listen front: %v", err) 187 } 188 defer front.Close() 189 190 go func() { 191 conn, err := front.Accept() 192 if err != nil { 193 return 194 } 195 upstream, err := net.Dial("tcp", backend.Addr().String()) 196 if err != nil { 197 conn.Close() 198 return 199 } 200 pipe(conn, upstream) 201 }() 202 203 client, err := net.Dial("tcp", front.Addr().String()) 204 if err != nil { 205 t.Fatalf("net.Dial front: %v", err) 206 } 207 defer client.Close() 208 209 if _, err := client.Write([]byte("PING")); err != nil { 210 t.Fatalf("writing request: %v", err) 211 } 212 if err := client.(*net.TCPConn).CloseWrite(); err != nil { 213 t.Fatalf("CloseWrite: %v", err) 214 } 215 216 client.SetReadDeadline(time.Now().Add(5 * time.Second)) 217 got, err := io.ReadAll(client) 218 if err != nil { 219 t.Fatalf("reading response: %v", err) 220 } 221 if string(got) != "PONG" { 222 t.Fatalf("response: got %q, want %q", got, "PONG") 223 } 224 }