From b6b6413d0406d18692d70a72aed2d81621346e53 Mon Sep 17 00:00:00 2001 From: Skyxim Date: Sat, 14 Jan 2023 21:08:06 +0800 Subject: [PATCH] refactor: replace experimental.fingerprints with custom-certificates and Change the fingerprint verification logic to SSL pinning --- adapter/outbound/http.go | 2 +- adapter/outbound/hysteria.go | 2 +- adapter/outbound/shadowsocks.go | 2 +- adapter/outbound/socks5.go | 2 +- adapter/outbound/trojan.go | 2 +- adapter/outbound/tuic.go | 2 +- adapter/outbound/vless.go | 4 +- adapter/outbound/vmess.go | 2 +- component/tls/config.go | 133 +++++++++++++--------------- config/config.go | 8 +- dns/client.go | 2 +- dns/doh.go | 2 +- dns/doq.go | 2 +- hub/executor/executor.go | 9 +- hub/hub.go | 4 +- transport/trojan/trojan.go | 4 +- transport/v2ray-plugin/websocket.go | 2 +- transport/vless/xtls.go | 2 +- transport/vmess/tls.go | 2 +- 19 files changed, 91 insertions(+), 97 deletions(-) diff --git a/adapter/outbound/http.go b/adapter/outbound/http.go index 088dd8ff..720dc3e1 100644 --- a/adapter/outbound/http.go +++ b/adapter/outbound/http.go @@ -150,7 +150,7 @@ func NewHttp(option HttpOption) (*Http, error) { sni = option.SNI } if len(option.Fingerprint) == 0 { - tlsConfig = tlsC.GetGlobalFingerprintTLSConfig(&tls.Config{ + tlsConfig = tlsC.GetGlobalTLSConfig(&tls.Config{ InsecureSkipVerify: option.SkipCertVerify, ServerName: sni, }) diff --git a/adapter/outbound/hysteria.go b/adapter/outbound/hysteria.go index 9d32cb33..bd75cc3c 100644 --- a/adapter/outbound/hysteria.go +++ b/adapter/outbound/hysteria.go @@ -178,7 +178,7 @@ func NewHysteria(option HysteriaOption) (*Hysteria, error) { return nil, err } } else { - tlsConfig = tlsC.GetGlobalFingerprintTLSConfig(tlsConfig) + tlsConfig = tlsC.GetGlobalTLSConfig(tlsConfig) } if len(option.ALPN) > 0 { diff --git a/adapter/outbound/shadowsocks.go b/adapter/outbound/shadowsocks.go index 3e04b6ef..54566666 100644 --- a/adapter/outbound/shadowsocks.go +++ b/adapter/outbound/shadowsocks.go @@ -223,7 +223,7 @@ func NewShadowSocks(option ShadowSocksOption) (*ShadowSocks, error) { } if len(shadowTLSOpt.Fingerprint) == 0 { - tlsConfig = tlsC.GetGlobalFingerprintTLSConfig(tlsConfig) + tlsConfig = tlsC.GetGlobalTLSConfig(tlsConfig) } else { if tlsConfig, err = tlsC.GetSpecifiedFingerprintTLSConfig(tlsConfig, shadowTLSOpt.Fingerprint); err != nil { return nil, err diff --git a/adapter/outbound/socks5.go b/adapter/outbound/socks5.go index c76707c3..d40a6bff 100644 --- a/adapter/outbound/socks5.go +++ b/adapter/outbound/socks5.go @@ -167,7 +167,7 @@ func NewSocks5(option Socks5Option) (*Socks5, error) { } if len(option.Fingerprint) == 0 { - tlsConfig = tlsC.GetGlobalFingerprintTLSConfig(tlsConfig) + tlsConfig = tlsC.GetGlobalTLSConfig(tlsConfig) } else { var err error if tlsConfig, err = tlsC.GetSpecifiedFingerprintTLSConfig(tlsConfig, option.Fingerprint); err != nil { diff --git a/adapter/outbound/trojan.go b/adapter/outbound/trojan.go index 99c49345..c401999f 100644 --- a/adapter/outbound/trojan.go +++ b/adapter/outbound/trojan.go @@ -268,7 +268,7 @@ func NewTrojan(option TrojanOption) (*Trojan, error) { } if len(option.Fingerprint) == 0 { - tlsConfig = tlsC.GetGlobalFingerprintTLSConfig(tlsConfig) + tlsConfig = tlsC.GetGlobalTLSConfig(tlsConfig) } else { var err error if tlsConfig, err = tlsC.GetSpecifiedFingerprintTLSConfig(tlsConfig, option.Fingerprint); err != nil { diff --git a/adapter/outbound/tuic.go b/adapter/outbound/tuic.go index 3bd750c1..417339be 100644 --- a/adapter/outbound/tuic.go +++ b/adapter/outbound/tuic.go @@ -143,7 +143,7 @@ func NewTuic(option TuicOption) (*Tuic, error) { return nil, err } } else { - tlsConfig = tlsC.GetGlobalFingerprintTLSConfig(tlsConfig) + tlsConfig = tlsC.GetGlobalTLSConfig(tlsConfig) } if len(option.ALPN) > 0 { diff --git a/adapter/outbound/vless.go b/adapter/outbound/vless.go index a0d711cb..449663f7 100644 --- a/adapter/outbound/vless.go +++ b/adapter/outbound/vless.go @@ -98,7 +98,7 @@ func (v *Vless) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) { } if len(v.option.Fingerprint) == 0 { - wsOpts.TLSConfig = tlsC.GetGlobalFingerprintTLSConfig(tlsConfig) + wsOpts.TLSConfig = tlsC.GetGlobalTLSConfig(tlsConfig) } else { wsOpts.TLSConfig, err = tlsC.GetSpecifiedFingerprintTLSConfig(tlsConfig, v.option.Fingerprint) } @@ -522,7 +522,7 @@ func NewVless(option VlessOption) (*Vless, error) { ServiceName: v.option.GrpcOpts.GrpcServiceName, Host: v.option.ServerName, } - tlsConfig := tlsC.GetGlobalFingerprintTLSConfig(&tls.Config{ + tlsConfig := tlsC.GetGlobalTLSConfig(&tls.Config{ InsecureSkipVerify: v.option.SkipCertVerify, ServerName: v.option.ServerName, }) diff --git a/adapter/outbound/vmess.go b/adapter/outbound/vmess.go index 54e5f865..999e1283 100644 --- a/adapter/outbound/vmess.go +++ b/adapter/outbound/vmess.go @@ -115,7 +115,7 @@ func (v *Vmess) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) { } if len(v.option.Fingerprint) == 0 { - wsOpts.TLSConfig = tlsC.GetGlobalFingerprintTLSConfig(tlsConfig) + wsOpts.TLSConfig = tlsC.GetGlobalTLSConfig(tlsConfig) } else { if wsOpts.TLSConfig, err = tlsC.GetSpecifiedFingerprintTLSConfig(tlsConfig, v.option.Fingerprint); err != nil { return nil, err diff --git a/component/tls/config.go b/component/tls/config.go index 6f5b1107..14e5b20d 100644 --- a/component/tls/config.go +++ b/component/tls/config.go @@ -6,63 +6,57 @@ import ( "crypto/tls" "crypto/x509" "encoding/hex" + "errors" "fmt" + "strings" "sync" - "time" + + CN "github.com/Dreamacro/clash/common/net" xtls "github.com/xtls/go" ) -var globalFingerprints = make([][32]byte, 0) -var mutex sync.Mutex +var tlsCertificates = make([]tls.Certificate, 0) -func verifyPeerCertificateAndFingerprints(fingerprints *[][32]byte, insecureSkipVerify bool) func(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error { +var mutex sync.RWMutex +var errNotMacth error = errors.New("certificate fingerprints do not match") + +func AddCertificate(privateKey, certificate string) error { + mutex.Lock() + defer mutex.Unlock() + if cert, err := CN.ParseCert(certificate, privateKey); err != nil { + return err + } else { + tlsCertificates = append(tlsCertificates, cert) + } + return nil +} + +func GetCertificates() []tls.Certificate { + mutex.RLock() + defer mutex.RUnlock() + return tlsCertificates +} + +func verifyFingerprint(fingerprint *[32]byte) func(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error { return func(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error { - if insecureSkipVerify { - return nil - } - - var preErr error + // ssl pining for i := range rawCerts { rawCert := rawCerts[i] cert, err := x509.ParseCertificate(rawCert) if err == nil { - opts := x509.VerifyOptions{ - CurrentTime: time.Now(), - } - - if _, err := cert.Verify(opts); err == nil { + hash := sha256.Sum256(cert.Raw) + if bytes.Equal(fingerprint[:], hash[:]) { return nil - } else { - fingerprint := sha256.Sum256(cert.Raw) - for _, fp := range *fingerprints { - if bytes.Equal(fingerprint[:], fp[:]) { - return nil - } - } - - preErr = err } } } - - return preErr + return errNotMacth } } -func AddCertFingerprint(fingerprint string) error { - fpByte, err2 := convertFingerprint(fingerprint) - if err2 != nil { - return err2 - } - - mutex.Lock() - globalFingerprints = append(globalFingerprints, *fpByte) - mutex.Unlock() - return nil -} - func convertFingerprint(fingerprint string) (*[32]byte, error) { + fingerprint = strings.TrimSpace(strings.Replace(fingerprint, ":", "", -1)) fpByte, err := hex.DecodeString(fingerprint) if err != nil { return nil, err @@ -75,7 +69,7 @@ func convertFingerprint(fingerprint string) (*[32]byte, error) { } func GetDefaultTLSConfig() *tls.Config { - return GetGlobalFingerprintTLSConfig(nil) + return GetGlobalTLSConfig(nil) } // GetSpecifiedFingerprintTLSConfig specified fingerprint @@ -83,33 +77,20 @@ func GetSpecifiedFingerprintTLSConfig(tlsConfig *tls.Config, fingerprint string) if fingerprintBytes, err := convertFingerprint(fingerprint); err != nil { return nil, err } else { - if tlsConfig == nil { - return &tls.Config{ - InsecureSkipVerify: true, - VerifyPeerCertificate: verifyPeerCertificateAndFingerprints(&[][32]byte{*fingerprintBytes}, false), - }, nil - } else { - tlsConfig.VerifyPeerCertificate = verifyPeerCertificateAndFingerprints(&[][32]byte{*fingerprintBytes}, tlsConfig.InsecureSkipVerify) - tlsConfig.InsecureSkipVerify = true - return tlsConfig, nil - } + tlsConfig = GetGlobalTLSConfig(tlsConfig) + tlsConfig.VerifyPeerCertificate = verifyFingerprint(fingerprintBytes) + tlsConfig.InsecureSkipVerify = true + return tlsConfig, nil } } -func GetGlobalFingerprintTLSConfig(tlsConfig *tls.Config) *tls.Config { - // If there's at least one fingerprint then we could skip the general check - // If there's no fingerprints but the config insists then we should skip. - // Otherwise we should do a general verification. - shouldSkipVerify := len(globalFingerprints) != 0 || tlsConfig != nil && tlsConfig.InsecureSkipVerify +func GetGlobalTLSConfig(tlsConfig *tls.Config) *tls.Config { if tlsConfig == nil { return &tls.Config{ - InsecureSkipVerify: shouldSkipVerify, - VerifyPeerCertificate: verifyPeerCertificateAndFingerprints(&globalFingerprints, false), + Certificates: tlsCertificates, } } - - tlsConfig.VerifyPeerCertificate = verifyPeerCertificateAndFingerprints(&globalFingerprints, tlsConfig.InsecureSkipVerify) - tlsConfig.InsecureSkipVerify = shouldSkipVerify + tlsConfig.Certificates = append(tlsConfig.Certificates, tlsCertificates...) return tlsConfig } @@ -118,29 +99,37 @@ func GetSpecifiedFingerprintXTLSConfig(tlsConfig *xtls.Config, fingerprint strin if fingerprintBytes, err := convertFingerprint(fingerprint); err != nil { return nil, err } else { - if tlsConfig == nil { - return &xtls.Config{ - InsecureSkipVerify: true, - VerifyPeerCertificate: verifyPeerCertificateAndFingerprints(&[][32]byte{*fingerprintBytes}, false), - }, nil - } else { - tlsConfig.VerifyPeerCertificate = verifyPeerCertificateAndFingerprints(&[][32]byte{*fingerprintBytes}, tlsConfig.InsecureSkipVerify) + tlsConfig=GetGlobalXTLSConfig(tlsConfig) + tlsConfig.VerifyPeerCertificate = verifyFingerprint(fingerprintBytes) tlsConfig.InsecureSkipVerify = true return tlsConfig, nil - } } } -func GetGlobalFingerprintXTLSConfig(tlsConfig *xtls.Config) *xtls.Config { - shouldSkipVerify := len(globalFingerprints) != 0 || tlsConfig != nil && tlsConfig.InsecureSkipVerify +func GetGlobalXTLSConfig(tlsConfig *xtls.Config) *xtls.Config { + xtlsCerts := make([]xtls.Certificate, len(tlsCertificates)) + for _, cert := range tlsCertificates { + tlsSsaList := make([]xtls.SignatureScheme, len(cert.SupportedSignatureAlgorithms)) + for _, ssa := range cert.SupportedSignatureAlgorithms { + tlsSsa := xtls.SignatureScheme(ssa) + tlsSsaList = append(tlsSsaList, tlsSsa) + } + xtlsCert := xtls.Certificate{ + Certificate: cert.Certificate, + PrivateKey: cert.PrivateKey, + OCSPStaple: cert.OCSPStaple, + SignedCertificateTimestamps: cert.SignedCertificateTimestamps, + Leaf: cert.Leaf, + SupportedSignatureAlgorithms: tlsSsaList, + } + xtlsCerts = append(xtlsCerts, xtlsCert) + } if tlsConfig == nil { return &xtls.Config{ - InsecureSkipVerify: shouldSkipVerify, - VerifyPeerCertificate: verifyPeerCertificateAndFingerprints(&globalFingerprints, false), + Certificates: xtlsCerts, } } - tlsConfig.VerifyPeerCertificate = verifyPeerCertificateAndFingerprints(&globalFingerprints, tlsConfig.InsecureSkipVerify) - tlsConfig.InsecureSkipVerify = shouldSkipVerify + tlsConfig.Certificates = xtlsCerts return tlsConfig } diff --git a/config/config.go b/config/config.go index e2250e8a..43ff5872 100644 --- a/config/config.go +++ b/config/config.go @@ -4,7 +4,6 @@ import ( "container/list" "errors" "fmt" - P "github.com/Dreamacro/clash/component/process" "net" "net/netip" "net/url" @@ -14,6 +13,8 @@ import ( "strings" "time" + P "github.com/Dreamacro/clash/component/process" + "github.com/Dreamacro/clash/adapter" "github.com/Dreamacro/clash/adapter/outbound" "github.com/Dreamacro/clash/adapter/outboundgroup" @@ -116,6 +117,11 @@ type Profile struct { } type TLS struct { + RawCert + CustomTrustCert []RawCert `yaml:"custom-certifactes"` +} + +type RawCert struct { Certificate string `yaml:"certificate"` PrivateKey string `yaml:"private-key"` } diff --git a/dns/client.go b/dns/client.go index 30fd25c8..fe9362bb 100644 --- a/dns/client.go +++ b/dns/client.go @@ -78,7 +78,7 @@ func (c *client) ExchangeContext(ctx context.Context, m *D.Msg) (*D.Msg, error) ch := make(chan result, 1) go func() { if strings.HasSuffix(c.Client.Net, "tls") { - conn = tls.Client(conn, tlsC.GetGlobalFingerprintTLSConfig(c.Client.TLSConfig)) + conn = tls.Client(conn, tlsC.GetGlobalTLSConfig(c.Client.TLSConfig)) } msg, _, err := c.Client.ExchangeWithConn(m, &D.Conn{ diff --git a/dns/doh.go b/dns/doh.go index ca694fb9..5abd0479 100644 --- a/dns/doh.go +++ b/dns/doh.go @@ -374,7 +374,7 @@ func (doh *dnsOverHTTPS) createClient(ctx context.Context) (*http.Client, error) // HTTP3 is enabled in the upstream options). If this attempt is successful, // it returns an HTTP3 transport, otherwise it returns the H1/H2 transport. func (doh *dnsOverHTTPS) createTransport(ctx context.Context) (t http.RoundTripper, err error) { - tlsConfig := tlsC.GetGlobalFingerprintTLSConfig( + tlsConfig := tlsC.GetGlobalTLSConfig( &tls.Config{ InsecureSkipVerify: false, MinVersion: tls.VersionTLS12, diff --git a/dns/doq.go b/dns/doq.go index 85c3a85c..1097b500 100644 --- a/dns/doq.go +++ b/dns/doq.go @@ -298,7 +298,7 @@ func (doq *dnsOverQUIC) openStream(ctx context.Context, conn quic.Connection) (q // openConnection opens a new QUIC connection. func (doq *dnsOverQUIC) openConnection(ctx context.Context) (conn quic.Connection, err error) { - tlsConfig := tlsC.GetGlobalFingerprintTLSConfig( + tlsConfig := tlsC.GetGlobalTLSConfig( &tls.Config{ InsecureSkipVerify: false, NextProtos: []string{ diff --git a/hub/executor/executor.go b/hub/executor/executor.go index 55786864..be682f36 100644 --- a/hub/executor/executor.go +++ b/hub/executor/executor.go @@ -18,7 +18,7 @@ import ( "github.com/Dreamacro/clash/component/profile/cachefile" "github.com/Dreamacro/clash/component/resolver" SNI "github.com/Dreamacro/clash/component/sniffer" - "github.com/Dreamacro/clash/component/tls" + CTLS "github.com/Dreamacro/clash/component/tls" "github.com/Dreamacro/clash/component/trie" "github.com/Dreamacro/clash/config" C "github.com/Dreamacro/clash/constant" @@ -146,10 +146,9 @@ func updateExperimental(c *config.Config) { } func preUpdateExperimental(c *config.Config) { - for _, fingerprint := range c.Experimental.Fingerprints { - if err := tls.AddCertFingerprint(fingerprint); err != nil { - log.Warnln("fingerprint[%s] is err, %s", fingerprint, err.Error()) - } + CTLS.AddCertificate(c.TLS.PrivateKey, c.TLS.Certificate) + for _, c := range c.TLS.CustomTrustCert { + CTLS.AddCertificate(c.PrivateKey, c.Certificate) } } diff --git a/hub/hub.go b/hub/hub.go index ee18e70a..1e925bfe 100644 --- a/hub/hub.go +++ b/hub/hub.go @@ -42,8 +42,8 @@ func Parse(options ...Option) error { } if cfg.General.ExternalController != "" { - go route.Start(cfg.General.ExternalController,cfg.General.ExternalControllerTLS, - cfg.General.Secret,cfg.TLS.Certificate,cfg.TLS.PrivateKey) + go route.Start(cfg.General.ExternalController, cfg.General.ExternalControllerTLS, + cfg.General.Secret, cfg.TLS.Certificate, cfg.TLS.PrivateKey) } executor.ApplyConfig(cfg, true) diff --git a/transport/trojan/trojan.go b/transport/trojan/trojan.go index 561f8765..ca7b9425 100644 --- a/transport/trojan/trojan.go +++ b/transport/trojan/trojan.go @@ -82,7 +82,7 @@ func (t *Trojan) StreamConn(conn net.Conn) (net.Conn, error) { } if len(t.option.Fingerprint) == 0 { - xtlsConfig = tlsC.GetGlobalFingerprintXTLSConfig(xtlsConfig) + xtlsConfig = tlsC.GetGlobalXTLSConfig(xtlsConfig) } else { var err error if xtlsConfig, err = tlsC.GetSpecifiedFingerprintXTLSConfig(xtlsConfig, t.option.Fingerprint); err != nil { @@ -107,7 +107,7 @@ func (t *Trojan) StreamConn(conn net.Conn) (net.Conn, error) { } if len(t.option.Fingerprint) == 0 { - tlsConfig = tlsC.GetGlobalFingerprintTLSConfig(tlsConfig) + tlsConfig = tlsC.GetGlobalTLSConfig(tlsConfig) } else { var err error if tlsConfig, err = tlsC.GetSpecifiedFingerprintTLSConfig(tlsConfig, t.option.Fingerprint); err != nil { diff --git a/transport/v2ray-plugin/websocket.go b/transport/v2ray-plugin/websocket.go index 56b0e481..7c2c8a88 100644 --- a/transport/v2ray-plugin/websocket.go +++ b/transport/v2ray-plugin/websocket.go @@ -43,7 +43,7 @@ func NewV2rayObfs(conn net.Conn, option *Option) (net.Conn, error) { NextProtos: []string{"http/1.1"}, } if len(option.Fingerprint) == 0 { - config.TLSConfig = tlsC.GetGlobalFingerprintTLSConfig(tlsConfig) + config.TLSConfig = tlsC.GetGlobalTLSConfig(tlsConfig) } else { var err error if config.TLSConfig, err = tlsC.GetSpecifiedFingerprintTLSConfig(tlsConfig, option.Fingerprint); err != nil { diff --git a/transport/vless/xtls.go b/transport/vless/xtls.go index 0b461c56..a1aea44f 100644 --- a/transport/vless/xtls.go +++ b/transport/vless/xtls.go @@ -23,7 +23,7 @@ func StreamXTLSConn(conn net.Conn, cfg *XTLSConfig) (net.Conn, error) { NextProtos: cfg.NextProtos, } if len(cfg.Fingerprint) == 0 { - xtlsConfig = tlsC.GetGlobalFingerprintXTLSConfig(xtlsConfig) + xtlsConfig = tlsC.GetGlobalXTLSConfig(xtlsConfig) } else { var err error if xtlsConfig, err = tlsC.GetSpecifiedFingerprintXTLSConfig(xtlsConfig, cfg.Fingerprint); err != nil { diff --git a/transport/vmess/tls.go b/transport/vmess/tls.go index 8ac80ce6..02442771 100644 --- a/transport/vmess/tls.go +++ b/transport/vmess/tls.go @@ -24,7 +24,7 @@ func StreamTLSConn(conn net.Conn, cfg *TLSConfig) (net.Conn, error) { } if len(cfg.FingerPrint) == 0 { - tlsConfig = tlsC.GetGlobalFingerprintTLSConfig(tlsConfig) + tlsConfig = tlsC.GetGlobalTLSConfig(tlsConfig) } else { var err error if tlsConfig, err = tlsC.GetSpecifiedFingerprintTLSConfig(tlsConfig, cfg.FingerPrint); err != nil {