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 }