Permalink
Cannot retrieve contributors at this time
264 lines (222 sloc)
6.71 KB
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // +build !confonly | |
| package tls | |
| import ( | |
| "crypto/tls" | |
| "crypto/x509" | |
| "strings" | |
| "sync" | |
| "time" | |
| "v2ray.com/core/common/net" | |
| "v2ray.com/core/common/protocol/tls/cert" | |
| "v2ray.com/core/transport/internet" | |
| ) | |
| var ( | |
| globalSessionCache = tls.NewLRUClientSessionCache(128) | |
| ) | |
| const exp8357 = "experiment:8357" | |
| // ParseCertificate converts a cert.Certificate to Certificate. | |
| func ParseCertificate(c *cert.Certificate) *Certificate { | |
| certPEM, keyPEM := c.ToPEM() | |
| return &Certificate{ | |
| Certificate: certPEM, | |
| Key: keyPEM, | |
| } | |
| } | |
| func (c *Config) loadSelfCertPool() (*x509.CertPool, error) { | |
| root := x509.NewCertPool() | |
| for _, cert := range c.Certificate { | |
| if !root.AppendCertsFromPEM(cert.Certificate) { | |
| return nil, newError("failed to append cert").AtWarning() | |
| } | |
| } | |
| return root, nil | |
| } | |
| // BuildCertificates builds a list of TLS certificates from proto definition. | |
| func (c *Config) BuildCertificates() []tls.Certificate { | |
| certs := make([]tls.Certificate, 0, len(c.Certificate)) | |
| for _, entry := range c.Certificate { | |
| if entry.Usage != Certificate_ENCIPHERMENT { | |
| continue | |
| } | |
| keyPair, err := tls.X509KeyPair(entry.Certificate, entry.Key) | |
| if err != nil { | |
| newError("ignoring invalid X509 key pair").Base(err).AtWarning().WriteToLog() | |
| continue | |
| } | |
| certs = append(certs, keyPair) | |
| } | |
| return certs | |
| } | |
| 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)) | |
| } | |
| func issueCertificate(rawCA *Certificate, domain string) (*tls.Certificate, error) { | |
| parent, err := cert.ParseCertificate(rawCA.Certificate, rawCA.Key) | |
| if err != nil { | |
| return nil, newError("failed to parse raw certificate").Base(err) | |
| } | |
| newCert, err := cert.Generate(parent, cert.CommonName(domain), cert.DNSNames(domain)) | |
| if err != nil { | |
| return nil, newError("failed to generate new certificate for ", domain).Base(err) | |
| } | |
| newCertPEM, newKeyPEM := newCert.ToPEM() | |
| 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) | |
| } | |
| } | |
| 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) | |
| } | |
| } | |
| 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 { | |
| newError("failed to issue new certificate for ", domain).Base(err).WriteToLog() | |
| continue | |
| } | |
| access.Lock() | |
| c.Certificates = append(c.Certificates, *newCert) | |
| issuedCertificate = &c.Certificates[len(c.Certificates)-1] | |
| access.Unlock() | |
| break | |
| } | |
| } | |
| if issuedCertificate == nil { | |
| return nil, newError("failed to create a new certificate for ", domain) | |
| } | |
| access.Lock() | |
| c.BuildNameToCertificate() | |
| access.Unlock() | |
| return issuedCertificate, nil | |
| } | |
| } | |
| func (c *Config) IsExperiment8357() bool { | |
| return strings.HasPrefix(c.ServerName, exp8357) | |
| } | |
| func (c *Config) parseServerName() string { | |
| if c.IsExperiment8357() { | |
| return c.ServerName[len(exp8357):] | |
| } | |
| return c.ServerName | |
| } | |
| // GetTLSConfig converts this Config into tls.Config. | |
| func (c *Config) GetTLSConfig(opts ...Option) *tls.Config { | |
| root, err := c.getCertPool() | |
| if err != nil { | |
| newError("failed to load system root certificate").AtError().Base(err).WriteToLog() | |
| } | |
| config := &tls.Config{ | |
| ClientSessionCache: globalSessionCache, | |
| RootCAs: root, | |
| SessionTicketsDisabled: c.DisableSessionResumption, | |
| } | |
| if c == nil { | |
| return config | |
| } | |
| for _, opt := range opts { | |
| opt(config) | |
| } | |
| if !c.AllowInsecureCiphers && len(config.CipherSuites) == 0 { | |
| config.CipherSuites = []uint16{ | |
| tls.TLS_AES_128_GCM_SHA256, | |
| tls.TLS_AES_256_GCM_SHA384, | |
| tls.TLS_CHACHA20_POLY1305_SHA256, | |
| tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305, | |
| tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305, | |
| tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, | |
| tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, | |
| tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, | |
| tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, | |
| tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256, | |
| tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA, | |
| tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256, | |
| tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA, | |
| tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA, | |
| tls.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA, | |
| } | |
| } | |
| config.InsecureSkipVerify = c.AllowInsecure | |
| config.Certificates = c.BuildCertificates() | |
| config.BuildNameToCertificate() | |
| caCerts := c.getCustomCA() | |
| if len(caCerts) > 0 { | |
| config.GetCertificate = getGetCertificateFunc(config, caCerts) | |
| } | |
| if sn := c.parseServerName(); len(sn) > 0 { | |
| config.ServerName = sn | |
| } | |
| if len(c.NextProtocol) > 0 { | |
| config.NextProtos = c.NextProtocol | |
| } | |
| if len(config.NextProtos) == 0 { | |
| config.NextProtos = []string{"http/1.1"} | |
| } | |
| return config | |
| } | |
| // Option for building TLS config. | |
| type Option func(*tls.Config) | |
| // WithDestination sets the server name in TLS config. | |
| func WithDestination(dest net.Destination) Option { | |
| return func(config *tls.Config) { | |
| if dest.Address.Family().IsDomain() && config.ServerName == "" { | |
| config.ServerName = dest.Address.Domain() | |
| } | |
| } | |
| } | |
| // 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 | |
| } |