Chore: signature wildcard certificates

This commit is contained in:
yaling888
2022-04-25 05:02:24 +08:00
parent d763900b14
commit 62bc75af8a
8 changed files with 80 additions and 77 deletions

View File

@ -11,6 +11,7 @@ import (
"math/big"
"net"
"os"
"strings"
"sync/atomic"
"time"
)
@ -38,19 +39,6 @@ type CertsStorage interface {
Set(key string, cert *tls.Certificate)
}
type CertsCache struct {
certsCache map[string]*tls.Certificate
}
func (c *CertsCache) Get(key string) (*tls.Certificate, bool) {
v, ok := c.certsCache[key]
return v, ok
}
func (c *CertsCache) Set(key string, cert *tls.Certificate) {
c.certsCache[key] = cert
}
func NewAuthority(name, organization string, validity time.Duration) (*x509.Certificate, *rsa.PrivateKey, error) {
privateKey, err := rsa.GenerateKey(rand.Reader, 2048)
if err != nil {
@ -100,7 +88,7 @@ func NewAuthority(name, organization string, validity time.Duration) (*x509.Cert
return x509c, privateKey, nil
}
func NewConfig(ca *x509.Certificate, caPrivateKey *rsa.PrivateKey, storage CertsStorage) (*Config, error) {
func NewConfig(ca *x509.Certificate, caPrivateKey *rsa.PrivateKey) (*Config, error) {
roots := x509.NewCertPool()
roots.AddCert(ca)
@ -121,10 +109,6 @@ func NewConfig(ca *x509.Certificate, caPrivateKey *rsa.PrivateKey, storage Certs
}
keyID := h.Sum(nil)
if storage == nil {
storage = &CertsCache{certsCache: make(map[string]*tls.Certificate)}
}
return &Config{
ca: ca,
caPrivateKey: caPrivateKey,
@ -132,7 +116,7 @@ func NewConfig(ca *x509.Certificate, caPrivateKey *rsa.PrivateKey, storage Certs
keyID: keyID,
validity: time.Hour,
organization: "Clash",
certsStorage: storage,
certsStorage: NewDomainTrieCertsStorage(),
roots: roots,
}, nil
}
@ -168,14 +152,9 @@ func (c *Config) NewTLSConfigForHost(hostname string) *tls.Config {
}
func (c *Config) GetOrCreateCert(hostname string, ips ...net.IP) (*tls.Certificate, error) {
host, _, err := net.SplitHostPort(hostname)
if err == nil {
hostname = host
}
tlsCertificate, ok := c.certsStorage.Get(hostname)
if ok {
if _, err = tlsCertificate.Leaf.Verify(x509.VerifyOptions{
if _, err := tlsCertificate.Leaf.Verify(x509.VerifyOptions{
DNSName: hostname,
Roots: c.roots,
}); err == nil {
@ -183,12 +162,37 @@ func (c *Config) GetOrCreateCert(hostname string, ips ...net.IP) (*tls.Certifica
}
}
var (
key = hostname
topHost = hostname
dnsNames []string
)
if ip := net.ParseIP(hostname); ip != nil {
ips = append(ips, ip)
} else {
parts := strings.Split(topHost, ".")
l := len(parts)
if l >= 2 {
for i := l - 2; i >= 0; i-- {
topHost = strings.Join(parts[i:], ".")
dnsNames = append(dnsNames, topHost, "*."+topHost)
}
topHost = strings.Join(parts[l-2:], ".")
key = "+." + topHost
} else {
dnsNames = append(dnsNames, topHost)
}
}
serial := atomic.AddInt64(&currentSerialNumber, 1)
tmpl := &x509.Certificate{
SerialNumber: big.NewInt(serial),
Subject: pkix.Name{
CommonName: hostname,
CommonName: topHost,
Organization: []string{c.organization},
},
SubjectKeyId: c.keyID,
@ -199,12 +203,7 @@ func (c *Config) GetOrCreateCert(hostname string, ips ...net.IP) (*tls.Certifica
NotAfter: time.Now().Add(c.validity),
}
if ip := net.ParseIP(hostname); ip != nil {
ips = append(ips, ip)
} else {
tmpl.DNSNames = []string{hostname}
}
tmpl.DNSNames = dnsNames
tmpl.IPAddresses = ips
raw, err := x509.CreateCertificate(rand.Reader, tmpl, c.ca, c.privateKey.Public(), c.caPrivateKey)
@ -223,7 +222,7 @@ func (c *Config) GetOrCreateCert(hostname string, ips ...net.IP) (*tls.Certifica
Leaf: x509c,
}
c.certsStorage.Set(hostname, tlsCertificate)
c.certsStorage.Set(key, tlsCertificate)
return tlsCertificate, nil
}

View File

@ -18,19 +18,19 @@ func TestCert(t *testing.T) {
assert.NotNil(t, ca)
assert.NotNil(t, privateKey)
c, err := NewConfig(ca, privateKey, nil)
c, err := NewConfig(ca, privateKey)
assert.Nil(t, err)
c.SetValidity(20 * time.Hour)
c.SetOrganization("Test Organization")
conf := c.NewTLSConfigForHost("example.org")
conf := c.NewTLSConfigForHost("abc.example.org")
assert.Equal(t, []string{"http/1.1"}, conf.NextProtos)
assert.True(t, conf.InsecureSkipVerify)
// Test generating a certificate
clientHello := &tls.ClientHelloInfo{
ServerName: "example.org",
ServerName: "abc.example.org",
}
tlsCert, err := conf.GetCertificate(clientHello)
assert.Nil(t, err)
@ -40,13 +40,15 @@ func TestCert(t *testing.T) {
x509c := tlsCert.Leaf
assert.Equal(t, "example.org", x509c.Subject.CommonName)
assert.Nil(t, x509c.VerifyHostname("example.org"))
assert.Nil(t, x509c.VerifyHostname("abc.example.org"))
assert.Nil(t, x509c.VerifyHostname("efg.abc.example.org"))
assert.Equal(t, []string{"Test Organization"}, x509c.Subject.Organization)
assert.NotNil(t, x509c.SubjectKeyId)
assert.True(t, x509c.BasicConstraintsValid)
assert.True(t, x509c.KeyUsage&x509.KeyUsageKeyEncipherment == x509.KeyUsageKeyEncipherment)
assert.True(t, x509c.KeyUsage&x509.KeyUsageDigitalSignature == x509.KeyUsageDigitalSignature)
assert.Equal(t, []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, x509c.ExtKeyUsage)
assert.Equal(t, []string{"example.org"}, x509c.DNSNames)
assert.Equal(t, []string{"example.org", "*.example.org", "abc.example.org", "*.abc.example.org"}, x509c.DNSNames)
assert.True(t, x509c.NotBefore.Before(time.Now().Add(-2*time.Hour)))
assert.True(t, x509c.NotAfter.After(time.Now().Add(2*time.Hour)))
@ -56,11 +58,17 @@ func TestCert(t *testing.T) {
assert.True(t, tlsCert == tlsCert2)
// Check the certificate for an IP
tlsCertForIP, err := c.GetOrCreateCert("192.168.0.1:443")
assert.Nil(t, err)
tlsCertForIP, err := c.GetOrCreateCert("192.168.0.1")
x509c = tlsCertForIP.Leaf
assert.Nil(t, err)
assert.Equal(t, 1, len(x509c.IPAddresses))
assert.True(t, net.ParseIP("192.168.0.1").Equal(x509c.IPAddresses[0]))
tlsCertForIP2, err := c.GetOrCreateCert("192.168.0.1")
x509c = tlsCertForIP2.Leaf
assert.Nil(t, err)
assert.True(t, tlsCertForIP == tlsCertForIP2)
assert.Nil(t, x509c.VerifyHostname("192.168.0.1"))
}
func TestGenerateAndSave(t *testing.T) {

View File

@ -2,31 +2,31 @@ package cert
import (
"crypto/tls"
"time"
"github.com/Dreamacro/clash/common/cache"
"github.com/Dreamacro/clash/component/trie"
)
var TTL = time.Hour * 2
// AutoGCCertsStorage cache with the generated certificates, auto released after TTL
type AutoGCCertsStorage struct {
certsCache *cache.Cache[string, *tls.Certificate]
// DomainTrieCertsStorage cache wildcard certificates
type DomainTrieCertsStorage struct {
certsCache *trie.DomainTrie[*tls.Certificate]
}
// Get gets the certificate from the storage
func (c *AutoGCCertsStorage) Get(key string) (*tls.Certificate, bool) {
ca := c.certsCache.Get(key)
return ca, ca != nil
func (c *DomainTrieCertsStorage) Get(key string) (*tls.Certificate, bool) {
ca := c.certsCache.Search(key)
if ca == nil {
return nil, false
}
return ca.Data, true
}
// Set saves the certificate to the storage
func (c *AutoGCCertsStorage) Set(key string, cert *tls.Certificate) {
c.certsCache.Put(key, cert, TTL)
func (c *DomainTrieCertsStorage) Set(key string, cert *tls.Certificate) {
_ = c.certsCache.Insert(key, cert)
}
func NewAutoGCCertsStorage() *AutoGCCertsStorage {
return &AutoGCCertsStorage{
certsCache: cache.New[string, *tls.Certificate](TTL),
func NewDomainTrieCertsStorage() *DomainTrieCertsStorage {
return &DomainTrieCertsStorage{
certsCache: trie.New[*tls.Certificate](),
}
}