diff options
Diffstat (limited to 'vendor/golang.org/x/crypto/acme/autocert')
-rw-r--r-- | vendor/golang.org/x/crypto/acme/autocert/autocert.go | 47 | ||||
-rw-r--r-- | vendor/golang.org/x/crypto/acme/autocert/autocert_test.go | 126 |
2 files changed, 167 insertions, 6 deletions
diff --git a/vendor/golang.org/x/crypto/acme/autocert/autocert.go b/vendor/golang.org/x/crypto/acme/autocert/autocert.go index 98842b457..a478eff54 100644 --- a/vendor/golang.org/x/crypto/acme/autocert/autocert.go +++ b/vendor/golang.org/x/crypto/acme/autocert/autocert.go @@ -33,6 +33,12 @@ import ( "golang.org/x/crypto/acme" ) +// createCertRetryAfter is how much time to wait before removing a failed state +// entry due to an unsuccessful createCert call. +// This is a variable instead of a const for testing. +// TODO: Consider making it configurable or an exp backoff? +var createCertRetryAfter = time.Minute + // pseudoRand is safe for concurrent use. var pseudoRand *lockedMathRand @@ -170,6 +176,12 @@ func (m *Manager) GetCertificate(hello *tls.ClientHelloInfo) (*tls.Certificate, if name == "" { return nil, errors.New("acme/autocert: missing server name") } + if !strings.Contains(strings.Trim(name, "."), ".") { + return nil, errors.New("acme/autocert: server name component count invalid") + } + if strings.ContainsAny(name, `/\`) { + return nil, errors.New("acme/autocert: server name contains invalid character") + } ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute) defer cancel() @@ -244,6 +256,7 @@ func (m *Manager) cert(ctx context.Context, name string) (*tls.Certificate, erro } // cacheGet always returns a valid certificate, or an error otherwise. +// If a cached certficate exists but is not valid, ErrCacheMiss is returned. func (m *Manager) cacheGet(ctx context.Context, domain string) (*tls.Certificate, error) { if m.Cache == nil { return nil, ErrCacheMiss @@ -256,7 +269,7 @@ func (m *Manager) cacheGet(ctx context.Context, domain string) (*tls.Certificate // private priv, pub := pem.Decode(data) if priv == nil || !strings.Contains(priv.Type, "PRIVATE") { - return nil, errors.New("acme/autocert: no private key found in cache") + return nil, ErrCacheMiss } privKey, err := parsePrivateKey(priv.Bytes) if err != nil { @@ -274,13 +287,14 @@ func (m *Manager) cacheGet(ctx context.Context, domain string) (*tls.Certificate pubDER = append(pubDER, b.Bytes) } if len(pub) > 0 { - return nil, errors.New("acme/autocert: invalid public key") + // Leftover content not consumed by pem.Decode. Corrupt. Ignore. + return nil, ErrCacheMiss } // verify and create TLS cert leaf, err := validCert(domain, pubDER, privKey) if err != nil { - return nil, err + return nil, ErrCacheMiss } tlscert := &tls.Certificate{ Certificate: pubDER, @@ -361,6 +375,23 @@ func (m *Manager) createCert(ctx context.Context, domain string) (*tls.Certifica der, leaf, err := m.authorizedCert(ctx, state.key, domain) if err != nil { + // Remove the failed state after some time, + // making the manager call createCert again on the following TLS hello. + time.AfterFunc(createCertRetryAfter, func() { + defer testDidRemoveState(domain) + m.stateMu.Lock() + defer m.stateMu.Unlock() + // Verify the state hasn't changed and it's still invalid + // before deleting. + s, ok := m.state[domain] + if !ok { + return + } + if _, err := validCert(domain, s.cert, s.key); err == nil { + return + } + delete(m.state, domain) + }) return nil, err } state.cert = der @@ -409,7 +440,6 @@ func (m *Manager) certState(domain string) (*certState, error) { // authorizedCert starts domain ownership verification process and requests a new cert upon success. // The key argument is the certificate private key. func (m *Manager) authorizedCert(ctx context.Context, key crypto.Signer, domain string) (der [][]byte, leaf *x509.Certificate, err error) { - // TODO: make m.verify retry or retry m.verify calls here if err := m.verify(ctx, domain); err != nil { return nil, nil, err } @@ -780,5 +810,10 @@ func (r *lockedMathRand) int63n(max int64) int64 { return n } -// for easier testing -var timeNow = time.Now +// For easier testing. +var ( + timeNow = time.Now + + // Called when a state is removed. + testDidRemoveState = func(domain string) {} +) diff --git a/vendor/golang.org/x/crypto/acme/autocert/autocert_test.go b/vendor/golang.org/x/crypto/acme/autocert/autocert_test.go index 6df4cf3bf..0352e340d 100644 --- a/vendor/golang.org/x/crypto/acme/autocert/autocert_test.go +++ b/vendor/golang.org/x/crypto/acme/autocert/autocert_test.go @@ -178,6 +178,88 @@ func TestGetCertificate_nilPrompt(t *testing.T) { } } +func TestGetCertificate_expiredCache(t *testing.T) { + // Make an expired cert and cache it. + pk, 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(), + } + pub, err := x509.CreateCertificate(rand.Reader, tmpl, tmpl, &pk.PublicKey, pk) + if err != nil { + t.Fatal(err) + } + tlscert := &tls.Certificate{ + Certificate: [][]byte{pub}, + PrivateKey: pk, + } + + man := &Manager{Prompt: AcceptTOS, Cache: newMemCache()} + defer man.stopRenew() + if err := man.cachePut(context.Background(), "example.org", 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) +} + +func TestGetCertificate_failedAttempt(t *testing.T) { + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusBadRequest) + })) + defer ts.Close() + + const example = "example.org" + d := createCertRetryAfter + f := testDidRemoveState + defer func() { + createCertRetryAfter = d + testDidRemoveState = f + }() + createCertRetryAfter = 0 + done := make(chan struct{}) + testDidRemoveState = func(domain string) { + if domain != example { + t.Errorf("testDidRemoveState: domain = %q; want %q", domain, example) + } + close(done) + } + + key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + if err != nil { + t.Fatal(err) + } + man := &Manager{ + Prompt: AcceptTOS, + Client: &acme.Client{ + Key: key, + DirectoryURL: ts.URL, + }, + } + defer man.stopRenew() + hello := &tls.ClientHelloInfo{ServerName: example} + 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) + 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) + } + } +} + // 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()) { @@ -478,3 +560,47 @@ func TestValidCert(t *testing.T) { } } } + +type cacheGetFunc func(ctx context.Context, key string) ([]byte, error) + +func (f cacheGetFunc) Get(ctx context.Context, key string) ([]byte, error) { + return f(ctx, key) +} + +func (f cacheGetFunc) Put(ctx context.Context, key string, data []byte) error { + return fmt.Errorf("unsupported Put of %q = %q", key, data) +} + +func (f cacheGetFunc) Delete(ctx context.Context, key string) error { + return fmt.Errorf("unsupported Delete of %q", key) +} + +func TestManagerGetCertificateBogusSNI(t *testing.T) { + m := Manager{ + Prompt: AcceptTOS, + Cache: cacheGetFunc(func(ctx context.Context, key string) ([]byte, error) { + return nil, fmt.Errorf("cache.Get of %s", key) + }), + } + tests := []struct { + name string + wantErr string + }{ + {"foo.com", "cache.Get of foo.com"}, + {"foo.com.", "cache.Get of foo.com"}, + {`a\b.com`, "acme/autocert: server name contains invalid character"}, + {`a/b.com`, "acme/autocert: server name contains invalid character"}, + {"", "acme/autocert: missing server name"}, + {"foo", "acme/autocert: server name component count invalid"}, + {".foo", "acme/autocert: server name component count invalid"}, + {"foo.", "acme/autocert: server name component count invalid"}, + {"fo.o", "cache.Get of fo.o"}, + } + for _, tt := range tests { + _, err := m.GetCertificate(&tls.ClientHelloInfo{ServerName: tt.name}) + got := fmt.Sprint(err) + if got != tt.wantErr { + t.Errorf("GetCertificate(SNI = %q) = %q; want %q", tt.name, got, tt.wantErr) + } + } +} |