package tls import ( "bytes" "context" "crypto/hmac" "crypto/rand" "crypto/tls" "crypto/x509" "os" "slices" "strings" "sync" "sync/atomic" "time" "unsafe" "github.com/xtls/xray-core/common/errors" "github.com/xtls/xray-core/common/net" "github.com/xtls/xray-core/common/protocol/tls/cert" "github.com/xtls/xray-core/transport/internet" ) var globalSessionCache = tls.NewLRUClientSessionCache(128) // ParseCertificate converts a cert.Certificate to Certificate. func ParseCertificate(c *cert.Certificate) *Certificate { if c != nil { certPEM, keyPEM := c.ToPEM() return &Certificate{ Certificate: certPEM, Key: keyPEM, } } return nil } func (c *Config) loadSelfCertPool() (*x509.CertPool, error) { root := x509.NewCertPool() for _, cert := range c.Certificate { if cert.Usage != Certificate_AUTHORITY_VERIFY { continue } if !root.AppendCertsFromPEM(cert.Certificate) { return nil, errors.New("failed to append cert").AtWarning() } } return root, nil } func isCertificateExpired(c *tls.Certificate) bool { if c.Leaf == nil && len(c.Certificate) > 0 { if pc, err := x509.ParseCertificate(c.Certificate[0]); err == nil { c.Leaf = pc } } // If leaf is not there, the certificate is probably not used yet. We trust user to provide a valid certificate. return c.Leaf != nil && c.Leaf.NotAfter.Before(time.Now().Add(time.Minute*2)) } func issueCertificate(rawCA *Certificate, domain string) (*tls.Certificate, error) { parent, err := cert.ParseCertificate(rawCA.Certificate, rawCA.Key) if err != nil { return nil, errors.New("failed to parse raw certificate").Base(err) } newCert, err := cert.Generate(parent, cert.CommonName(domain), cert.DNSNames(domain)) if err != nil { return nil, errors.New("failed to generate new certificate for ", domain).Base(err) } newCertPEM, newKeyPEM := newCert.ToPEM() if rawCA.BuildChain { newCertPEM = bytes.Join([][]byte{newCertPEM, rawCA.Certificate}, []byte("\n")) } cert, err := tls.X509KeyPair(newCertPEM, newKeyPEM) return &cert, err } func (c *Config) getCustomCA() []*Certificate { certs := make([]*Certificate, 0, len(c.Certificate)) for _, certificate := range c.Certificate { if certificate.Usage == Certificate_AUTHORITY_ISSUE { certs = append(certs, certificate) setupHotReload(certificate) } } return certs } func (c *Config) getClientCert() []*Certificate { certs := make([]*Certificate, 0, len(c.Certificate)) for _, certificate := range c.Certificate { if certificate.Usage == Certificate_MTLS_CLIENT_CERT { certs = append(certs, certificate) setupHotReload(certificate) } } return certs } func (c *Config) getClientCA() []*Certificate { certs := make([]*Certificate, 0, len(c.Certificate)) for _, certificate := range c.Certificate { if certificate.Usage == Certificate_MTLS_CLIENT_CA { certs = append(certs, certificate) } } return certs } func getGetCertificateFunc(c *tls.Config, ca []*Certificate) func(hello *tls.ClientHelloInfo) (*tls.Certificate, error) { var access sync.RWMutex return func(hello *tls.ClientHelloInfo) (*tls.Certificate, error) { domain := hello.ServerName certExpired := false access.RLock() certificate, found := c.NameToCertificate[domain] access.RUnlock() if found { if !isCertificateExpired(certificate) { return certificate, nil } certExpired = true } if certExpired { newCerts := make([]tls.Certificate, 0, len(c.Certificates)) access.Lock() for _, certificate := range c.Certificates { if !isCertificateExpired(&certificate) { newCerts = append(newCerts, certificate) } else if certificate.Leaf != nil { expTime := certificate.Leaf.NotAfter.Format(time.RFC3339) errors.LogInfo(context.Background(), "old certificate for ", domain, " (expire on ", expTime, ") discarded") } } c.Certificates = newCerts access.Unlock() } var issuedCertificate *tls.Certificate // Create a new certificate from existing CA if possible for _, rawCert := range ca { if rawCert.Usage == Certificate_AUTHORITY_ISSUE { newCert, err := issueCertificate(rawCert, domain) if err != nil { errors.LogInfoInner(context.Background(), err, "failed to issue new certificate for ", domain) continue } parsed, err := x509.ParseCertificate(newCert.Certificate[0]) if err == nil { newCert.Leaf = parsed expTime := parsed.NotAfter.Format(time.RFC3339) errors.LogInfo(context.Background(), "new certificate for ", domain, " (expire on ", expTime, ") issued") } else { errors.LogInfoInner(context.Background(), err, "failed to parse new certificate for ", domain) } access.Lock() c.Certificates = append(c.Certificates, *newCert) issuedCertificate = &c.Certificates[len(c.Certificates)-1] access.Unlock() break } } if issuedCertificate == nil { return nil, errors.New("failed to create a new certificate for ", domain) } access.Lock() c.BuildNameToCertificate() access.Unlock() return issuedCertificate, nil } } type extraProtoCertData struct { parsed atomic.Pointer[tls.Certificate] ocspData atomic.Pointer[[]byte] lastReload int64 } // atomic.Pointer must be exactly one pointer word for the ParsedCache overlay below. var _ [unsafe.Sizeof(unsafe.Pointer(nil))]byte = [unsafe.Sizeof(atomic.Pointer[extraProtoCertData]{})]byte{} func (c *Certificate) extraData() *extraProtoCertData { // wtf is this slot := (*atomic.Pointer[extraProtoCertData])(unsafe.Pointer(&c.ExtraData)) if s := slot.Load(); s != nil { return s } s := &extraProtoCertData{} if slot.CompareAndSwap(nil, s) { return s } return slot.Load() } func (c *Certificate) parseX509KeyPair() *tls.Certificate { keyPair, err := tls.X509KeyPair(c.Certificate, c.Key) if err != nil { errors.LogWarningInner(context.Background(), err, "ignoring invalid X509 key pair") return nil } keyPair.Leaf, err = x509.ParseCertificate(keyPair.Certificate[0]) if err != nil { errors.LogWarningInner(context.Background(), err, "ignoring invalid certificate") return nil } st := c.extraData() if OCSPData := st.ocspData.Load(); OCSPData != nil { keyPair.OCSPStaple = *OCSPData } st.parsed.Store(&keyPair) return &keyPair } func (c *Certificate) getX509KeyPair() *tls.Certificate { if keyPair := c.extraData().parsed.Load(); keyPair != nil { return keyPair } return c.parseX509KeyPair() } func (c *Config) getCertificate(hello *tls.ClientHelloInfo) (*tls.Certificate, error) { var defaultCert *tls.Certificate for _, cert := range c.Certificate { if cert.Usage == Certificate_ENCIPHERMENT { defaultCert = cert.getX509KeyPair() if defaultCert != nil { break } } } if defaultCert == nil { return nil, errNoCertificates } sni := strings.ToLower(hello.ServerName) if !c.RejectUnknownSni && (len(c.Certificate) == 1 || sni == "") { return defaultCert, nil } gsni := "*" if index := strings.IndexByte(sni, '.'); index != -1 { gsni += sni[index:] } for _, rawCertificate := range c.Certificate { if rawCertificate.Usage != Certificate_ENCIPHERMENT { continue } keyPair := rawCertificate.getX509KeyPair() if keyPair == nil { continue } if keyPair.Leaf.Subject.CommonName == sni || keyPair.Leaf.Subject.CommonName == gsni { return keyPair, nil } for _, name := range keyPair.Leaf.DNSNames { if name == sni || name == gsni { return keyPair, nil } } } if c.RejectUnknownSni { return nil, errNoCertificates } return defaultCert, nil } func (c *Config) getClientCertificate(cri *tls.CertificateRequestInfo) (*tls.Certificate, error) { for _, cert := range c.Certificate { parsed := cert.getX509KeyPair() if cert.Usage != Certificate_MTLS_CLIENT_CERT || parsed == nil { continue } if err := cri.SupportsCertificate(parsed); err == nil { return parsed, nil } } return nil, errNoCertificates } func (c *Config) parseServerName() string { if IsFromMitm(c.ServerName) { return "" } return c.ServerName } func (r *RandCarrier) verifyPeerCert(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) (err error) { // extract x509 certificates from rawCerts (verifiedChains will be nil if InsecureSkipVerify is true) certs := make([]*x509.Certificate, len(rawCerts)) for i, asn1Data := range rawCerts { certs[i], _ = x509.ParseCertificate(asn1Data) } if len(certs) == 0 { return errors.New("unexpected certs") } // directly return success if pinned cert is leaf // or replace RootCAs if pinned cert is CA (and can be used in VerifyPeerCertByName) CAs := r.RootCAs var verifyResult verifyResult var verifiedCert *x509.Certificate if r.PinnedPeerCertSha256 != nil { verifyResult, verifiedCert = verifyChain(certs, r.PinnedPeerCertSha256) switch verifyResult { case certNotFound: return errors.New("peer cert is unrecognized (against pinnedPeerCertSha256)") case foundLeaf: return nil case foundCA: CAs = x509.NewCertPool() CAs.AddCert(verifiedCert) default: panic("impossible pinnedPeerCertSha256 verify result") } } if r.VerifyPeerCertByName != nil { // RAW's Dial() may make it empty but not nil opts := x509.VerifyOptions{ Roots: CAs, CurrentTime: time.Now(), Intermediates: x509.NewCertPool(), } for _, cert := range certs[1:] { opts.Intermediates.AddCert(cert) } for _, opts.DNSName = range r.VerifyPeerCertByName { if _, err := certs[0].Verify(opts); err == nil { return nil } } if verifyResult == foundCA { errors.New("peer cert is invalid (against pinned CA and verifyPeerCertByName)") } return errors.New("peer cert is invalid (against root CAs and verifyPeerCertByName)") } if verifyResult == foundCA { // if found CA, we need to verify here opts := x509.VerifyOptions{ Roots: CAs, CurrentTime: time.Now(), Intermediates: x509.NewCertPool(), DNSName: r.Config.ServerName, } for _, cert := range certs[1:] { opts.Intermediates.AddCert(cert) } if _, err := certs[0].Verify(opts); err == nil { return nil } return errors.New("peer cert is invalid (against pinned CA and serverName)") } return nil // r.PinnedPeerCertSha256==nil && r.verifyPeerCertByName==nil } type RandCarrier struct { Config *tls.Config RootCAs *x509.CertPool VerifyPeerCertByName []string PinnedPeerCertSha256 [][]byte } func (r *RandCarrier) Read(p []byte) (n int, err error) { return rand.Read(p) } // GetTLSConfig converts this Config into tls.Config. func (c *Config) GetTLSConfig(opts ...Option) *tls.Config { root, err := c.getCertPool() if err != nil { errors.LogErrorInner(context.Background(), err, "failed to load system root certificate") } if c == nil { return &tls.Config{ ClientSessionCache: globalSessionCache, RootCAs: root, SessionTicketsDisabled: true, } } randCarrier := &RandCarrier{ RootCAs: root, VerifyPeerCertByName: slices.Clone(c.VerifyPeerCertByName), PinnedPeerCertSha256: c.PinnedPeerCertSha256, } config := &tls.Config{ Rand: randCarrier, ClientSessionCache: globalSessionCache, RootCAs: root, NextProtos: slices.Clone(c.NextProtocol), SessionTicketsDisabled: !c.EnableSessionResumption, VerifyPeerCertificate: randCarrier.verifyPeerCert, } randCarrier.Config = config if len(c.VerifyPeerCertByName) > 0 { config.InsecureSkipVerify = true } else { randCarrier.VerifyPeerCertByName = nil } if len(c.PinnedPeerCertSha256) > 0 { config.InsecureSkipVerify = true } else { randCarrier.PinnedPeerCertSha256 = nil } for _, opt := range opts { opt(config) } caCerts := c.getCustomCA() if len(caCerts) > 0 { config.GetCertificate = getGetCertificateFunc(config, caCerts) } else { for _, cert := range c.Certificate { if cert.Usage == Certificate_ENCIPHERMENT { setupHotReload(cert) } } config.GetCertificate = c.getCertificate } if len(c.getClientCert()) > 0 { config.GetClientCertificate = c.getClientCertificate } if clientCA := c.getClientCA(); len(clientCA) > 0 { clientCAPool := x509.NewCertPool() for _, cert := range clientCA { if !clientCAPool.AppendCertsFromPEM(cert.Certificate) { errors.LogError(context.Background(), errors.New("failed to append client CA certificate")) } } config.ClientCAs = clientCAPool } if sn := c.parseServerName(); len(sn) > 0 { config.ServerName = sn } if len(c.CurvePreferences) > 0 { config.CurvePreferences = ParseCurveName(c.CurvePreferences) } if len(config.NextProtos) == 0 { config.NextProtos = []string{"h2", "http/1.1"} } switch c.MinVersion { case "1.0": config.MinVersion = tls.VersionTLS10 case "1.1": config.MinVersion = tls.VersionTLS11 case "1.2": config.MinVersion = tls.VersionTLS12 case "1.3": config.MinVersion = tls.VersionTLS13 } switch c.MaxVersion { case "1.0": config.MaxVersion = tls.VersionTLS10 case "1.1": config.MaxVersion = tls.VersionTLS11 case "1.2": config.MaxVersion = tls.VersionTLS12 case "1.3": config.MaxVersion = tls.VersionTLS13 } if len(c.CipherSuites) > 0 { id := make(map[string]uint16) for _, s := range tls.CipherSuites() { id[s.Name] = s.ID } for _, n := range strings.Split(c.CipherSuites, ":") { if id[n] != 0 { config.CipherSuites = append(config.CipherSuites, id[n]) } } } if len(c.MasterKeyLog) > 0 && c.MasterKeyLog != "none" { writer, err := os.OpenFile(c.MasterKeyLog, os.O_CREATE|os.O_RDWR|os.O_APPEND, 0o644) if err != nil { errors.LogErrorInner(context.Background(), err, "failed to open ", c.MasterKeyLog, " as master key log") } else { config.KeyLogWriter = writer } } if len(c.EchConfigList) > 0 || len(c.EchServerKeys) > 0 { err := ApplyECH(c, config) if err != nil { errors.LogError(context.Background(), err) } } config.ClientAuth = ParseClientAuth(c.ClientAuth) if config.ClientAuth >= tls.VerifyClientCertIfGiven && config.ClientCAs == nil { errors.LogWarning(context.Background(), "clientAuth is set to ", c.ClientAuth, " but no client CA is provided") } return config } // Option for building TLS config. type Option func(*tls.Config) // WithDestination sets the server name in TLS config. // Due to the incorrect structure of GetTLSConfig(), the config.ServerName will always be empty. // So the real logic for SNI is: // set it to dest -> overwrite it with servername(if it's len>0). func WithDestination(dest net.Destination) Option { return func(config *tls.Config) { if config.ServerName == "" { config.ServerName = dest.Address.String() } } } func WithOverrideName(serverName string) Option { return func(config *tls.Config) { config.ServerName = serverName } } // WithNextProto sets the ALPN values in TLS config. func WithNextProto(protocol ...string) Option { return func(config *tls.Config) { if len(config.NextProtos) == 0 { config.NextProtos = protocol } } } // ConfigFromStreamSettings fetches Config from stream settings. Nil if not found. func ConfigFromStreamSettings(settings *internet.MemoryStreamConfig) *Config { if settings == nil { return nil } config, ok := settings.SecuritySettings.(*Config) if !ok { return nil } return config } var curveMap = map[string]tls.CurveID{ "curvep256": tls.CurveP256, "curvep384": tls.CurveP384, "curvep521": tls.CurveP521, "x25519": tls.X25519, "x25519mlkem768": tls.X25519MLKEM768, "secp256r1mlkem768": tls.SecP256r1MLKEM768, "secp384r1mlkem1024": tls.SecP384r1MLKEM1024, } func ParseCurveName(curveNames []string) []tls.CurveID { var curveIDs []tls.CurveID for _, name := range curveNames { if curveID, ok := curveMap[strings.ToLower(name)]; ok { curveIDs = append(curveIDs, curveID) } else { errors.LogWarning(context.Background(), "unsupported curve name: "+name) } } return curveIDs } var clientAuthMap = map[string]tls.ClientAuthType{ "noclientcert": tls.NoClientCert, "requestclientcert": tls.RequestClientCert, "requireanyclientcert": tls.RequireAnyClientCert, "verifyclientcertifgiven": tls.VerifyClientCertIfGiven, "requireandverifyclientcert": tls.RequireAndVerifyClientCert, } func ParseClientAuth(clientAuth string) tls.ClientAuthType { if clientAuth == "" { return tls.NoClientCert } if clientAuthType, ok := clientAuthMap[strings.ToLower(clientAuth)]; ok { return clientAuthType } errors.LogWarning(context.Background(), "unsupported clientAuth: "+clientAuth) return tls.NoClientCert } func IsFromMitm(str string) bool { return strings.ToLower(str) == "frommitm" } type verifyResult int const ( certNotFound verifyResult = iota foundLeaf foundCA ) func verifyChain(certs []*x509.Certificate, pinnedPeerCertSha256 [][]byte) (verifyResult, *x509.Certificate) { leafHash := GenerateCertHash(certs[0]) for _, c := range pinnedPeerCertSha256 { if hmac.Equal(leafHash, c) { return foundLeaf, nil } } certs = certs[1:] // skip leaf for _, cert := range certs { certHash := GenerateCertHash(cert) for _, c := range pinnedPeerCertSha256 { if hmac.Equal(certHash, c) { if cert.IsCA { return foundCA, cert } } } } return certNotFound, nil }