Skip to content

Commit 1dd928a

Browse files
committed
impl. utls conn factory
1 parent 6899715 commit 1dd928a

File tree

3 files changed

+195
-0
lines changed

3 files changed

+195
-0
lines changed

go.mod

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ require (
1212
github.com/jellydator/ttlcache/v3 v3.3.0
1313
github.com/libp2p/go-reuseport v0.4.0
1414
github.com/redis/go-redis/v9 v9.8.0
15+
github.com/refraction-networking/utls v1.8.0
1516
github.com/tg123/go-htpasswd v1.2.4
1617
github.com/zeebo/xxh3 v1.0.2
1718
golang.org/x/crypto v0.38.0
@@ -23,12 +24,14 @@ require (
2324

2425
require (
2526
github.com/GehirnInc/crypt v0.0.0-20230320061759-8cc1b52080c5 // indirect
27+
github.com/andybalholm/brotli v1.0.6 // indirect
2628
github.com/cespare/xxhash/v2 v2.3.0 // indirect
2729
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
2830
github.com/dlclark/regexp2 v1.11.5 // indirect
2931
github.com/go-sourcemap/sourcemap v2.1.4+incompatible // indirect
3032
github.com/google/pprof v0.0.0-20250501235452-c0086092b71a // indirect
3133
github.com/hashicorp/errwrap v1.1.0 // indirect
34+
github.com/klauspost/compress v1.17.4 // indirect
3235
github.com/klauspost/cpuid/v2 v2.2.10 // indirect
3336
github.com/pires/go-proxyproto v0.8.1
3437
golang.org/x/sys v0.33.0 // indirect

go.sum

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ github.com/Snawoot/uniqueslice v0.1.1 h1:KEfv3FtAXiNEoxvcc79pFQDhnqwYXQyZIkxOM4e
66
github.com/Snawoot/uniqueslice v0.1.1/go.mod h1:K9zIaHO43FGLHbqm6WCDFeY6+CN/du5eiio/vxvDVC8=
77
github.com/Snawoot/xtime v0.0.0-20250501122004-d1ce456948bb h1:PleTDwc/EQenzLsvIal2BgvIXr2D214M88RFac3WkeI=
88
github.com/Snawoot/xtime v0.0.0-20250501122004-d1ce456948bb/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg=
9+
github.com/andybalholm/brotli v1.0.6 h1:Yf9fFpf49Zrxb9NlQaluyE92/+X7UVHlhMNJN2sxfOI=
10+
github.com/andybalholm/brotli v1.0.6/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
911
github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs=
1012
github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c=
1113
github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA=
@@ -34,6 +36,8 @@ github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+l
3436
github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
3537
github.com/jellydator/ttlcache/v3 v3.3.0 h1:BdoC9cE81qXfrxeb9eoJi9dWrdhSuwXMAnHTbnBm4Wc=
3638
github.com/jellydator/ttlcache/v3 v3.3.0/go.mod h1:bj2/e0l4jRnQdrnSTaGTsh4GSXvMjQcy41i7th0GVGw=
39+
github.com/klauspost/compress v1.17.4 h1:Ej5ixsIri7BrIjBkRZLTo6ghwrEtHFk7ijlczPW4fZ4=
40+
github.com/klauspost/compress v1.17.4/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM=
3741
github.com/klauspost/cpuid/v2 v2.2.10 h1:tBs3QSyvjDyFTq3uoc/9xFpCuOsJQFNPiAhYdw2skhE=
3842
github.com/klauspost/cpuid/v2 v2.2.10/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
3943
github.com/libp2p/go-reuseport v0.4.0 h1:nR5KU7hD0WxXCJbmw7r2rhRYruNRl2koHw8fQscQm2s=
@@ -44,6 +48,8 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
4448
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
4549
github.com/redis/go-redis/v9 v9.8.0 h1:q3nRvjrlge/6UD7eTu/DSg2uYiU2mCL0G/uzBWqhicI=
4650
github.com/redis/go-redis/v9 v9.8.0/go.mod h1:huWgSWd8mW6+m0VPhJjSSQ+d6Nh1VICQ6Q5lHuCH/Iw=
51+
github.com/refraction-networking/utls v1.8.0 h1:L38krhiTAyj9EeiQQa2sg+hYb4qwLCqdMcpZrRfbONE=
52+
github.com/refraction-networking/utls v1.8.0/go.mod h1:jkSOEkLqn+S/jtpEHPOsVv/4V4EVnelwbMQl4vCWXAM=
4753
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
4854
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
4955
github.com/tg123/go-htpasswd v1.2.4 h1:HgH8KKCjdmo7jjXWN9k1nefPBd7Be3tFCTjc2jPraPU=

tlsutil/util.go

Lines changed: 186 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,12 @@ import (
44
"crypto/tls"
55
"crypto/x509"
66
"fmt"
7+
"net"
78
"net/url"
89
"os"
910
"strings"
11+
12+
utls "github.com/refraction-networking/utls"
1013
)
1114

1215
func ExpectPeerName(name string, roots *x509.CertPool) func(cs tls.ConnectionState) error {
@@ -222,3 +225,186 @@ func TLSConfigFromURL(u *url.URL) (*tls.Config, error) {
222225
}
223226
return tlsConfig, nil
224227
}
228+
229+
func TLSFactoryFromURL(u *url.URL) (func(c net.Conn, config *tls.Config) net.Conn, error) {
230+
params, err := url.ParseQuery(u.RawQuery)
231+
if err != nil {
232+
return nil, fmt.Errorf("unable to parse query string of proxy specification URL %q: %w", u.String(), err)
233+
}
234+
if params.Has("utls-fp") {
235+
var fp utls.ClientHelloID
236+
switch params.Get("utls-fp") {
237+
case "Hello360_11_0":
238+
fp = utls.Hello360_11_0
239+
case "Hello360_7_5":
240+
fp = utls.Hello360_7_5
241+
case "Hello360_Auto":
242+
fp = utls.Hello360_Auto
243+
case "HelloAndroid_11_OkHttp":
244+
fp = utls.HelloAndroid_11_OkHttp
245+
case "HelloChrome_100":
246+
fp = utls.HelloChrome_100
247+
case "HelloChrome_100_PSK":
248+
fp = utls.HelloChrome_100_PSK
249+
case "HelloChrome_102":
250+
fp = utls.HelloChrome_102
251+
case "HelloChrome_106_Shuffle":
252+
fp = utls.HelloChrome_106_Shuffle
253+
case "HelloChrome_112_PSK_Shuf":
254+
fp = utls.HelloChrome_112_PSK_Shuf
255+
case "HelloChrome_114_Padding_PSK_Shuf":
256+
fp = utls.HelloChrome_114_Padding_PSK_Shuf
257+
case "HelloChrome_115_PQ":
258+
fp = utls.HelloChrome_115_PQ
259+
case "HelloChrome_115_PQ_PSK":
260+
fp = utls.HelloChrome_115_PQ_PSK
261+
case "HelloChrome_120":
262+
fp = utls.HelloChrome_120
263+
case "HelloChrome_120_PQ":
264+
fp = utls.HelloChrome_120_PQ
265+
case "HelloChrome_131":
266+
fp = utls.HelloChrome_131
267+
case "HelloChrome_133":
268+
fp = utls.HelloChrome_133
269+
case "HelloChrome_58":
270+
fp = utls.HelloChrome_58
271+
case "HelloChrome_62":
272+
fp = utls.HelloChrome_62
273+
case "HelloChrome_70":
274+
fp = utls.HelloChrome_70
275+
case "HelloChrome_72":
276+
fp = utls.HelloChrome_72
277+
case "HelloChrome_83":
278+
fp = utls.HelloChrome_83
279+
case "HelloChrome_87":
280+
fp = utls.HelloChrome_87
281+
case "HelloChrome_96":
282+
fp = utls.HelloChrome_96
283+
case "HelloChrome_Auto":
284+
fp = utls.HelloChrome_Auto
285+
case "HelloCustom":
286+
fp = utls.HelloCustom
287+
case "HelloEdge_106":
288+
fp = utls.HelloEdge_106
289+
case "HelloEdge_85":
290+
fp = utls.HelloEdge_85
291+
case "HelloEdge_Auto":
292+
fp = utls.HelloEdge_Auto
293+
case "HelloFirefox_102":
294+
fp = utls.HelloFirefox_102
295+
case "HelloFirefox_105":
296+
fp = utls.HelloFirefox_105
297+
case "HelloFirefox_120":
298+
fp = utls.HelloFirefox_120
299+
case "HelloFirefox_55":
300+
fp = utls.HelloFirefox_55
301+
case "HelloFirefox_56":
302+
fp = utls.HelloFirefox_56
303+
case "HelloFirefox_63":
304+
fp = utls.HelloFirefox_63
305+
case "HelloFirefox_65":
306+
fp = utls.HelloFirefox_65
307+
case "HelloFirefox_99":
308+
fp = utls.HelloFirefox_99
309+
case "HelloFirefox_Auto":
310+
fp = utls.HelloFirefox_Auto
311+
case "HelloGolang":
312+
fp = utls.HelloGolang
313+
case "HelloIOS_11_1":
314+
fp = utls.HelloIOS_11_1
315+
case "HelloIOS_12_1":
316+
fp = utls.HelloIOS_12_1
317+
case "HelloIOS_13":
318+
fp = utls.HelloIOS_13
319+
case "HelloIOS_14":
320+
fp = utls.HelloIOS_14
321+
case "HelloIOS_Auto":
322+
fp = utls.HelloIOS_Auto
323+
case "HelloQQ_11_1":
324+
fp = utls.HelloQQ_11_1
325+
case "HelloQQ_Auto":
326+
fp = utls.HelloQQ_Auto
327+
case "HelloRandomized":
328+
fp = utls.HelloRandomized
329+
case "HelloRandomizedALPN":
330+
fp = utls.HelloRandomizedALPN
331+
case "HelloRandomizedNoALPN":
332+
fp = utls.HelloRandomizedNoALPN
333+
case "HelloSafari_16_0":
334+
fp = utls.HelloSafari_16_0
335+
case "HelloSafari_Auto":
336+
fp = utls.HelloSafari_Auto
337+
default:
338+
return nil, fmt.Errorf("unknown uTLS client hello ID %q", params.Get("utls-fp"))
339+
}
340+
return func(c net.Conn, config *tls.Config) net.Conn {
341+
var ucfg *utls.Config
342+
if config != nil {
343+
ucfg = &utls.Config{
344+
Rand: config.Rand,
345+
Time: config.Time,
346+
Certificates: castCertsToUCerts(config.Certificates),
347+
RootCAs: config.RootCAs,
348+
NextProtos: config.NextProtos,
349+
ServerName: config.ServerName,
350+
ClientAuth: utls.ClientAuthType(config.ClientAuth),
351+
ClientCAs: config.ClientCAs,
352+
InsecureSkipVerify: config.InsecureSkipVerify,
353+
CipherSuites: config.CipherSuites,
354+
PreferServerCipherSuites: config.PreferServerCipherSuites,
355+
SessionTicketsDisabled: config.SessionTicketsDisabled,
356+
SessionTicketKey: config.SessionTicketKey,
357+
MinVersion: config.MinVersion,
358+
MaxVersion: config.MaxVersion,
359+
CurvePreferences: castCurvesToUCurves(config.CurvePreferences),
360+
DynamicRecordSizingDisabled: config.DynamicRecordSizingDisabled,
361+
KeyLogWriter: config.KeyLogWriter,
362+
}
363+
}
364+
return utls.UClient(c, ucfg, fp)
365+
}, nil
366+
}
367+
return func(c net.Conn, config *tls.Config) net.Conn {
368+
return tls.Client(c, config)
369+
}, nil
370+
}
371+
372+
func castCertsToUCerts(certs []tls.Certificate) []utls.Certificate {
373+
if certs == nil {
374+
return nil
375+
}
376+
ucerts := make([]utls.Certificate, len(certs))
377+
for i, cert := range certs {
378+
ucerts[i] = utls.Certificate{
379+
Certificate: cert.Certificate,
380+
PrivateKey: cert.PrivateKey,
381+
SupportedSignatureAlgorithms: castSigSchemesToUSigSchemes(cert.SupportedSignatureAlgorithms),
382+
OCSPStaple: cert.OCSPStaple,
383+
SignedCertificateTimestamps: cert.SignedCertificateTimestamps,
384+
Leaf: cert.Leaf,
385+
}
386+
}
387+
return ucerts
388+
}
389+
390+
func castSigSchemesToUSigSchemes(schemes []tls.SignatureScheme) []utls.SignatureScheme {
391+
if schemes == nil {
392+
return nil
393+
}
394+
uschemes := make([]utls.SignatureScheme, len(schemes))
395+
for i, scheme := range schemes {
396+
uschemes[i] = utls.SignatureScheme(scheme)
397+
}
398+
return uschemes
399+
}
400+
401+
func castCurvesToUCurves(curves []tls.CurveID) []utls.CurveID {
402+
if curves == nil {
403+
return nil
404+
}
405+
ucurves := make([]utls.CurveID, len(curves))
406+
for i, curve := range curves {
407+
ucurves[i] = utls.CurveID(curve)
408+
}
409+
return ucurves
410+
}

0 commit comments

Comments
 (0)