|
|
@@ -5,6 +5,7 @@
|
|
|
package autocert
|
|
|
|
|
|
import (
|
|
|
+ "bytes"
|
|
|
"context"
|
|
|
"crypto"
|
|
|
"crypto/ecdsa"
|
|
|
@@ -32,6 +33,12 @@ import (
|
|
|
"golang.org/x/crypto/acme"
|
|
|
)
|
|
|
|
|
|
+var (
|
|
|
+ exampleDomain = "example.org"
|
|
|
+ exampleCertKey = certKey{domain: exampleDomain}
|
|
|
+ exampleCertKeyRSA = certKey{domain: exampleDomain, isRSA: true}
|
|
|
+)
|
|
|
+
|
|
|
var discoTmpl = template.Must(template.New("disco").Parse(`{
|
|
|
"new-reg": "{{.}}/new-reg",
|
|
|
"new-authz": "{{.}}/new-authz",
|
|
|
@@ -65,6 +72,7 @@ var authzTmpl = template.Must(template.New("authz").Parse(`{
|
|
|
}`))
|
|
|
|
|
|
type memCache struct {
|
|
|
+ t *testing.T
|
|
|
mu sync.Mutex
|
|
|
keyData map[string][]byte
|
|
|
}
|
|
|
@@ -80,7 +88,26 @@ func (m *memCache) Get(ctx context.Context, key string) ([]byte, error) {
|
|
|
return v, nil
|
|
|
}
|
|
|
|
|
|
+// filenameSafe returns whether all characters in s are printable ASCII
|
|
|
+// and safe to use in a filename on most filesystems.
|
|
|
+func filenameSafe(s string) bool {
|
|
|
+ for _, c := range s {
|
|
|
+ if c < 0x20 || c > 0x7E {
|
|
|
+ return false
|
|
|
+ }
|
|
|
+ switch c {
|
|
|
+ case '\\', '/', ':', '*', '?', '"', '<', '>', '|':
|
|
|
+ return false
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return true
|
|
|
+}
|
|
|
+
|
|
|
func (m *memCache) Put(ctx context.Context, key string, data []byte) error {
|
|
|
+ if !filenameSafe(key) {
|
|
|
+ m.t.Errorf("invalid characters in cache key %q", key)
|
|
|
+ }
|
|
|
+
|
|
|
m.mu.Lock()
|
|
|
defer m.mu.Unlock()
|
|
|
|
|
|
@@ -96,12 +123,29 @@ func (m *memCache) Delete(ctx context.Context, key string) error {
|
|
|
return nil
|
|
|
}
|
|
|
|
|
|
-func newMemCache() *memCache {
|
|
|
+func newMemCache(t *testing.T) *memCache {
|
|
|
return &memCache{
|
|
|
+ t: t,
|
|
|
keyData: make(map[string][]byte),
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+func (m *memCache) numCerts() int {
|
|
|
+ m.mu.Lock()
|
|
|
+ defer m.mu.Unlock()
|
|
|
+
|
|
|
+ res := 0
|
|
|
+ for key := range m.keyData {
|
|
|
+ if strings.HasSuffix(key, "+token") ||
|
|
|
+ strings.HasSuffix(key, "+key") ||
|
|
|
+ strings.HasSuffix(key, "+http-01") {
|
|
|
+ continue
|
|
|
+ }
|
|
|
+ res++
|
|
|
+ }
|
|
|
+ return res
|
|
|
+}
|
|
|
+
|
|
|
func dummyCert(pub interface{}, san ...string) ([]byte, error) {
|
|
|
return dateDummyCert(pub, time.Now(), time.Now().Add(90*24*time.Hour), san...)
|
|
|
}
|
|
|
@@ -138,43 +182,55 @@ func decodePayload(v interface{}, r io.Reader) error {
|
|
|
return json.Unmarshal(payload, v)
|
|
|
}
|
|
|
|
|
|
+func clientHelloInfo(sni string, ecdsaSupport bool) *tls.ClientHelloInfo {
|
|
|
+ hello := &tls.ClientHelloInfo{
|
|
|
+ ServerName: sni,
|
|
|
+ CipherSuites: []uint16{tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305},
|
|
|
+ }
|
|
|
+ if ecdsaSupport {
|
|
|
+ hello.CipherSuites = append(hello.CipherSuites, tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305)
|
|
|
+ }
|
|
|
+ return hello
|
|
|
+}
|
|
|
+
|
|
|
func TestGetCertificate(t *testing.T) {
|
|
|
man := &Manager{Prompt: AcceptTOS}
|
|
|
defer man.stopRenew()
|
|
|
- hello := &tls.ClientHelloInfo{ServerName: "example.org"}
|
|
|
+ hello := clientHelloInfo("example.org", true)
|
|
|
testGetCertificate(t, man, "example.org", hello)
|
|
|
}
|
|
|
|
|
|
func TestGetCertificate_trailingDot(t *testing.T) {
|
|
|
man := &Manager{Prompt: AcceptTOS}
|
|
|
defer man.stopRenew()
|
|
|
- hello := &tls.ClientHelloInfo{ServerName: "example.org."}
|
|
|
+ hello := clientHelloInfo("example.org.", true)
|
|
|
testGetCertificate(t, man, "example.org", hello)
|
|
|
}
|
|
|
|
|
|
func TestGetCertificate_ForceRSA(t *testing.T) {
|
|
|
man := &Manager{
|
|
|
Prompt: AcceptTOS,
|
|
|
- Cache: newMemCache(),
|
|
|
+ Cache: newMemCache(t),
|
|
|
ForceRSA: true,
|
|
|
}
|
|
|
defer man.stopRenew()
|
|
|
- hello := &tls.ClientHelloInfo{ServerName: "example.org"}
|
|
|
- testGetCertificate(t, man, "example.org", hello)
|
|
|
+ hello := clientHelloInfo(exampleDomain, true)
|
|
|
+ testGetCertificate(t, man, exampleDomain, hello)
|
|
|
|
|
|
- cert, err := man.cacheGet(context.Background(), "example.org")
|
|
|
+ // ForceRSA was deprecated and is now ignored.
|
|
|
+ cert, err := man.cacheGet(context.Background(), exampleCertKey)
|
|
|
if err != nil {
|
|
|
t.Fatalf("man.cacheGet: %v", err)
|
|
|
}
|
|
|
- if _, ok := cert.PrivateKey.(*rsa.PrivateKey); !ok {
|
|
|
- t.Errorf("cert.PrivateKey is %T; want *rsa.PrivateKey", cert.PrivateKey)
|
|
|
+ if _, ok := cert.PrivateKey.(*ecdsa.PrivateKey); !ok {
|
|
|
+ t.Errorf("cert.PrivateKey is %T; want *ecdsa.PrivateKey", cert.PrivateKey)
|
|
|
}
|
|
|
}
|
|
|
|
|
|
func TestGetCertificate_nilPrompt(t *testing.T) {
|
|
|
man := &Manager{}
|
|
|
defer man.stopRenew()
|
|
|
- url, finish := startACMEServerStub(t, man, "example.org")
|
|
|
+ url, finish := startACMEServerStub(t, getCertificateFromManager(man, true), "example.org")
|
|
|
defer finish()
|
|
|
key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
|
|
if err != nil {
|
|
|
@@ -184,7 +240,7 @@ func TestGetCertificate_nilPrompt(t *testing.T) {
|
|
|
Key: key,
|
|
|
DirectoryURL: url,
|
|
|
}
|
|
|
- hello := &tls.ClientHelloInfo{ServerName: "example.org"}
|
|
|
+ hello := clientHelloInfo("example.org", true)
|
|
|
if _, err := man.GetCertificate(hello); err == nil {
|
|
|
t.Error("got certificate for example.org; wanted error")
|
|
|
}
|
|
|
@@ -198,7 +254,7 @@ func TestGetCertificate_expiredCache(t *testing.T) {
|
|
|
}
|
|
|
tmpl := &x509.Certificate{
|
|
|
SerialNumber: big.NewInt(1),
|
|
|
- Subject: pkix.Name{CommonName: "example.org"},
|
|
|
+ Subject: pkix.Name{CommonName: exampleDomain},
|
|
|
NotAfter: time.Now(),
|
|
|
}
|
|
|
pub, err := x509.CreateCertificate(rand.Reader, tmpl, tmpl, &pk.PublicKey, pk)
|
|
|
@@ -210,16 +266,16 @@ func TestGetCertificate_expiredCache(t *testing.T) {
|
|
|
PrivateKey: pk,
|
|
|
}
|
|
|
|
|
|
- man := &Manager{Prompt: AcceptTOS, Cache: newMemCache()}
|
|
|
+ man := &Manager{Prompt: AcceptTOS, Cache: newMemCache(t)}
|
|
|
defer man.stopRenew()
|
|
|
- if err := man.cachePut(context.Background(), "example.org", tlscert); err != nil {
|
|
|
+ if err := man.cachePut(context.Background(), exampleCertKey, tlscert); err != nil {
|
|
|
t.Fatalf("man.cachePut: %v", err)
|
|
|
}
|
|
|
|
|
|
// The expired cached cert should trigger a new cert issuance
|
|
|
// and return without an error.
|
|
|
- hello := &tls.ClientHelloInfo{ServerName: "example.org"}
|
|
|
- testGetCertificate(t, man, "example.org", hello)
|
|
|
+ hello := clientHelloInfo(exampleDomain, true)
|
|
|
+ testGetCertificate(t, man, exampleDomain, hello)
|
|
|
}
|
|
|
|
|
|
func TestGetCertificate_failedAttempt(t *testing.T) {
|
|
|
@@ -228,7 +284,6 @@ func TestGetCertificate_failedAttempt(t *testing.T) {
|
|
|
}))
|
|
|
defer ts.Close()
|
|
|
|
|
|
- const example = "example.org"
|
|
|
d := createCertRetryAfter
|
|
|
f := testDidRemoveState
|
|
|
defer func() {
|
|
|
@@ -237,9 +292,9 @@ func TestGetCertificate_failedAttempt(t *testing.T) {
|
|
|
}()
|
|
|
createCertRetryAfter = 0
|
|
|
done := make(chan struct{})
|
|
|
- testDidRemoveState = func(domain string) {
|
|
|
- if domain != example {
|
|
|
- t.Errorf("testDidRemoveState: domain = %q; want %q", domain, example)
|
|
|
+ testDidRemoveState = func(ck certKey) {
|
|
|
+ if ck != exampleCertKey {
|
|
|
+ t.Errorf("testDidRemoveState: domain = %v; want %v", ck, exampleCertKey)
|
|
|
}
|
|
|
close(done)
|
|
|
}
|
|
|
@@ -256,32 +311,174 @@ func TestGetCertificate_failedAttempt(t *testing.T) {
|
|
|
},
|
|
|
}
|
|
|
defer man.stopRenew()
|
|
|
- hello := &tls.ClientHelloInfo{ServerName: example}
|
|
|
+ hello := clientHelloInfo(exampleDomain, true)
|
|
|
if _, err := man.GetCertificate(hello); err == nil {
|
|
|
t.Error("GetCertificate: err is nil")
|
|
|
}
|
|
|
select {
|
|
|
case <-time.After(5 * time.Second):
|
|
|
- t.Errorf("took too long to remove the %q state", example)
|
|
|
+ t.Errorf("took too long to remove the %q state", exampleCertKey)
|
|
|
case <-done:
|
|
|
man.stateMu.Lock()
|
|
|
defer man.stateMu.Unlock()
|
|
|
- if v, exist := man.state[example]; exist {
|
|
|
- t.Errorf("state exists for %q: %+v", example, v)
|
|
|
+ if v, exist := man.state[exampleCertKey]; exist {
|
|
|
+ t.Errorf("state exists for %v: %+v", exampleCertKey, v)
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+// testGetCertificate_tokenCache tests the fallback of token certificate fetches
|
|
|
+// to cache when Manager.certTokens misses. ecdsaSupport refers to the CA when
|
|
|
+// verifying the certificate token.
|
|
|
+func testGetCertificate_tokenCache(t *testing.T, ecdsaSupport bool) {
|
|
|
+ man1 := &Manager{
|
|
|
+ Cache: newMemCache(t),
|
|
|
+ Prompt: AcceptTOS,
|
|
|
+ }
|
|
|
+ defer man1.stopRenew()
|
|
|
+ man2 := &Manager{
|
|
|
+ Cache: man1.Cache,
|
|
|
+ Prompt: AcceptTOS,
|
|
|
+ }
|
|
|
+ defer man2.stopRenew()
|
|
|
+
|
|
|
+ // Send the verification request to a different Manager from the one that
|
|
|
+ // initiated the authorization, when they share caches.
|
|
|
+ url, finish := startACMEServerStub(t, getCertificateFromManager(man2, ecdsaSupport), "example.org")
|
|
|
+ defer finish()
|
|
|
+ key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
|
|
+ if err != nil {
|
|
|
+ t.Fatal(err)
|
|
|
+ }
|
|
|
+ man1.Client = &acme.Client{
|
|
|
+ Key: key,
|
|
|
+ DirectoryURL: url,
|
|
|
+ }
|
|
|
+ hello := clientHelloInfo("example.org", true)
|
|
|
+ if _, err := man1.GetCertificate(hello); err != nil {
|
|
|
+ t.Error(err)
|
|
|
+ }
|
|
|
+ if _, err := man2.GetCertificate(hello); err != nil {
|
|
|
+ t.Error(err)
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+func TestGetCertificate_tokenCache(t *testing.T) {
|
|
|
+ t.Run("ecdsaSupport=true", func(t *testing.T) {
|
|
|
+ testGetCertificate_tokenCache(t, true)
|
|
|
+ })
|
|
|
+ t.Run("ecdsaSupport=false", func(t *testing.T) {
|
|
|
+ testGetCertificate_tokenCache(t, false)
|
|
|
+ })
|
|
|
+}
|
|
|
+
|
|
|
+func TestGetCertificate_ecdsaVsRSA(t *testing.T) {
|
|
|
+ cache := newMemCache(t)
|
|
|
+ man := &Manager{Prompt: AcceptTOS, Cache: cache}
|
|
|
+ defer man.stopRenew()
|
|
|
+ url, finish := startACMEServerStub(t, getCertificateFromManager(man, true), "example.org")
|
|
|
+ defer finish()
|
|
|
+ key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
|
|
+ if err != nil {
|
|
|
+ t.Fatal(err)
|
|
|
+ }
|
|
|
+ man.Client = &acme.Client{
|
|
|
+ Key: key,
|
|
|
+ DirectoryURL: url,
|
|
|
+ }
|
|
|
+
|
|
|
+ cert, err := man.GetCertificate(clientHelloInfo("example.org", true))
|
|
|
+ if err != nil {
|
|
|
+ t.Error(err)
|
|
|
+ }
|
|
|
+ if _, ok := cert.Leaf.PublicKey.(*ecdsa.PublicKey); !ok {
|
|
|
+ t.Error("an ECDSA client was served a non-ECDSA certificate")
|
|
|
+ }
|
|
|
+
|
|
|
+ cert, err = man.GetCertificate(clientHelloInfo("example.org", false))
|
|
|
+ if err != nil {
|
|
|
+ t.Error(err)
|
|
|
+ }
|
|
|
+ if _, ok := cert.Leaf.PublicKey.(*rsa.PublicKey); !ok {
|
|
|
+ t.Error("a RSA client was served a non-RSA certificate")
|
|
|
+ }
|
|
|
+
|
|
|
+ if _, err := man.GetCertificate(clientHelloInfo("example.org", true)); err != nil {
|
|
|
+ t.Error(err)
|
|
|
+ }
|
|
|
+ if _, err := man.GetCertificate(clientHelloInfo("example.org", false)); err != nil {
|
|
|
+ t.Error(err)
|
|
|
+ }
|
|
|
+ if numCerts := cache.numCerts(); numCerts != 2 {
|
|
|
+ t.Errorf("found %d certificates in cache; want %d", numCerts, 2)
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+func TestGetCertificate_wrongCacheKeyType(t *testing.T) {
|
|
|
+ cache := newMemCache(t)
|
|
|
+ man := &Manager{Prompt: AcceptTOS, Cache: cache}
|
|
|
+ defer man.stopRenew()
|
|
|
+ url, finish := startACMEServerStub(t, getCertificateFromManager(man, true), exampleDomain)
|
|
|
+ defer finish()
|
|
|
+ key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
|
|
+ if err != nil {
|
|
|
+ t.Fatal(err)
|
|
|
+ }
|
|
|
+ man.Client = &acme.Client{
|
|
|
+ Key: key,
|
|
|
+ DirectoryURL: url,
|
|
|
+ }
|
|
|
+
|
|
|
+ // Make an RSA cert and cache it without suffix.
|
|
|
+ pk, err := rsa.GenerateKey(rand.Reader, 512)
|
|
|
+ if err != nil {
|
|
|
+ t.Fatal(err)
|
|
|
+ }
|
|
|
+ tmpl := &x509.Certificate{
|
|
|
+ SerialNumber: big.NewInt(1),
|
|
|
+ Subject: pkix.Name{CommonName: exampleDomain},
|
|
|
+ NotAfter: time.Now().Add(90 * 24 * time.Hour),
|
|
|
+ }
|
|
|
+ pub, err := x509.CreateCertificate(rand.Reader, tmpl, tmpl, &pk.PublicKey, pk)
|
|
|
+ if err != nil {
|
|
|
+ t.Fatal(err)
|
|
|
+ }
|
|
|
+ rsaCert := &tls.Certificate{
|
|
|
+ Certificate: [][]byte{pub},
|
|
|
+ PrivateKey: pk,
|
|
|
+ }
|
|
|
+ if err := man.cachePut(context.Background(), exampleCertKey, rsaCert); err != nil {
|
|
|
+ t.Fatalf("man.cachePut: %v", err)
|
|
|
+ }
|
|
|
+
|
|
|
+ // The RSA cached cert should be silently ignored and replaced.
|
|
|
+ cert, err := man.GetCertificate(clientHelloInfo(exampleDomain, true))
|
|
|
+ if err != nil {
|
|
|
+ t.Error(err)
|
|
|
+ }
|
|
|
+ if _, ok := cert.Leaf.PublicKey.(*ecdsa.PublicKey); !ok {
|
|
|
+ t.Error("an ECDSA client was served a non-ECDSA certificate")
|
|
|
+ }
|
|
|
+ if numCerts := cache.numCerts(); numCerts != 1 {
|
|
|
+ t.Errorf("found %d certificates in cache; want %d", numCerts, 1)
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+func getCertificateFromManager(man *Manager, ecdsaSupport bool) func(string) error {
|
|
|
+ return func(sni string) error {
|
|
|
+ _, err := man.GetCertificate(clientHelloInfo(sni, ecdsaSupport))
|
|
|
+ return err
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
// startACMEServerStub runs an ACME server
|
|
|
// The domain argument is the expected domain name of a certificate request.
|
|
|
-func startACMEServerStub(t *testing.T, man *Manager, domain string) (url string, finish func()) {
|
|
|
+func startACMEServerStub(t *testing.T, getCertificate func(string) error, domain string) (url string, finish func()) {
|
|
|
// echo token-02 | shasum -a 256
|
|
|
// then divide result in 2 parts separated by dot
|
|
|
tokenCertName := "4e8eb87631187e9ff2153b56b13a4dec.13a35d002e485d60ff37354b32f665d9.token.acme.invalid"
|
|
|
verifyTokenCert := func() {
|
|
|
- hello := &tls.ClientHelloInfo{ServerName: tokenCertName}
|
|
|
- _, err := man.GetCertificate(hello)
|
|
|
- if err != nil {
|
|
|
+ if err := getCertificate(tokenCertName); err != nil {
|
|
|
t.Errorf("verifyTokenCert: GetCertificate(%q): %v", tokenCertName, err)
|
|
|
return
|
|
|
}
|
|
|
@@ -363,8 +560,7 @@ func startACMEServerStub(t *testing.T, man *Manager, domain string) (url string,
|
|
|
tick := time.NewTicker(100 * time.Millisecond)
|
|
|
defer tick.Stop()
|
|
|
for {
|
|
|
- hello := &tls.ClientHelloInfo{ServerName: tokenCertName}
|
|
|
- if _, err := man.GetCertificate(hello); err != nil {
|
|
|
+ if err := getCertificate(tokenCertName); err != nil {
|
|
|
return
|
|
|
}
|
|
|
select {
|
|
|
@@ -388,7 +584,7 @@ func startACMEServerStub(t *testing.T, man *Manager, domain string) (url string,
|
|
|
// tests man.GetCertificate flow using the provided hello argument.
|
|
|
// The domain argument is the expected domain name of a certificate request.
|
|
|
func testGetCertificate(t *testing.T, man *Manager, domain string, hello *tls.ClientHelloInfo) {
|
|
|
- url, finish := startACMEServerStub(t, man, domain)
|
|
|
+ url, finish := startACMEServerStub(t, getCertificateFromManager(man, true), domain)
|
|
|
defer finish()
|
|
|
|
|
|
// use EC key to run faster on 386
|
|
|
@@ -446,7 +642,7 @@ func TestVerifyHTTP01(t *testing.T) {
|
|
|
if w.Code != http.StatusOK {
|
|
|
t.Errorf("http token: w.Code = %d; want %d", w.Code, http.StatusOK)
|
|
|
}
|
|
|
- if v := string(w.Body.Bytes()); !strings.HasPrefix(v, "token-http-01.") {
|
|
|
+ if v := w.Body.String(); !strings.HasPrefix(v, "token-http-01.") {
|
|
|
t.Errorf("http token value = %q; want 'token-http-01.' prefix", v)
|
|
|
}
|
|
|
}
|
|
|
@@ -619,7 +815,7 @@ func TestRevokeFailedAuthz(t *testing.T) {
|
|
|
// The first 2 are tsl-sni-02 and tls-sni-01 challenges.
|
|
|
// The third time an authorization is created but no viable challenge is found.
|
|
|
// See revokedAuthz above for more explanation.
|
|
|
- if _, err := m.createCert(context.Background(), "example.org"); err == nil {
|
|
|
+ if _, err := m.createCert(context.Background(), exampleCertKey); err == nil {
|
|
|
t.Errorf("m.createCert returned nil error")
|
|
|
}
|
|
|
select {
|
|
|
@@ -677,7 +873,7 @@ func TestHTTPHandlerDefaultFallback(t *testing.T) {
|
|
|
}
|
|
|
|
|
|
func TestAccountKeyCache(t *testing.T) {
|
|
|
- m := Manager{Cache: newMemCache()}
|
|
|
+ m := Manager{Cache: newMemCache(t)}
|
|
|
ctx := context.Background()
|
|
|
k1, err := m.accountKey(ctx)
|
|
|
if err != nil {
|
|
|
@@ -693,36 +889,57 @@ func TestAccountKeyCache(t *testing.T) {
|
|
|
}
|
|
|
|
|
|
func TestCache(t *testing.T) {
|
|
|
- privKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
|
|
+ ecdsaKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
|
|
if err != nil {
|
|
|
t.Fatal(err)
|
|
|
}
|
|
|
- tmpl := &x509.Certificate{
|
|
|
- SerialNumber: big.NewInt(1),
|
|
|
- Subject: pkix.Name{CommonName: "example.org"},
|
|
|
- NotAfter: time.Now().Add(time.Hour),
|
|
|
+ cert, err := dummyCert(ecdsaKey.Public(), exampleDomain)
|
|
|
+ if err != nil {
|
|
|
+ t.Fatal(err)
|
|
|
}
|
|
|
- pub, err := x509.CreateCertificate(rand.Reader, tmpl, tmpl, &privKey.PublicKey, privKey)
|
|
|
+ ecdsaCert := &tls.Certificate{
|
|
|
+ Certificate: [][]byte{cert},
|
|
|
+ PrivateKey: ecdsaKey,
|
|
|
+ }
|
|
|
+
|
|
|
+ rsaKey, err := rsa.GenerateKey(rand.Reader, 512)
|
|
|
if err != nil {
|
|
|
t.Fatal(err)
|
|
|
}
|
|
|
- tlscert := &tls.Certificate{
|
|
|
- Certificate: [][]byte{pub},
|
|
|
- PrivateKey: privKey,
|
|
|
+ cert, err = dummyCert(rsaKey.Public(), exampleDomain)
|
|
|
+ if err != nil {
|
|
|
+ t.Fatal(err)
|
|
|
+ }
|
|
|
+ rsaCert := &tls.Certificate{
|
|
|
+ Certificate: [][]byte{cert},
|
|
|
+ PrivateKey: rsaKey,
|
|
|
}
|
|
|
|
|
|
- man := &Manager{Cache: newMemCache()}
|
|
|
+ man := &Manager{Cache: newMemCache(t)}
|
|
|
defer man.stopRenew()
|
|
|
ctx := context.Background()
|
|
|
- if err := man.cachePut(ctx, "example.org", tlscert); err != nil {
|
|
|
+
|
|
|
+ if err := man.cachePut(ctx, exampleCertKey, ecdsaCert); err != nil {
|
|
|
t.Fatalf("man.cachePut: %v", err)
|
|
|
}
|
|
|
- res, err := man.cacheGet(ctx, "example.org")
|
|
|
+ if err := man.cachePut(ctx, exampleCertKeyRSA, rsaCert); err != nil {
|
|
|
+ t.Fatalf("man.cachePut: %v", err)
|
|
|
+ }
|
|
|
+
|
|
|
+ res, err := man.cacheGet(ctx, exampleCertKey)
|
|
|
+ if err != nil {
|
|
|
+ t.Fatalf("man.cacheGet: %v", err)
|
|
|
+ }
|
|
|
+ if res == nil || !bytes.Equal(res.Certificate[0], ecdsaCert.Certificate[0]) {
|
|
|
+ t.Errorf("man.cacheGet = %+v; want %+v", res, ecdsaCert)
|
|
|
+ }
|
|
|
+
|
|
|
+ res, err = man.cacheGet(ctx, exampleCertKeyRSA)
|
|
|
if err != nil {
|
|
|
t.Fatalf("man.cacheGet: %v", err)
|
|
|
}
|
|
|
- if res == nil {
|
|
|
- t.Fatal("res is nil")
|
|
|
+ if res == nil || !bytes.Equal(res.Certificate[0], rsaCert.Certificate[0]) {
|
|
|
+ t.Errorf("man.cacheGet = %+v; want %+v", res, rsaCert)
|
|
|
}
|
|
|
}
|
|
|
|
|
|
@@ -786,26 +1003,28 @@ func TestValidCert(t *testing.T) {
|
|
|
}
|
|
|
|
|
|
tt := []struct {
|
|
|
- domain string
|
|
|
- key crypto.Signer
|
|
|
- cert [][]byte
|
|
|
- ok bool
|
|
|
+ ck certKey
|
|
|
+ key crypto.Signer
|
|
|
+ cert [][]byte
|
|
|
+ ok bool
|
|
|
}{
|
|
|
- {"example.org", key1, [][]byte{cert1}, true},
|
|
|
- {"example.org", key3, [][]byte{cert3}, true},
|
|
|
- {"example.org", key1, [][]byte{cert1, cert2, cert3}, true},
|
|
|
- {"example.org", key1, [][]byte{cert1, {1}}, false},
|
|
|
- {"example.org", key1, [][]byte{{1}}, false},
|
|
|
- {"example.org", key1, [][]byte{cert2}, false},
|
|
|
- {"example.org", key2, [][]byte{cert1}, false},
|
|
|
- {"example.org", key1, [][]byte{cert3}, false},
|
|
|
- {"example.org", key3, [][]byte{cert1}, false},
|
|
|
- {"example.net", key1, [][]byte{cert1}, false},
|
|
|
- {"example.org", key1, [][]byte{early}, false},
|
|
|
- {"example.org", key1, [][]byte{expired}, false},
|
|
|
+ {certKey{domain: "example.org"}, key1, [][]byte{cert1}, true},
|
|
|
+ {certKey{domain: "example.org", isRSA: true}, key3, [][]byte{cert3}, true},
|
|
|
+ {certKey{domain: "example.org"}, key1, [][]byte{cert1, cert2, cert3}, true},
|
|
|
+ {certKey{domain: "example.org"}, key1, [][]byte{cert1, {1}}, false},
|
|
|
+ {certKey{domain: "example.org"}, key1, [][]byte{{1}}, false},
|
|
|
+ {certKey{domain: "example.org"}, key1, [][]byte{cert2}, false},
|
|
|
+ {certKey{domain: "example.org"}, key2, [][]byte{cert1}, false},
|
|
|
+ {certKey{domain: "example.org"}, key1, [][]byte{cert3}, false},
|
|
|
+ {certKey{domain: "example.org"}, key3, [][]byte{cert1}, false},
|
|
|
+ {certKey{domain: "example.net"}, key1, [][]byte{cert1}, false},
|
|
|
+ {certKey{domain: "example.org"}, key1, [][]byte{early}, false},
|
|
|
+ {certKey{domain: "example.org"}, key1, [][]byte{expired}, false},
|
|
|
+ {certKey{domain: "example.org", isRSA: true}, key1, [][]byte{cert1}, false},
|
|
|
+ {certKey{domain: "example.org"}, key3, [][]byte{cert3}, false},
|
|
|
}
|
|
|
for i, test := range tt {
|
|
|
- leaf, err := validCert(test.domain, test.cert, test.key)
|
|
|
+ leaf, err := validCert(test.ck, test.cert, test.key)
|
|
|
if err != nil && test.ok {
|
|
|
t.Errorf("%d: err = %v", i, err)
|
|
|
}
|
|
|
@@ -854,7 +1073,7 @@ func TestManagerGetCertificateBogusSNI(t *testing.T) {
|
|
|
{"fo.o", "cache.Get of fo.o"},
|
|
|
}
|
|
|
for _, tt := range tests {
|
|
|
- _, err := m.GetCertificate(&tls.ClientHelloInfo{ServerName: tt.name})
|
|
|
+ _, err := m.GetCertificate(clientHelloInfo(tt.name, true))
|
|
|
got := fmt.Sprint(err)
|
|
|
if got != tt.wantErr {
|
|
|
t.Errorf("GetCertificate(SNI = %q) = %q; want %q", tt.name, got, tt.wantErr)
|
|
|
@@ -891,3 +1110,62 @@ func TestCertRequest(t *testing.T) {
|
|
|
t.Errorf("want %v in Extensions: %v", ext, r.Extensions)
|
|
|
}
|
|
|
}
|
|
|
+
|
|
|
+func TestSupportsECDSA(t *testing.T) {
|
|
|
+ tests := []struct {
|
|
|
+ CipherSuites []uint16
|
|
|
+ SignatureSchemes []tls.SignatureScheme
|
|
|
+ SupportedCurves []tls.CurveID
|
|
|
+ ecdsaOk bool
|
|
|
+ }{
|
|
|
+ {[]uint16{
|
|
|
+ tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
|
|
|
+ }, nil, nil, false},
|
|
|
+ {[]uint16{
|
|
|
+ tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
|
|
|
+ }, nil, nil, true},
|
|
|
+
|
|
|
+ // SignatureSchemes limits, not extends, CipherSuites
|
|
|
+ {[]uint16{
|
|
|
+ tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
|
|
|
+ }, []tls.SignatureScheme{
|
|
|
+ tls.PKCS1WithSHA256, tls.ECDSAWithP256AndSHA256,
|
|
|
+ }, nil, false},
|
|
|
+ {[]uint16{
|
|
|
+ tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
|
|
|
+ }, []tls.SignatureScheme{
|
|
|
+ tls.PKCS1WithSHA256,
|
|
|
+ }, nil, false},
|
|
|
+ {[]uint16{
|
|
|
+ tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
|
|
|
+ }, []tls.SignatureScheme{
|
|
|
+ tls.PKCS1WithSHA256, tls.ECDSAWithP256AndSHA256,
|
|
|
+ }, nil, true},
|
|
|
+
|
|
|
+ {[]uint16{
|
|
|
+ tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
|
|
|
+ }, []tls.SignatureScheme{
|
|
|
+ tls.PKCS1WithSHA256, tls.ECDSAWithP256AndSHA256,
|
|
|
+ }, []tls.CurveID{
|
|
|
+ tls.CurveP521,
|
|
|
+ }, false},
|
|
|
+ {[]uint16{
|
|
|
+ tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
|
|
|
+ }, []tls.SignatureScheme{
|
|
|
+ tls.PKCS1WithSHA256, tls.ECDSAWithP256AndSHA256,
|
|
|
+ }, []tls.CurveID{
|
|
|
+ tls.CurveP256,
|
|
|
+ tls.CurveP521,
|
|
|
+ }, true},
|
|
|
+ }
|
|
|
+ for i, tt := range tests {
|
|
|
+ result := supportsECDSA(&tls.ClientHelloInfo{
|
|
|
+ CipherSuites: tt.CipherSuites,
|
|
|
+ SignatureSchemes: tt.SignatureSchemes,
|
|
|
+ SupportedCurves: tt.SupportedCurves,
|
|
|
+ })
|
|
|
+ if result != tt.ecdsaOk {
|
|
|
+ t.Errorf("%d: supportsECDSA = %v; want %v", i, result, tt.ecdsaOk)
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|