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)
| | | | | | | | | | |