mic.git


mic

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

main.go (2129B)

      1 // probe prints the JA4 hash tlsinfo.me observes for each mic profile, to
      2 // populate the fingerprint table.
      3 package main
      4 
      5 import (
      6 	"bufio"
      7 	"context"
      8 	"encoding/json"
      9 	"fmt"
     10 	"io"
     11 	"maps"
     12 	"net"
     13 	"net/http"
     14 	"slices"
     15 
     16 	utls "github.com/bogdanfinn/utls"
     17 	"golang.org/x/net/http2"
     18 
     19 	"github.com/djnnvx/mic/fingerprint"
     20 )
     21 
     22 func dialUTLS(ctx context.Context, id utls.ClientHelloID) (*utls.UConn, error) {
     23 	tcpConn, err := (&net.Dialer{}).DialContext(ctx, "tcp", "tlsinfo.me:443")
     24 	if err != nil {
     25 		return nil, err
     26 	}
     27 	uconn := utls.UClient(tcpConn, &utls.Config{ServerName: "tlsinfo.me"}, id, false, false, false)
     28 	if err := uconn.Handshake(); err != nil {
     29 		tcpConn.Close()
     30 		return nil, err
     31 	}
     32 	return uconn, nil
     33 }
     34 
     35 func decodeJA4(body io.Reader) (string, error) {
     36 	var result struct {
     37 		JA4 string `json:"ja4"`
     38 	}
     39 	if err := json.NewDecoder(body).Decode(&result); err != nil {
     40 		return "", err
     41 	}
     42 	return result.JA4, nil
     43 }
     44 
     45 func probe(id utls.ClientHelloID) (string, error) {
     46 	ctx := context.Background()
     47 	uconn, err := dialUTLS(ctx, id)
     48 	if err != nil {
     49 		return "", err
     50 	}
     51 	defer uconn.Close()
     52 
     53 	if uconn.ConnectionState().NegotiatedProtocol == "h2" {
     54 		// Reuse the handshaked uconn. A Transport redial would measure a different handshake.
     55 		cc, err := (&http2.Transport{}).NewClientConn(uconn)
     56 		if err != nil {
     57 			return "", err
     58 		}
     59 		req, _ := http.NewRequest("GET", "https://tlsinfo.me/json", nil)
     60 		resp, err := cc.RoundTrip(req)
     61 		if err != nil {
     62 			return "", err
     63 		}
     64 		defer resp.Body.Close()
     65 		return decodeJA4(resp.Body)
     66 	}
     67 
     68 	req, _ := http.NewRequest("GET", "/json", nil)
     69 	req.Host = "tlsinfo.me"
     70 	req.Header.Set("Connection", "close")
     71 	if err := req.Write(uconn); err != nil {
     72 		return "", err
     73 	}
     74 	resp, err := http.ReadResponse(bufio.NewReader(uconn), req)
     75 	if err != nil {
     76 		return "", err
     77 	}
     78 	defer resp.Body.Close()
     79 	return decodeJA4(resp.Body)
     80 }
     81 
     82 func main() {
     83 	for _, name := range slices.Sorted(maps.Keys(fingerprint.NameTable)) {
     84 		ja4, err := probe(fingerprint.NameTable[name])
     85 		if err != nil {
     86 			fmt.Printf("%-22s  ERROR: %v\n", name, err)
     87 			continue
     88 		}
     89 		fmt.Printf("%-22s  %s\n", name, ja4)
     90 	}
     91 }