|
|
@@ -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
|
|
|
|
|
|
@@ -363,6 +369,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
|
|
|
@@ -411,7 +434,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
|
|
|
}
|
|
|
@@ -782,5 +804,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) {}
|
|
|
+)
|