mic.git


mic

~ proxy for JA4-TLS fingerprinting evasion
git clone https://github.com/djnnvx/mic.git
log | files | refs | archive | README | LICENSE

commit ce7dc1c260b2decc8908dc9b9d642b17050ace09
parent 5502c1d43a42d948d131755e82e5437b4ba3aa56
Author: djnn <[email protected]>
Date: Fri, 20 Feb 2026 11:58:46 +0900

chore(meta): cleaning up useless comments

Diffstat:
Mfingerprint/fingerprint.go | 3+--
Mfingerprint/ja4.go | 1-
Dfingerprint/tcp.go | 11-----------
Mfingerprint/tls.go | 6------
Mproxy/ca.go | 19++++++++-----------
Mproxy/https.go | 18++++--------------
Mproxy/integration_test.go | 24+++++-------------------
Mproxy/ja4_integration_test.go | 3+--
Mproxy/proxy.go | 17+++++------------
Mproxy/proxy_test.go | 8+-------
Mproxy/serverfront.go | 1-

11 files changed, 25 insertions(+), 86 deletions(-)

diff --git a/fingerprint/fingerprint.go b/fingerprint/fingerprint.go
@@ -1,10 +1,9 @@
 // Package fingerprint provides interfaces and implementations for controlling
-// outbound connection fingerprints (TLS ClientHello, TCP socket options, etc.).
+// outbound TLS connection fingerprints.
 package fingerprint
 
 import utls "github.com/refraction-networking/utls"
 
-// Applier is the base interface for all fingerprint types.
 type Applier interface {
 	Name() string
 }
diff --git a/fingerprint/ja4.go b/fingerprint/ja4.go
@@ -225,7 +225,6 @@ func parseSigAlgs(data []byte, f *ClientHelloFields) {
 	}
 }
 
-// ComputeJA4 returns the JA4 fingerprint string for ch.
 func ComputeJA4(ch *ClientHelloFields) string {
 	return buildJA4a(ch) + "_" + buildJA4b(ch.CipherSuites) + "_" + buildJA4c(ch.Extensions, ch.SigAlgs)
 }
diff --git a/fingerprint/tcp.go b/fingerprint/tcp.go
@@ -1,11 +0,0 @@
-package fingerprint
-
-// TCPApplier is reserved for future TCP-level fingerprinting support
-// (TCP window size, TTL, MSS, timestamps, etc.).
-//
-// Future interface outline:
-//
-//	type TCPApplier interface {
-//	    Applier
-//	    Apply(fd uintptr) error
-//	}
diff --git a/fingerprint/tls.go b/fingerprint/tls.go
@@ -32,20 +32,16 @@ var Table = map[string]utls.ClientHelloID{
 	"t13d1516h2_8daaf6152771_e5627efa2ab1": utls.HelloEdge_106,
 }
 
-// Lookup returns the utls.ClientHelloID for the given JA4 hash.
 func Lookup(ja4 string) (utls.ClientHelloID, bool) {
 	id, ok := Table[ja4]
 	return id, ok
 }
 
-// TLSFingerprint implements TLSApplier for a specific named profile.
 type TLSFingerprint struct {
 	name string
 	id   utls.ClientHelloID
 }
 
