diff --git a/client.go b/client.go index e9db4b8..21accd0 100644 --- a/client.go +++ b/client.go @@ -1225,6 +1225,10 @@ func (conn *uTLSConn) ConnectionState() tls.ConnectionState { // which uses the specified clientHelloID to simulate the tls fingerprint. // Note this is valid for HTTP1 and HTTP2, not HTTP3. func (c *Client) SetTLSFingerprint(clientHelloID utls.ClientHelloID) *Client { + c.setTLSFingerprint(clientHelloID, nil) + return c +} +func (c *Client) setTLSFingerprint(clientHelloID utls.ClientHelloID, uTLSConnApply func(*uTLSConn) error) *Client { fn := func(ctx context.Context, addr string, plainConn net.Conn) (conn net.Conn, tlsState *tls.ConnectionState, err error) { colonPos := strings.LastIndex(addr, ":") if colonPos == -1 { @@ -1248,6 +1252,11 @@ func (c *Client) SetTLSFingerprint(clientHelloID utls.ClientHelloID) *Client { KeyLogWriter: tlsConfig.KeyLogWriter, } uconn := &uTLSConn{utls.UClient(plainConn, utlsConfig, clientHelloID)} + if uTLSConnApply != nil { + if err = uTLSConnApply(uconn); err != nil { + return + } + } err = uconn.HandshakeContext(ctx) if err != nil { return @@ -1274,6 +1283,17 @@ func (c *Client) SetTLSFingerprint(clientHelloID utls.ClientHelloID) *Client { return c } +// SetTLSFingerprintSpec set the tls fingerprint for tls handshake, will use utls +// (https://github.com/refraction-networking/utls) to perform the tls handshake, +// which uses the specified clientHelloID to simulate the tls fingerprint. +func (c *Client) SetTLSFingerprintSpec(clientHelloID *utls.ClientHelloSpec) *Client { + c.setTLSFingerprint(utls.HelloCustom, func(conn *uTLSConn) error { + err := conn.ApplyPreset(clientHelloID) + return err + }) + return c +} + // SetTLSHandshake set the custom tls handshake function, only valid for HTTP1 and HTTP2, not HTTP3, // it specifies an optional dial function for tls handshake, it works even if a proxy is set, can be // used to customize the tls fingerprint. diff --git a/client_test.go b/client_test.go index 8f7bc9b..de3f000 100644 --- a/client_test.go +++ b/client_test.go @@ -19,6 +19,7 @@ import ( "github.com/imroc/req/v3/internal/header" "github.com/imroc/req/v3/internal/tests" + utls "github.com/refraction-networking/utls" "golang.org/x/net/publicsuffix" ) @@ -720,3 +721,33 @@ func TestCloneCookieJar(t *testing.T) { tests.AssertEqual(t, true, c2.cookiejarFactory == nil) tests.AssertEqual(t, true, c2.httpClient.Jar == nil) } +func TestUTLSConnApply(t *testing.T) { + c1 := C() + + c1.setTLSFingerprint(utls.HelloCustom, func(conn *uTLSConn) error { + tt, _ := utls.UTLSIdToSpec(utls.HelloQQ_Auto) + //"github.com/Danny-Dasilva/CycleTLS/cycletls" + //cycletls.StringToSpec("771,4865-4866-4867-49195-49199-49196-49200-52393-52392-49171-49172-156-157-47-53,0-5-10-11-13-16-18-21-23-27-35-43-45-51-17513-65281,29-23-24,0", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:120.0) Gecko/20100101 Firefox/120.0", false) + err := conn.ApplyPreset(&tt) + return err + }) + + //c1.SetTLSFingerprintQQ() + bodyJson := &struct { + Ja3NText string `json:"ja3n_text"` + }{} + _ = c1.Get("https://tls.browserleaks.com/json").Do().Into(bodyJson) + //println(string(bodyJson.Ja3NText)) + tests.AssertEqual(t, true, bodyJson.Ja3NText == "771,4865-4866-4867-49195-49199-49196-49200-52393-52392-49171-49172-156-157-47-53,0-5-10-11-13-16-18-21-23-27-35-43-45-51-17513-65281,29-23-24,0") +} +func TestSetTLSFingerprintSpec(t *testing.T) { + c1 := C() + tt, _ := utls.UTLSIdToSpec(utls.HelloQQ_Auto) + c1.SetTLSFingerprintSpec(&tt) + bodyJson := &struct { + Ja3NText string `json:"ja3n_text"` + }{} + _ = c1.Get("https://tls.browserleaks.com/json").Do().Into(bodyJson) + //println(string(bodyJson.Ja3NText)) + tests.AssertEqual(t, true, bodyJson.Ja3NText == "771,4865-4866-4867-49195-49199-49196-49200-52393-52392-49171-49172-156-157-47-53,0-5-10-11-13-16-18-21-23-27-35-43-45-51-17513-65281,29-23-24,0") +}