package tls import ( "crypto/tls" "net" "github.com/metacubex/mihomo/common/once" "github.com/metacubex/mihomo/common/utils" "github.com/metacubex/mihomo/log" utls "github.com/metacubex/utls" "github.com/mroth/weightedrand/v2" ) type Conn = utls.Conn type UConn = utls.UConn type UClientHelloID = utls.ClientHelloID const VersionTLS13 = utls.VersionTLS13 func Client(c net.Conn, config *utls.Config) *Conn { return utls.Client(c, config) } func UClient(c net.Conn, config *utls.Config, fingerprint UClientHelloID) *UConn { return utls.UClient(c, config, fingerprint) } func GetFingerprint(clientFingerprint string) (UClientHelloID, bool) { if len(clientFingerprint) == 0 { clientFingerprint = globalFingerprint } if len(clientFingerprint) == 0 || clientFingerprint == "none" { return UClientHelloID{}, false } if clientFingerprint == "random" { fingerprint := randomFingerprint() log.Debugln("use initial random HelloID:%s", fingerprint.Client) return fingerprint, true } if fingerprint, ok := fingerprints[clientFingerprint]; ok { log.Debugln("use specified fingerprint:%s", fingerprint.Client) return fingerprint, true } else { log.Warnln("wrong clientFingerprint:%s", clientFingerprint) return UClientHelloID{}, false } } var randomFingerprint = once.OnceValue(func() UClientHelloID { chooser, _ := weightedrand.NewChooser( weightedrand.NewChoice("chrome", 6), weightedrand.NewChoice("safari", 3), weightedrand.NewChoice("ios", 2), weightedrand.NewChoice("firefox", 1), ) initClient := chooser.Pick() log.Debugln("initial random HelloID:%s", initClient) fingerprint, ok := fingerprints[initClient] if !ok { log.Warnln("error in initial random HelloID:%s", initClient) } return fingerprint }) var fingerprints = map[string]UClientHelloID{ "chrome": utls.HelloChrome_Auto, "chrome_psk": utls.HelloChrome_100_PSK, "chrome_psk_shuffle": utls.HelloChrome_106_Shuffle, "chrome_padding_psk_shuffle": utls.HelloChrome_114_Padding_PSK_Shuf, "chrome_pq": utls.HelloChrome_115_PQ, "chrome_pq_psk": utls.HelloChrome_115_PQ_PSK, "firefox": utls.HelloFirefox_Auto, "safari": utls.HelloSafari_Auto, "ios": utls.HelloIOS_Auto, "android": utls.HelloAndroid_11_OkHttp, "edge": utls.HelloEdge_Auto, "360": utls.Hello360_Auto, "qq": utls.HelloQQ_Auto, "random": {}, "randomized": utls.HelloRandomized, } func init() { weights := utls.DefaultWeights weights.TLSVersMax_Set_VersionTLS13 = 1 weights.FirstKeyShare_Set_CurveP256 = 0 randomized := utls.HelloRandomized randomized.Seed, _ = utls.NewPRNGSeed() randomized.Weights = &weights fingerprints["randomized"] = randomized } func UCertificates(it tls.Certificate) utls.Certificate { return utls.Certificate{ Certificate: it.Certificate, PrivateKey: it.PrivateKey, SupportedSignatureAlgorithms: utils.Map(it.SupportedSignatureAlgorithms, func(it tls.SignatureScheme) utls.SignatureScheme { return utls.SignatureScheme(it) }), OCSPStaple: it.OCSPStaple, SignedCertificateTimestamps: it.SignedCertificateTimestamps, Leaf: it.Leaf, } } type Config = utls.Config func UConfig(config *tls.Config) *utls.Config { return &utls.Config{ Rand: config.Rand, Time: config.Time, Certificates: utils.Map(config.Certificates, UCertificates), VerifyPeerCertificate: config.VerifyPeerCertificate, RootCAs: config.RootCAs, NextProtos: config.NextProtos, ServerName: config.ServerName, InsecureSkipVerify: config.InsecureSkipVerify, CipherSuites: config.CipherSuites, MinVersion: config.MinVersion, MaxVersion: config.MaxVersion, CurvePreferences: utils.Map(config.CurvePreferences, func(it tls.CurveID) utls.CurveID { return utls.CurveID(it) }), SessionTicketsDisabled: config.SessionTicketsDisabled, Renegotiation: utls.RenegotiationSupport(config.Renegotiation), } } // BuildWebsocketHandshakeState it will only send http/1.1 in its ALPN. // Copy from https://github.com/XTLS/Xray-core/blob/main/transport/internet/tls/tls.go func BuildWebsocketHandshakeState(c *UConn) error { // Build the handshake state. This will apply every variable of the TLS of the // fingerprint in the UConn if err := c.BuildHandshakeState(); err != nil { return err } // Iterate over extensions and check for utls.ALPNExtension hasALPNExtension := false for _, extension := range c.Extensions { if alpn, ok := extension.(*utls.ALPNExtension); ok { hasALPNExtension = true alpn.AlpnProtocols = []string{"http/1.1"} break } } if !hasALPNExtension { // Append extension if doesn't exists c.Extensions = append(c.Extensions, &utls.ALPNExtension{AlpnProtocols: []string{"http/1.1"}}) } // Rebuild the client hello if err := c.BuildHandshakeState(); err != nil { return err } return nil } var globalFingerprint string func SetGlobalFingerprint(fingerprint string) { globalFingerprint = fingerprint } func GetGlobalFingerprint() string { return globalFingerprint }