-// ByName creates a TLSFingerprint for the given profile name.
-// Returns an error if the name is not in NameTable.
 func ByName(name string) (*TLSFingerprint, error) {
 	id, ok := NameTable[name]
 	if !ok {
@@ -54,12 +50,10 @@ func ByName(name string) (*TLSFingerprint, error) {
 	return &TLSFingerprint{name: name, id: id}, nil
 }
 
-// Name returns the profile name.
 func (f *TLSFingerprint) Name() string {
 	return f.name
 }
 
-// ClientHelloID returns the utls preset for this fingerprint.
 func (f *TLSFingerprint) ClientHelloID() utls.ClientHelloID {
 	return f.id
 }
diff --git a/proxy/ca.go b/proxy/ca.go
@@ -21,13 +21,12 @@ import (
 type LocalCA struct {
 	cert    *x509.Certificate
 	key     *ecdsa.PrivateKey
-	derCert []byte // raw DER of CA cert, appended as chain in issued certs
+	derCert []byte // raw DER, appended as chain in issued certs
 
 	mu    sync.Mutex
 	cache map[string]tls.Certificate
 }
 
-// GenerateCA creates a fresh in-memory self-signed CA.
 func GenerateCA() (*LocalCA, error) {
 	key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
 	if err != nil {
@@ -37,8 +36,8 @@ func GenerateCA() (*LocalCA, error) {
 }
 
 // LoadOrGenerateCA loads the CA from certPath + keyPath. If the files do not
-// exist, it generates a new CA and writes it to those paths. Pass empty strings
-// to get an ephemeral in-memory CA.
+// exist it generates a new CA and writes it to those paths. Pass empty strings
+// for an ephemeral in-memory CA.
 func LoadOrGenerateCA(certPath, keyPath string) (*LocalCA, error) {
 	if certPath == "" || keyPath == "" {
 		return GenerateCA()
@@ -52,7 +51,6 @@ func LoadOrGenerateCA(certPath, keyPath string) (*LocalCA, error) {
 		return nil, fmt.Errorf("loading CA: %w", err)
 	}
 
-	// Files don't exist — generate a new CA and persist it.
 	ca, err = GenerateCA()
 	if err != nil {
 		return nil, err
@@ -114,7 +112,6 @@ func buildCA(key *ecdsa.PrivateKey) (*LocalCA, error) {
 	}, nil
 }
 
-// Save writes the CA certificate and private key to PEM files.
 func (ca *LocalCA) Save(certPath, keyPath string) error {
 	cf, err := os.Create(certPath)
 	if err != nil {
@@ -137,13 +134,14 @@ func (ca *LocalCA) Save(certPath, keyPath string) error {
 	return pem.Encode(kf, &pem.Block{Type: "EC PRIVATE KEY", Bytes: keyBytes})
 }
 
-// CertPEM returns the CA certificate as a PEM block, ready to be imported
-// into a trust store (e.g. curl --cacert, system store).
+// CertPEM returns the CA certificate as PEM, suitable for curl --cacert or
+// importing into a system trust store.
 func (ca *LocalCA) CertPEM() []byte {
 	return pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: ca.derCert})
 }
 
-// issueCert generates (and caches) a leaf certificate for host, signed by the CA.
+// issueCert returns a leaf certificate for host signed by the CA, issuing and
+// caching a new one on first call.
 func (ca *LocalCA) issueCert(host string) (tls.Certificate, error) {
 	ca.mu.Lock()
 	defer ca.mu.Unlock()
@@ -179,8 +177,7 @@ func (ca *LocalCA) issueCert(host string) (tls.Certificate, error) {
 		return tls.Certificate{}, err
 	}
 	cert := tls.Certificate{
-		// Include the CA cert so clients that load the CA can verify the chain.
-		Certificate: [][]byte{der, ca.derCert},
+		Certificate: [][]byte{der, ca.derCert}, // CA appended so clients can verify the chain
 		PrivateKey:  key,
 	}
 	ca.cache[host] = cert
diff --git a/proxy/https.go b/proxy/https.go
@@ -12,7 +12,6 @@ import (
 func HttpsHandler(clientConn net.Conn, p *Proxy) {
 	defer clientConn.Close()
 
-	// Read the first line of the request to check for the CONNECT method.
 	reader := bufio.NewReader(clientConn)
 	req, err := http.ReadRequest(reader)
 	if err != nil {
@@ -20,14 +19,11 @@ func HttpsHandler(clientConn net.Conn, p *Proxy) {
 		return
 	}
 
-	// An HTTPS proxy relies on the HTTP CONNECT method.
 	if req.Method != http.MethodConnect {
 		log.Printf("Received non-CONNECT request: %s %s", req.Method, req.Host)
 		return
 	}
 
-	// The Host field contains the destination host and port.
-	// Default to :443 if no port is specified.
 	host := req.URL.Host
 	if !strings.Contains(host, ":") {
 		host = host + ":443"
@@ -43,9 +39,6 @@ func HttpsHandler(clientConn net.Conn, p *Proxy) {
 
 	clientConn.Write([]byte("HTTP/1.1 200 Connection established\r\n\r\n"))
 
-	// MitM path: terminate TLS from the client using a per-host certificate
-	// issued by the local CA, then pipe decrypted traffic over the already-
-	// established uTLS connection to the target.
 	if p.LocalCA != nil {
 		serverName := host
 		if idx := strings.LastIndex(host, ":"); idx != -1 {
@@ -58,10 +51,10 @@ func HttpsHandler(clientConn net.Conn, p *Proxy) {
 			return
 		}
 
-		// Mirror the ALPN negotiated with the target so the client uses the
-		// same application protocol (e.g. h2 vs http/1.1). Without this,
-		// Chrome-fingerprint uTLS often negotiates h2 with the target while
-		// the client sends HTTP/1.1, causing an immediate protocol error.
+		// Mirror the ALPN negotiated with the target so the client uses the same
+		// application protocol. Without this, Chrome-fingerprint uTLS often
+		// negotiates h2 with the target while the client sends HTTP/1.1, causing
+		// an immediate protocol error.
 		nextProtos := []string{"http/1.1"}
 		if proto := targetConn.ConnectionState().NegotiatedProtocol; proto != "" {
 			nextProtos = []string{proto}
@@ -78,12 +71,9 @@ func HttpsHandler(clientConn net.Conn, p *Proxy) {
 		defer tlsClient.Close()
 
 		log.Printf("MitM tunnel established for %s (proto: %s)", serverName, tlsClient.ConnectionState().NegotiatedProtocol)
-
-		// Both sides are now on the same protocol — pipe plaintext between them.
 		pipe(tlsClient, tlsClient, targetConn)
 		return
 	}
 
-	// No LocalCA: pipe raw bytes (client must not speak TLS after CONNECT).
 	pipe(clientConn, reader, targetConn)
 }
diff --git a/proxy/integration_test.go b/proxy/integration_test.go
@@ -21,8 +21,6 @@ import (
 
 const testTimeout = 10 * time.Second
 
-// serveHandler starts a TCP listener and dispatches accepted connections to h.
-// The listener is closed when the test ends.
 func serveHandler(t *testing.T, p *Proxy, h Handler) string {
 	t.Helper()
 	ln, err := net.Listen("tcp", "127.0.0.1:0")
@@ -44,15 +42,11 @@ func serveHandler(t *testing.T, p *Proxy, h Handler) string {
 	return ln.Addr().String()
 }
 
-// writeTempCertKey generates a self-signed cert/key, writes PEM files to disk,
-// and returns the file paths plus a CA pool that trusts the cert.
-// Files are removed when the test ends.
 func writeTempCertKey(t *testing.T) (certFile, keyFile string, pool *x509.CertPool) {
 	t.Helper()
 
 	pool, tlsCert := generateSelfSignedCert(t)
 
-	// Certificate PEM.
 	cf, err := os.CreateTemp("", "mic-cert-*.pem")
 	if err != nil {
 		t.Fatalf("creating cert temp file: %v", err)
@@ -61,7 +55,7 @@ func writeTempCertKey(t *testing.T) (certFile, keyFile string, pool *x509.CertPo
 	cf.Close()
 	certFile = cf.Name()
 
-	// Private key PEM (PKCS8 — accepted by tls.LoadX509KeyPair as "PRIVATE KEY").
+	// PKCS8 format is required: tls.LoadX509KeyPair accepts "PRIVATE KEY" blocks.
 	keyBytes, err := x509.MarshalPKCS8PrivateKey(tlsCert.PrivateKey)
 	if err != nil {
 		t.Fatalf("marshaling private key: %v", err)
@@ -86,7 +80,6 @@ func writeTempCertKey(t *testing.T) (certFile, keyFile string, pool *x509.CertPo
 //
 //	client → TCP CONNECT → proxy → uTLS(Chrome120) → httptest.TLSServer
 func TestClientFront_Integration(t *testing.T) {
-	// Start a target HTTPS server.
 	ts := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
 		w.Header().Set("Connection", "close")
 		w.WriteHeader(http.StatusOK)
@@ -94,7 +87,6 @@ func TestClientFront_Integration(t *testing.T) {
 	}))
 	defer ts.Close()
 
-	// Trust the test server's certificate.
 	caPool := ts.Client().Transport.(*http.Transport).TLSClientConfig.RootCAs
 
 	fp, err := fingerprint.ByName("chrome-120")
@@ -105,7 +97,6 @@ func TestClientFront_Integration(t *testing.T) {
 	p := &Proxy{CAPool: caPool, Fingerprint: fp}
 	proxyAddr := serveHandler(t, p, HttpsHandler)
 
-	// Connect to the proxy over plain TCP.
 	conn, err := net.DialTimeout("tcp", proxyAddr, testTimeout)
 	if err != nil {
 		t.Fatalf("dial proxy: %v", err)
@@ -113,7 +104,6 @@ func TestClientFront_Integration(t *testing.T) {
 	defer conn.Close()
 	conn.SetDeadline(time.Now().Add(testTimeout))
 
-	// Send HTTP CONNECT to open a tunnel to the target.
 	targetAddr := ts.Listener.Addr().String()
 	fmt.Fprintf(conn, "CONNECT %s HTTP/1.1\r\nHost: %s\r\n\r\n", targetAddr, targetAddr)
 
@@ -126,8 +116,8 @@ func TestClientFront_Integration(t *testing.T) {
 		t.Fatalf("CONNECT status: %d; want 200", resp.StatusCode)
 	}
 
-	// After CONNECT the proxy has already done uTLS to the target.
-	// We send raw HTTP — the proxy wraps it in TLS on our behalf.
+	// The proxy has already done uTLS to the target; raw bytes we send here are
+	// wrapped in that TLS connection on our behalf.
 	fmt.Fprintf(conn, "GET / HTTP/1.1\r\nHost: %s\r\nConnection: close\r\n\r\n", targetAddr)
 
 	resp, err = http.ReadResponse(reader, nil)
@@ -147,7 +137,6 @@ func TestClientFront_Integration(t *testing.T) {
 //
 //	client → TLS → proxy (terminates TLS) → uTLS(Chrome120) → httptest.TLSServer
 func TestServerFront_Integration(t *testing.T) {
-	// Start the backend HTTPS server (what the proxy connects to upstream).
 	backend := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
 		w.Header().Set("Connection", "close")
 		w.WriteHeader(http.StatusOK)
@@ -156,8 +145,6 @@ func TestServerFront_Integration(t *testing.T) {
 	defer backend.Close()
 
 	backendCAPool := backend.Client().Transport.(*http.Transport).TLSClientConfig.RootCAs
-
-	// Generate a cert/key pair the proxy will use to terminate incoming client TLS.
 	certFile, keyFile, proxyCertPool := writeTempCertKey(t)
 
 	fp, err := fingerprint.ByName("chrome-120")
@@ -172,7 +159,6 @@ func TestServerFront_Integration(t *testing.T) {
 	}
 	proxyAddr := serveHandler(t, p, ServerFrontHandler(certFile, keyFile))
 
-	// Connect to the proxy with TLS, trusting the proxy's generated cert.
 	tlsConn, err := tls.DialWithDialer(
 		&net.Dialer{Timeout: testTimeout},
 		"tcp", proxyAddr,
@@ -184,8 +170,8 @@ func TestServerFront_Integration(t *testing.T) {
 	defer tlsConn.Close()
 	tlsConn.SetDeadline(time.Now().Add(testTimeout))
 
-	// Send plain HTTP — the proxy decrypts from us and re-encrypts with the
-	// configured fingerprint before forwarding to the backend.
+	// The proxy decrypts our TLS and re-encrypts with the configured fingerprint
+	// before forwarding to the backend.
 	fmt.Fprintf(tlsConn, "GET / HTTP/1.1\r\nHost: localhost\r\nConnection: close\r\n\r\n")
 
 	reader := bufio.NewReader(tlsConn)
diff --git a/proxy/ja4_integration_test.go b/proxy/ja4_integration_test.go
@@ -14,8 +14,7 @@ import (
 	"github.com/djnnvx/mic/fingerprint"
 )
 
-// expectedJA4 maps fingerprint profile names to their known JA4 strings.
-// Values come from fingerprint.Table (measured empirically via cmd/probe against
+// Values measured empirically via cmd/probe against
 // tlsinfo.me). chrome-120-pq is absent: it emits the same JA4 as chrome-120
 // because JA4 does not distinguish the X25519MLKEM768 key share.
 var expectedJA4 = map[string]string{
diff --git a/proxy/proxy.go b/proxy/proxy.go
@@ -22,17 +22,14 @@ type Proxy struct {
 	handlers []Handler
 }
 
-// Handler is a function that processes an incoming client connection.
 type Handler func(conn net.Conn, p *Proxy)
 
-// RegisterHandler adds a handler to the proxy.
 func (p *Proxy) RegisterHandler(h Handler) {
 	p.handlers = append(p.handlers, h)
 }
 
-// dialTarget dials host (host:port), performs a uTLS handshake using p.Fingerprint,
-// and returns the ready-to-use connection. Falls back to HelloRandomized if no
-// fingerprint is configured.
+// dialTarget dials host (host:port) and returns a uTLS connection after a
+// successful handshake. Falls back to HelloRandomized when no fingerprint is set.
 func (p *Proxy) dialTarget(host string) (*utls.UConn, error) {
 	tcpConn, err := net.Dial("tcp", host)
 	if err != nil {
@@ -49,9 +46,7 @@ func (p *Proxy) dialTarget(host string) (*utls.UConn, error) {
 		serverName = host[:idx]
 	}
 
-	cfg := &utls.Config{
-		ServerName: serverName,
-	}
+	cfg := &utls.Config{ServerName: serverName}
 	if p.CAPool != nil {
 		cfg.RootCAs = p.CAPool
 	}
@@ -64,9 +59,8 @@ func (p *Proxy) dialTarget(host string) (*utls.UConn, error) {
 	return uconn, nil
 }
 
-// pipe copies data bidirectionally between client (reading from r) and target,
-// blocking until both directions are done. When either direction finishes it
-// closes that side's connection so the other goroutine unblocks and exits.
+// pipe copies bidirectionally between client (reading from r) and target.
+// When either direction finishes it closes that side so the other goroutine unblocks.
 func pipe(client io.ReadWriteCloser, r io.Reader, target io.ReadWriteCloser) {
 	var wg sync.WaitGroup
 	wg.Add(2)
@@ -86,7 +80,6 @@ func pipe(client io.ReadWriteCloser, r io.Reader, target io.ReadWriteCloser) {
 	wg.Wait()
 }
 
-// Run starts the proxy server and dispatches incoming connections to registered handlers.
 func (p *Proxy) Run() error {
 	ln, err := net.Listen("tcp", p.ListenAddr)
 	if err != nil {
diff --git a/proxy/proxy_test.go b/proxy/proxy_test.go
@@ -16,7 +16,6 @@ import (
 	"github.com/djnnvx/mic/fingerprint"
 )
 
-// generateSelfSignedCert creates an in-memory self-signed cert/pool for testing.
 func generateSelfSignedCert(t *testing.T) (*x509.CertPool, tls.Certificate) {
 	t.Helper()
 
@@ -58,7 +57,6 @@ func generateSelfSignedCert(t *testing.T) (*x509.CertPool, tls.Certificate) {
 func TestDialTarget_HandshakeSucceeds(t *testing.T) {
 	pool, tlsCert := generateSelfSignedCert(t)
 
-	// Start an in-process TLS server on a random port.
 	ln, err := tls.Listen("tcp", "127.0.0.1:0", &tls.Config{
 		Certificates: []tls.Certificate{tlsCert},
 	})
@@ -67,7 +65,6 @@ func TestDialTarget_HandshakeSucceeds(t *testing.T) {
 	}
 	defer ln.Close()
 
-	// Accept and drain one connection in the background.
 	go func() {
 		conn, err := ln.Accept()
 		if err != nil {
@@ -94,7 +91,6 @@ func TestDialTarget_HandshakeSucceeds(t *testing.T) {
 	}
 	defer conn.Close()
 
-	// Verify connection is usable for writes.
 	if _, err := conn.Write([]byte("hello\n")); err != nil {
 		t.Fatalf("writing to connection: %v", err)
 	}
@@ -120,7 +116,6 @@ func TestDialTarget_FallbackToRandomized(t *testing.T) {
 		io.Copy(io.Discard, conn)
 	}()
 
-	// No fingerprint set — should fall back to HelloRandomized.
 	p := &Proxy{
 		ListenAddr: "127.0.0.1:0",
 		CAPool:     pool,
@@ -128,8 +123,7 @@ func TestDialTarget_FallbackToRandomized(t *testing.T) {
 
 	conn, err := p.dialTarget(ln.Addr().String())
 	if err != nil {
-		// HelloRandomized may select post-quantum curves not supported by stdlib TLS servers.
-		// Skip rather than fail — the code path is exercised regardless.
+		// HelloRandomized may select post-quantum curves unsupported by stdlib TLS.
 		t.Skipf("dialTarget with randomized preset: %v", err)
 	}
 	conn.Close()
diff --git a/proxy/serverfront.go b/proxy/serverfront.go
@@ -30,7 +30,6 @@ func ServerFrontHandler(certFile, keyFile string) Handler {
 		}
 		defer tlsConn.Close()
 
-		// Dial the backend with the configured uTLS fingerprint.
 		targetConn, err := p.dialTarget(p.BackendAddr)
 		if err != nil {
 			log.Printf("ServerFront: failed to connect to backend %s: %v", p.BackendAddr, err)