diff --git a/fingerprint/fingerprint.go b/fingerprint/fingerprint.go
@@ -14,11 +14,3 @@ type TLSApplier interface {
Applier
ClientHelloID() utls.ClientHelloID
}
-
-// TCPApplier will control TCP socket options (window size, TTL, MSS, etc.).
-// Left for a future implementation.
-//
-// type TCPApplier interface {
-// Applier
-// Apply(fd uintptr) error
-// }
diff --git a/fingerprint/tls.go b/fingerprint/tls.go
@@ -6,10 +6,16 @@ import (
utls "github.com/refraction-networking/utls"
)
+// NameTable maps human-readable profile names to utls ClientHelloID presets.
+var NameTable = map[string]utls.ClientHelloID{
+ "chrome-120": utls.HelloChrome_120,
+ "chrome-120-pq": utls.HelloChrome_120_PQ,
+ "firefox-120": utls.HelloFirefox_120,
+ "safari-16": utls.HelloSafari_16_0,
+ "edge-106": utls.HelloEdge_106,
+}
+
// Table maps known JA4-TLS fingerprint hashes to utls ClientHelloID presets.
-// JA4 hashes are computed from real traffic captures; verify against current
-// browser versions when adding new entries.
-// Table maps JA4-TLS hashes to utls ClientHelloID presets.
//
// Hashes are measured empirically against tlsinfo.me using cmd/probe — they
// reflect what the utls preset actually emits, not captures from real browsers.
@@ -32,25 +38,25 @@ func Lookup(ja4 string) (utls.ClientHelloID, bool) {
return id, ok
}
-// TLSFingerprint implements TLSApplier for a specific JA4 hash.
+// TLSFingerprint implements TLSApplier for a specific named profile.
type TLSFingerprint struct {
- ja4 string
- id utls.ClientHelloID
+ name string
+ id utls.ClientHelloID
}
-// NewTLS creates a TLSFingerprint for the given JA4 hash.
-// Returns an error if the hash is not in the built-in table.
-func NewTLS(ja4 string) (*TLSFingerprint, error) {
- id, ok := Lookup(ja4)
+// 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 {
- return nil, fmt.Errorf("unknown JA4-TLS fingerprint %q", ja4)
+ return nil, fmt.Errorf("unknown TLS fingerprint profile %q", name)
}
- return &TLSFingerprint{ja4: ja4, id: id}, nil
+ return &TLSFingerprint{name: name, id: id}, nil
}
-// Name returns a human-readable identifier including the JA4 hash.
+// Name returns the profile name.
func (f *TLSFingerprint) Name() string {
- return "tls:" + f.ja4
+ return f.name
}
// ClientHelloID returns the utls preset for this fingerprint.
diff --git a/fingerprint/tls_test.go b/fingerprint/tls_test.go
@@ -1,62 +1,61 @@
package fingerprint_test
import (
- "strings"
"testing"
"github.com/djnnvx/mic/fingerprint"
utls "github.com/refraction-networking/utls"
)
-const knownJA4 = "t13d1516h2_8daaf6152771_b0da82dd1658"
-
-func TestLookup_Known(t *testing.T) {
- id, ok := fingerprint.Lookup(knownJA4)
- if !ok {
- t.Fatalf("Lookup(%q) returned false; expected true", knownJA4)
+func TestByName_Happy(t *testing.T) {
+ fp, err := fingerprint.ByName("chrome-120")
+ if err != nil {
+ t.Fatalf("ByName(%q) returned error: %v", "chrome-120", err)
}
- if id != utls.HelloChrome_120 {
- t.Errorf("Lookup(%q) = %v; want HelloChrome_120", knownJA4, id)
+ if fp == nil {
+ t.Fatal("ByName returned nil fingerprint")
}
}
-func TestLookup_Unknown(t *testing.T) {
- _, ok := fingerprint.Lookup("unknown_hash_that_does_not_exist")
- if ok {
- t.Fatal("Lookup with unknown hash returned true; expected false")
+func TestByName_Error(t *testing.T) {
+ _, err := fingerprint.ByName("not-a-real-profile")
+ if err == nil {
+ t.Fatal("ByName with unknown name returned nil error; expected error")
}
}
-func TestNewTLS_Happy(t *testing.T) {
- fp, err := fingerprint.NewTLS(knownJA4)
+func TestName_ReturnsProfile(t *testing.T) {
+ fp, err := fingerprint.ByName("chrome-120")
if err != nil {
- t.Fatalf("NewTLS(%q) returned error: %v", knownJA4, err)
+ t.Fatalf("ByName: %v", err)
}
- if fp == nil {
- t.Fatal("NewTLS returned nil fingerprint")
+ if fp.Name() != "chrome-120" {
+ t.Errorf("Name() = %q; want %q", fp.Name(), "chrome-120")
}
}
-func TestNewTLS_Error(t *testing.T) {
- _, err := fingerprint.NewTLS("not_a_real_ja4_hash")
- if err == nil {
- t.Fatal("NewTLS with unknown hash returned nil error; expected error")
+func TestLookup_Known(t *testing.T) {
+ id, ok := fingerprint.Lookup("t13d1516h2_8daaf6152771_02713d6af862")
+ if !ok {
+ t.Fatal("Lookup returned false for known Chrome 120 JA4 hash")
+ }
+ if id != utls.HelloChrome_120 {
+ t.Errorf("Lookup returned %v; want HelloChrome_120", id)
}
}
-func TestName_ContainsJA4(t *testing.T) {
- fp, err := fingerprint.NewTLS(knownJA4)
- if err != nil {
- t.Fatalf("NewTLS: %v", err)
- }
- name := fp.Name()
- if !strings.Contains(name, knownJA4) {
- t.Errorf("Name() = %q; does not contain JA4 hash %q", name, knownJA4)
+func TestLookup_Unknown(t *testing.T) {
+ _, ok := fingerprint.Lookup("unknown_hash_that_does_not_exist")
+ if ok {
+ t.Fatal("Lookup with unknown hash returned true; expected false")
}
}
-func TestTable_MinEntries(t *testing.T) {
- if len(fingerprint.Table) < 5 {
- t.Errorf("Table has %d entries; want at least 5", len(fingerprint.Table))
+func TestNameTable_HasExpectedProfiles(t *testing.T) {
+ expected := []string{"chrome-120", "chrome-120-pq", "firefox-120", "safari-16", "edge-106"}
+ for _, name := range expected {
+ if _, ok := fingerprint.NameTable[name]; !ok {
+ t.Errorf("NameTable missing profile %q", name)
+ }
}
}
diff --git a/proxy/integration_test.go b/proxy/integration_test.go
@@ -19,10 +19,7 @@ import (
"github.com/djnnvx/mic/fingerprint"
)
-const (
- chrome120JA4 = "t13d1516h2_8daaf6152771_b0da82dd1658"
- testTimeout = 10 * time.Second
-)
+const testTimeout = 10 * time.Second
// serveHandler starts a TCP listener and dispatches accepted connections to h.
// The listener is closed when the test ends.
@@ -100,9 +97,9 @@ func TestClientFront_Integration(t *testing.T) {
// Trust the test server's certificate.
caPool := ts.Client().Transport.(*http.Transport).TLSClientConfig.RootCAs
- fp, err := fingerprint.NewTLS(chrome120JA4)
+ fp, err := fingerprint.ByName("chrome-120")
if err != nil {
- t.Fatalf("fingerprint.NewTLS: %v", err)
+ t.Fatalf("fingerprint.ByName: %v", err)
}
p := &Proxy{CAPool: caPool, Fingerprint: fp}
@@ -163,9 +160,9 @@ func TestServerFront_Integration(t *testing.T) {
// Generate a cert/key pair the proxy will use to terminate incoming client TLS.
certFile, keyFile, proxyCertPool := writeTempCertKey(t)
- fp, err := fingerprint.NewTLS(chrome120JA4)
+ fp, err := fingerprint.ByName("chrome-120")
if err != nil {
- t.Fatalf("fingerprint.NewTLS: %v", err)
+ t.Fatalf("fingerprint.ByName: %v", err)
}
p := &Proxy{
diff --git a/proxy/proxy_test.go b/proxy/proxy_test.go
@@ -77,9 +77,9 @@ func TestDialTarget_HandshakeSucceeds(t *testing.T) {
io.Copy(io.Discard, conn)
}()
- fp, err := fingerprint.NewTLS("t13d1516h2_8daaf6152771_b0da82dd1658")
+ fp, err := fingerprint.ByName("chrome-120")
if err != nil {
- t.Fatalf("fingerprint.NewTLS: %v", err)
+ t.Fatalf("fingerprint.ByName: %v", err)
}
p := &Proxy{
| | | | |