github.com/yaling888/clash@v1.53.0/common/cert/cert.go (about)

     1  package cert
     2  
     3  import (
     4  	"crypto"
     5  	"crypto/ecdsa"
     6  	"crypto/elliptic"
     7  	"crypto/rand"
     8  	"crypto/rsa"
     9  	"crypto/tls"
    10  	"crypto/x509"
    11  	"crypto/x509/pkix"
    12  	"encoding/pem"
    13  	"net"
    14  	"os"
    15  	"strings"
    16  	"time"
    17  )
    18  
    19  const monthDur = 30 * 24 * time.Hour
    20  
    21  type CertsStorage interface {
    22  	Get(key string) (*tls.Certificate, bool)
    23  
    24  	Set(key string, cert *tls.Certificate)
    25  }
    26  
    27  type Config struct {
    28  	rootCA       *x509.Certificate
    29  	rootKey      any
    30  	ca           *x509.Certificate
    31  	caPrivateKey *ecdsa.PrivateKey
    32  
    33  	roots         *x509.CertPool
    34  	intermediates *x509.CertPool
    35  
    36  	privateKey *ecdsa.PrivateKey
    37  
    38  	validity time.Duration
    39  
    40  	certsStorage CertsStorage
    41  }
    42  
    43  func (c *Config) GetRootCA() *x509.Certificate {
    44  	return c.rootCA
    45  }
    46  
    47  func (c *Config) SetValidity(validity time.Duration) {
    48  	c.validity = validity
    49  }
    50  
    51  func (c *Config) NewTLSConfigForHost(hostname string) *tls.Config {
    52  	tlsConfig := &tls.Config{
    53  		GetCertificate: func(clientHello *tls.ClientHelloInfo) (*tls.Certificate, error) {
    54  			host := clientHello.ServerName
    55  			if host == "" {
    56  				host = hostname
    57  			}
    58  
    59  			return c.GetOrCreateCert(host)
    60  		},
    61  		NextProtos: []string{"http/1.1"},
    62  	}
    63  
    64  	return tlsConfig
    65  }
    66  
    67  func (c *Config) GetOrCreateCert(hostname string, ips ...net.IP) (*tls.Certificate, error) {
    68  	var leaf *x509.Certificate
    69  	tlsCertificate, ok := c.certsStorage.Get(hostname)
    70  	if ok {
    71  		leaf = tlsCertificate.Leaf
    72  		if _, err := leaf.Verify(x509.VerifyOptions{
    73  			DNSName:       hostname,
    74  			Roots:         c.roots,
    75  			Intermediates: c.intermediates,
    76  		}); err == nil {
    77  			return tlsCertificate, nil
    78  		}
    79  	}
    80  
    81  	var (
    82  		key          = hostname
    83  		topHost      = hostname
    84  		wildcardHost = "*." + hostname
    85  		dnsNames     []string
    86  	)
    87  
    88  	if ip := net.ParseIP(hostname); ip != nil {
    89  		ips = append(ips, ip)
    90  	} else {
    91  		parts := strings.Split(hostname, ".")
    92  		l := len(parts)
    93  
    94  		if leaf != nil {
    95  			dnsNames = append(dnsNames, leaf.DNSNames...)
    96  		}
    97  
    98  		if l > 2 {
    99  			topIndex := l - 2
   100  			topHost = strings.Join(parts[topIndex:], ".")
   101  
   102  			for i := topIndex; i > 0; i-- {
   103  				wildcardHost = "*." + strings.Join(parts[i:], ".")
   104  
   105  				if i == topIndex && (len(dnsNames) == 0 || dnsNames[0] != topHost) {
   106  					dnsNames = append(dnsNames, topHost, wildcardHost)
   107  				} else if !hasDnsNames(dnsNames, wildcardHost) {
   108  					dnsNames = append(dnsNames, wildcardHost)
   109  				}
   110  			}
   111  		} else {
   112  			dnsNames = append(dnsNames, topHost, wildcardHost)
   113  		}
   114  
   115  		key = "+." + topHost
   116  	}
   117  
   118  	now := time.Now()
   119  	if now.After(c.ca.NotAfter) {
   120  		midCA, midPrivateKey, err := generateCert("Clash TLS Hybrid ECC SHA384 CA1", true, c.rootCA, c.rootKey)
   121  		if err != nil {
   122  			return nil, err
   123  		}
   124  		c.ca = midCA
   125  		c.caPrivateKey = midPrivateKey.(*ecdsa.PrivateKey)
   126  	}
   127  
   128  	notAfter := now.AddDate(0, int(c.validity/monthDur+1), 0)
   129  	if notAfter.After(c.ca.NotAfter) {
   130  		notAfter = c.ca.NotAfter
   131  	}
   132  
   133  	serial, _ := rand.Prime(rand.Reader, 120)
   134  	tmpl := &x509.Certificate{
   135  		Version:      3,
   136  		SerialNumber: serial,
   137  		Subject: pkix.Name{
   138  			CommonName:         topHost,
   139  			Organization:       []string{"Clash Proxy Services"},
   140  			OrganizationalUnit: []string{"Clash Plus"},
   141  		},
   142  		KeyUsage:              x509.KeyUsageDigitalSignature,
   143  		ExtKeyUsage:           []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth},
   144  		NotBefore:             clearClock(now.AddDate(0, -1, 0)),
   145  		NotAfter:              clearClock(notAfter),
   146  		DNSNames:              dnsNames,
   147  		IPAddresses:           ips,
   148  		BasicConstraintsValid: true,
   149  		IsCA:                  false,
   150  	}
   151  
   152  	raw, err := x509.CreateCertificate(rand.Reader, tmpl, c.ca, c.privateKey.Public(), c.caPrivateKey)
   153  	if err != nil {
   154  		return nil, err
   155  	}
   156  
   157  	x509c, err := x509.ParseCertificate(raw)
   158  	if err != nil {
   159  		return nil, err
   160  	}
   161  
   162  	tlsCertificate = &tls.Certificate{
   163  		Certificate: [][]byte{raw, c.ca.Raw, c.rootCA.Raw},
   164  		PrivateKey:  c.privateKey,
   165  		Leaf:        x509c,
   166  	}
   167  
   168  	c.certsStorage.Set(key, tlsCertificate)
   169  	return tlsCertificate, nil
   170  }
   171  
   172  // GenerateAndSave generate CA private key and CA certificate and dump them to file
   173  func GenerateAndSave(caPath string, caKeyPath string) error {
   174  	ca, privateKey, err := generateCert("Clash Root CA", true, nil, nil)
   175  	if err != nil {
   176  		return err
   177  	}
   178  
   179  	caOut, err := os.OpenFile(caPath, os.O_CREATE|os.O_WRONLY, 0o600)
   180  	if err != nil {
   181  		return err
   182  	}
   183  	defer func(caOut *os.File) {
   184  		_ = caOut.Close()
   185  	}(caOut)
   186  
   187  	if err = pem.Encode(caOut, &pem.Block{Type: "CERTIFICATE", Bytes: ca.Raw}); err != nil {
   188  		return err
   189  	}
   190  
   191  	caKeyOut, err := os.OpenFile(caKeyPath, os.O_CREATE|os.O_WRONLY, 0o600)
   192  	if err != nil {
   193  		return err
   194  	}
   195  	defer func(caKeyOut *os.File) {
   196  		_ = caKeyOut.Close()
   197  	}(caKeyOut)
   198  
   199  	err = pem.Encode(caKeyOut, &pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(privateKey.(*rsa.PrivateKey))})
   200  	if err != nil {
   201  		return err
   202  	}
   203  
   204  	return nil
   205  }
   206  
   207  func NewConfig(rootCA *x509.Certificate, rootPrivateKey any) (*Config, error) {
   208  	midCA, midPrivateKey, err := generateCert("Clash TLS Hybrid ECC SHA384 CA1", true, rootCA, rootPrivateKey)
   209  	if err != nil {
   210  		return nil, err
   211  	}
   212  
   213  	privateKey, err := ecdsa.GenerateKey(elliptic.P384(), rand.Reader)
   214  	if err != nil {
   215  		return nil, err
   216  	}
   217  
   218  	roots := x509.NewCertPool()
   219  	roots.AddCert(rootCA)
   220  
   221  	intermediates := x509.NewCertPool()
   222  	intermediates.AddCert(midCA)
   223  
   224  	return &Config{
   225  		rootCA:        rootCA,
   226  		rootKey:       rootPrivateKey,
   227  		ca:            midCA,
   228  		caPrivateKey:  midPrivateKey.(*ecdsa.PrivateKey),
   229  		privateKey:    privateKey,
   230  		validity:      time.Hour,
   231  		certsStorage:  NewDomainTrieCertsStorage(),
   232  		roots:         roots,
   233  		intermediates: intermediates,
   234  	}, nil
   235  }
   236  
   237  func generateCert(cn string, isCA bool, parentCA *x509.Certificate, parentKey any) (*x509.Certificate, any, error) {
   238  	var (
   239  		privateKey any
   240  		err        error
   241  	)
   242  	if isCA && parentCA == nil {
   243  		privateKey, err = rsa.GenerateKey(rand.Reader, 2048)
   244  	} else {
   245  		privateKey, err = ecdsa.GenerateKey(elliptic.P384(), rand.Reader)
   246  	}
   247  	if err != nil {
   248  		return nil, nil, err
   249  	}
   250  
   251  	publicKey := privateKey.(crypto.Signer).Public()
   252  
   253  	serial, _ := rand.Prime(rand.Reader, 120)
   254  	year, month, day := time.Now().Date()
   255  
   256  	tmpl := &x509.Certificate{
   257  		Version:      3,
   258  		SerialNumber: serial,
   259  		Subject: pkix.Name{
   260  			CommonName:         cn,
   261  			Country:            []string{"US"},
   262  			Organization:       []string{"Clash Trust Services"},
   263  			OrganizationalUnit: []string{"clashplus.eu.org"},
   264  		},
   265  		KeyUsage:              x509.KeyUsageCertSign | x509.KeyUsageCRLSign | x509.KeyUsageDigitalSignature,
   266  		BasicConstraintsValid: true,
   267  		IsCA:                  isCA,
   268  	}
   269  
   270  	if parentCA == nil {
   271  		tmpl.NotBefore = time.Date(year-2, month, day, 0, 0, 0, 0, time.UTC)
   272  		tmpl.NotAfter = time.Date(year+23, month, day, 0, 0, 0, 0, time.UTC)
   273  		parentCA = tmpl
   274  	} else {
   275  		now := time.Now()
   276  		var notAfter time.Time
   277  		if isCA {
   278  			tmpl.MaxPathLenZero = true
   279  			notAfter = now.AddDate(5, 6, 0)
   280  		} else {
   281  			notAfter = now.AddDate(1, 6, 0)
   282  		}
   283  		if notAfter.After(parentCA.NotAfter) {
   284  			notAfter = parentCA.NotAfter
   285  		}
   286  		tmpl.ExtKeyUsage = []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth}
   287  		tmpl.NotBefore = clearClock(now.AddDate(0, -6, 0))
   288  		tmpl.NotAfter = clearClock(notAfter)
   289  	}
   290  
   291  	if parentKey == nil {
   292  		parentKey = privateKey
   293  	}
   294  
   295  	raw, err := x509.CreateCertificate(rand.Reader, tmpl, parentCA, publicKey, parentKey)
   296  	if err != nil {
   297  		return nil, nil, err
   298  	}
   299  
   300  	cert, err := x509.ParseCertificate(raw)
   301  	if err != nil {
   302  		return nil, nil, err
   303  	}
   304  
   305  	return cert, privateKey, nil
   306  }
   307  
   308  func hasDnsNames(dnsNames []string, hostname string) bool {
   309  	for _, name := range dnsNames {
   310  		if name == hostname {
   311  			return true
   312  		}
   313  	}
   314  	return false
   315  }
   316  
   317  func clearClock(t time.Time) time.Time {
   318  	year, month, day := t.Date()
   319  	return time.Date(year, month, day, 0, 0, 0, 0, time.UTC)
   320  }