aboutsummaryrefslogtreecommitdiffhomepage
path: root/vendor/golang.org/x/crypto/acme
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/golang.org/x/crypto/acme')
-rw-r--r--vendor/golang.org/x/crypto/acme/acme.go417
-rw-r--r--vendor/golang.org/x/crypto/acme/acme_test.go305
-rw-r--r--vendor/golang.org/x/crypto/acme/autocert/autocert.go289
-rw-r--r--vendor/golang.org/x/crypto/acme/autocert/autocert_test.go561
-rw-r--r--vendor/golang.org/x/crypto/acme/autocert/cache.go6
-rw-r--r--vendor/golang.org/x/crypto/acme/autocert/renewal.go43
-rw-r--r--vendor/golang.org/x/crypto/acme/autocert/renewal_test.go170
-rw-r--r--vendor/golang.org/x/crypto/acme/http.go281
-rw-r--r--vendor/golang.org/x/crypto/acme/http_test.go209
-rw-r--r--vendor/golang.org/x/crypto/acme/types.go8
10 files changed, 1620 insertions, 669 deletions
diff --git a/vendor/golang.org/x/crypto/acme/acme.go b/vendor/golang.org/x/crypto/acme/acme.go
index fa9c4b3..8257ffb 100644
--- a/vendor/golang.org/x/crypto/acme/acme.go
+++ b/vendor/golang.org/x/crypto/acme/acme.go
@@ -14,7 +14,6 @@
package acme
import (
- "bytes"
"context"
"crypto"
"crypto/ecdsa"
@@ -23,6 +22,8 @@ import (
"crypto/sha256"
"crypto/tls"
"crypto/x509"
+ "crypto/x509/pkix"
+ "encoding/asn1"
"encoding/base64"
"encoding/hex"
"encoding/json"
@@ -33,7 +34,6 @@ import (
"io/ioutil"
"math/big"
"net/http"
- "strconv"
"strings"
"sync"
"time"
@@ -42,6 +42,9 @@ import (
// LetsEncryptURL is the Directory endpoint of Let's Encrypt CA.
const LetsEncryptURL = "https://acme-v01.api.letsencrypt.org/directory"
+// idPeACMEIdentifierV1 is the OID for the ACME extension for the TLS-ALPN challenge.
+var idPeACMEIdentifierV1 = asn1.ObjectIdentifier{1, 3, 6, 1, 5, 5, 7, 1, 30, 1}
+
const (
maxChainLen = 5 // max depth and breadth of a certificate chain
maxCertSize = 1 << 20 // max size of a certificate, in bytes
@@ -76,6 +79,22 @@ type Client struct {
// will have no effect.
DirectoryURL string
+ // RetryBackoff computes the duration after which the nth retry of a failed request
+ // should occur. The value of n for the first call on failure is 1.
+ // The values of r and resp are the request and response of the last failed attempt.
+ // If the returned value is negative or zero, no more retries are done and an error
+ // is returned to the caller of the original method.
+ //
+ // Requests which result in a 4xx client error are not retried,
+ // except for 400 Bad Request due to "bad nonce" errors and 429 Too Many Requests.
+ //
+ // If RetryBackoff is nil, a truncated exponential backoff algorithm
+ // with the ceiling of 10 seconds is used, where each subsequent retry n
+ // is done after either ("Retry-After" + jitter) or (2^n seconds + jitter),
+ // preferring the former if "Retry-After" header is found in the resp.
+ // The jitter is a random value up to 1 second.
+ RetryBackoff func(n int, r *http.Request, resp *http.Response) time.Duration
+
dirMu sync.Mutex // guards writes to dir
dir *Directory // cached result of Client's Discover method
@@ -99,15 +118,12 @@ func (c *Client) Discover(ctx context.Context) (Directory, error) {
if dirURL == "" {
dirURL = LetsEncryptURL
}
- res, err := c.get(ctx, dirURL)
+ res, err := c.get(ctx, dirURL, wantStatus(http.StatusOK))
if err != nil {
return Directory{}, err
}
defer res.Body.Close()
c.addNonce(res.Header)
- if res.StatusCode != http.StatusOK {
- return Directory{}, responseError(res)
- }
var v struct {
Reg string `json:"new-reg"`
@@ -166,14 +182,11 @@ func (c *Client) CreateCert(ctx context.Context, csr []byte, exp time.Duration,
req.NotAfter = now.Add(exp).Format(time.RFC3339)
}
- res, err := c.retryPostJWS(ctx, c.Key, c.dir.CertURL, req)
+ res, err := c.post(ctx, c.Key, c.dir.CertURL, req, wantStatus(http.StatusCreated))
if err != nil {
return nil, "", err
}
defer res.Body.Close()
- if res.StatusCode != http.StatusCreated {
- return nil, "", responseError(res)
- }
curl := res.Header.Get("Location") // cert permanent URL
if res.ContentLength == 0 {
@@ -196,26 +209,11 @@ func (c *Client) CreateCert(ctx context.Context, csr []byte, exp time.Duration,
// Callers are encouraged to parse the returned value to ensure the certificate is valid
// and has expected features.
func (c *Client) FetchCert(ctx context.Context, url string, bundle bool) ([][]byte, error) {
- for {
- res, err := c.get(ctx, url)
- if err != nil {
- return nil, err
- }
- defer res.Body.Close()
- if res.StatusCode == http.StatusOK {
- return c.responseCert(ctx, res, bundle)
- }
- if res.StatusCode > 299 {
- return nil, responseError(res)
- }
- d := retryAfter(res.Header.Get("Retry-After"), 3*time.Second)
- select {
- case <-time.After(d):
- // retry
- case <-ctx.Done():
- return nil, ctx.Err()
- }
+ res, err := c.get(ctx, url, wantStatus(http.StatusOK))
+ if err != nil {
+ return nil, err
}
+ return c.responseCert(ctx, res, bundle)
}
// RevokeCert revokes a previously issued certificate cert, provided in DER format.
@@ -241,14 +239,11 @@ func (c *Client) RevokeCert(ctx context.Context, key crypto.Signer, cert []byte,
if key == nil {
key = c.Key
}
- res, err := c.retryPostJWS(ctx, key, c.dir.RevokeURL, body)
+ res, err := c.post(ctx, key, c.dir.RevokeURL, body, wantStatus(http.StatusOK))
if err != nil {
return err
}
defer res.Body.Close()
- if res.StatusCode != http.StatusOK {
- return responseError(res)
- }
return nil
}
@@ -329,14 +324,11 @@ func (c *Client) Authorize(ctx context.Context, domain string) (*Authorization,
Resource: "new-authz",
Identifier: authzID{Type: "dns", Value: domain},
}
- res, err := c.retryPostJWS(ctx, c.Key, c.dir.AuthzURL, req)
+ res, err := c.post(ctx, c.Key, c.dir.AuthzURL, req, wantStatus(http.StatusCreated))
if err != nil {
return nil, err
}
defer res.Body.Close()
- if res.StatusCode != http.StatusCreated {
- return nil, responseError(res)
- }
var v wireAuthz
if err := json.NewDecoder(res.Body).Decode(&v); err != nil {
@@ -353,14 +345,11 @@ func (c *Client) Authorize(ctx context.Context, domain string) (*Authorization,
// If a caller needs to poll an authorization until its status is final,
// see the WaitAuthorization method.
func (c *Client) GetAuthorization(ctx context.Context, url string) (*Authorization, error) {
- res, err := c.get(ctx, url)
+ res, err := c.get(ctx, url, wantStatus(http.StatusOK, http.StatusAccepted))
if err != nil {
return nil, err
}
defer res.Body.Close()
- if res.StatusCode != http.StatusOK && res.StatusCode != http.StatusAccepted {
- return nil, responseError(res)
- }
var v wireAuthz
if err := json.NewDecoder(res.Body).Decode(&v); err != nil {
return nil, fmt.Errorf("acme: invalid response: %v", err)
@@ -387,56 +376,58 @@ func (c *Client) RevokeAuthorization(ctx context.Context, url string) error {
Status: "deactivated",
Delete: true,
}
- res, err := c.retryPostJWS(ctx, c.Key, url, req)
+ res, err := c.post(ctx, c.Key, url, req, wantStatus(http.StatusOK))
if err != nil {
return err
}
defer res.Body.Close()
- if res.StatusCode != http.StatusOK {
- return responseError(res)
- }
return nil
}
// WaitAuthorization polls an authorization at the given URL
// until it is in one of the final states, StatusValid or StatusInvalid,
-// or the context is done.
+// the ACME CA responded with a 4xx error code, or the context is done.
//
// It returns a non-nil Authorization only if its Status is StatusValid.
// In all other cases WaitAuthorization returns an error.
// If the Status is StatusInvalid, the returned error is of type *AuthorizationError.
func (c *Client) WaitAuthorization(ctx context.Context, url string) (*Authorization, error) {
- sleep := sleeper(ctx)
for {
- res, err := c.get(ctx, url)
+ res, err := c.get(ctx, url, wantStatus(http.StatusOK, http.StatusAccepted))
if err != nil {
return nil, err
}
- retry := res.Header.Get("Retry-After")
- if res.StatusCode != http.StatusOK && res.StatusCode != http.StatusAccepted {
- res.Body.Close()
- if err := sleep(retry, 1); err != nil {
- return nil, err
- }
- continue
- }
+
var raw wireAuthz
err = json.NewDecoder(res.Body).Decode(&raw)
res.Body.Close()
- if err != nil {
- if err := sleep(retry, 0); err != nil {
- return nil, err
- }
- continue
- }
- if raw.Status == StatusValid {
+ switch {
+ case err != nil:
+ // Skip and retry.
+ case raw.Status == StatusValid:
return raw.authorization(url), nil
- }
- if raw.Status == StatusInvalid {
+ case raw.Status == StatusInvalid:
return nil, raw.error(url)
}
- if err := sleep(retry, 0); err != nil {
- return nil, err
+
+ // Exponential backoff is implemented in c.get above.
+ // This is just to prevent continuously hitting the CA
+ // while waiting for a final authorization status.
+ d := retryAfter(res.Header.Get("Retry-After"))
+ if d == 0 {
+ // Given that the fastest challenges TLS-SNI and HTTP-01
+ // require a CA to make at least 1 network round trip
+ // and most likely persist a challenge state,
+ // this default delay seems reasonable.
+ d = time.Second
+ }
+ t := time.NewTimer(d)
+ select {
+ case <-ctx.Done():
+ t.Stop()
+ return nil, ctx.Err()
+ case <-t.C:
+ // Retry.
}
}
}
@@ -445,14 +436,11 @@ func (c *Client) WaitAuthorization(ctx context.Context, url string) (*Authorizat
//
// A client typically polls a challenge status using this method.
func (c *Client) GetChallenge(ctx context.Context, url string) (*Challenge, error) {
- res, err := c.get(ctx, url)
+ res, err := c.get(ctx, url, wantStatus(http.StatusOK, http.StatusAccepted))
if err != nil {
return nil, err
}
defer res.Body.Close()
- if res.StatusCode != http.StatusOK && res.StatusCode != http.StatusAccepted {
- return nil, responseError(res)
- }
v := wireChallenge{URI: url}
if err := json.NewDecoder(res.Body).Decode(&v); err != nil {
return nil, fmt.Errorf("acme: invalid response: %v", err)
@@ -479,16 +467,14 @@ func (c *Client) Accept(ctx context.Context, chal *Challenge) (*Challenge, error
Type: chal.Type,
Auth: auth,
}
- res, err := c.retryPostJWS(ctx, c.Key, chal.URI, req)
+ res, err := c.post(ctx, c.Key, chal.URI, req, wantStatus(
+ http.StatusOK, // according to the spec
+ http.StatusAccepted, // Let's Encrypt: see https://goo.gl/WsJ7VT (acme-divergences.md)
+ ))
if err != nil {
return nil, err
}
defer res.Body.Close()
- // Note: the protocol specifies 200 as the expected response code, but
- // letsencrypt seems to be returning 202.
- if res.StatusCode != http.StatusOK && res.StatusCode != http.StatusAccepted {
- return nil, responseError(res)
- }
var v wireChallenge
if err := json.NewDecoder(res.Body).Decode(&v); err != nil {
@@ -545,7 +531,7 @@ func (c *Client) HTTP01ChallengePath(token string) string {
// If no WithKey option is provided, a new ECDSA key is generated using P-256 curve.
//
// The returned certificate is valid for the next 24 hours and must be presented only when
-// the server name of the client hello matches exactly the returned name value.
+// the server name of the TLS ClientHello matches exactly the returned name value.
func (c *Client) TLSSNI01ChallengeCert(token string, opt ...CertOption) (cert tls.Certificate, name string, err error) {
ka, err := keyAuth(c.Key.Public(), token)
if err != nil {
@@ -572,7 +558,7 @@ func (c *Client) TLSSNI01ChallengeCert(token string, opt ...CertOption) (cert tl
// If no WithKey option is provided, a new ECDSA key is generated using P-256 curve.
//
// The returned certificate is valid for the next 24 hours and must be presented only when
-// the server name in the client hello matches exactly the returned name value.
+// the server name in the TLS ClientHello matches exactly the returned name value.
func (c *Client) TLSSNI02ChallengeCert(token string, opt ...CertOption) (cert tls.Certificate, name string, err error) {
b := sha256.Sum256([]byte(token))
h := hex.EncodeToString(b[:])
@@ -593,6 +579,52 @@ func (c *Client) TLSSNI02ChallengeCert(token string, opt ...CertOption) (cert tl
return cert, sanA, nil
}
+// TLSALPN01ChallengeCert creates a certificate for TLS-ALPN-01 challenge response.
+// Servers can present the certificate to validate the challenge and prove control
+// over a domain name. For more details on TLS-ALPN-01 see
+// https://tools.ietf.org/html/draft-shoemaker-acme-tls-alpn-00#section-3
+//
+// The token argument is a Challenge.Token value.
+// If a WithKey option is provided, its private part signs the returned cert,
+// and the public part is used to specify the signee.
+// If no WithKey option is provided, a new ECDSA key is generated using P-256 curve.
+//
+// The returned certificate is valid for the next 24 hours and must be presented only when
+// the server name in the TLS ClientHello matches the domain, and the special acme-tls/1 ALPN protocol
+// has been specified.
+func (c *Client) TLSALPN01ChallengeCert(token, domain string, opt ...CertOption) (cert tls.Certificate, err error) {
+ ka, err := keyAuth(c.Key.Public(), token)
+ if err != nil {
+ return tls.Certificate{}, err
+ }
+ shasum := sha256.Sum256([]byte(ka))
+ extValue, err := asn1.Marshal(shasum[:])
+ if err != nil {
+ return tls.Certificate{}, err
+ }
+ acmeExtension := pkix.Extension{
+ Id: idPeACMEIdentifierV1,
+ Critical: true,
+ Value: extValue,
+ }
+
+ tmpl := defaultTLSChallengeCertTemplate()
+
+ var newOpt []CertOption
+ for _, o := range opt {
+ switch o := o.(type) {
+ case *certOptTemplate:
+ t := *(*x509.Certificate)(o) // shallow copy is ok
+ tmpl = &t
+ default:
+ newOpt = append(newOpt, o)
+ }
+ }
+ tmpl.ExtraExtensions = append(tmpl.ExtraExtensions, acmeExtension)
+ newOpt = append(newOpt, WithTemplate(tmpl))
+ return tlsChallengeCert([]string{domain}, newOpt)
+}
+
// doReg sends all types of registration requests.
// The type of request is identified by typ argument, which is a "resource"
// in the ACME spec terms.
@@ -612,14 +644,14 @@ func (c *Client) doReg(ctx context.Context, url string, typ string, acct *Accoun
req.Contact = acct.Contact
req.Agreement = acct.AgreedTerms
}
- res, err := c.retryPostJWS(ctx, c.Key, url, req)
+ res, err := c.post(ctx, c.Key, url, req, wantStatus(
+ http.StatusOK, // updates and deletes
+ http.StatusCreated, // new account creation
+ ))
if err != nil {
return nil, err
}
defer res.Body.Close()
- if res.StatusCode < 200 || res.StatusCode > 299 {
- return nil, responseError(res)
- }
var v struct {
Contact []string
@@ -649,59 +681,6 @@ func (c *Client) doReg(ctx context.Context, url string, typ string, acct *Accoun
}, nil
}
-// retryPostJWS will retry calls to postJWS if there is a badNonce error,
-// clearing the stored nonces after each error.
-// If the response was 4XX-5XX, then responseError is called on the body,
-// the body is closed, and the error returned.
-func (c *Client) retryPostJWS(ctx context.Context, key crypto.Signer, url string, body interface{}) (*http.Response, error) {
- sleep := sleeper(ctx)
- for {
- res, err := c.postJWS(ctx, key, url, body)
- if err != nil {
- return nil, err
- }
- // handle errors 4XX-5XX with responseError
- if res.StatusCode >= 400 && res.StatusCode <= 599 {
- err := responseError(res)
- res.Body.Close()
- // according to spec badNonce is urn:ietf:params:acme:error:badNonce
- // however, acme servers in the wild return their version of the error
- // https://tools.ietf.org/html/draft-ietf-acme-acme-02#section-5.4
- if ae, ok := err.(*Error); ok && strings.HasSuffix(strings.ToLower(ae.ProblemType), ":badnonce") {
- // clear any nonces that we might've stored that might now be
- // considered bad
- c.clearNonces()
- retry := res.Header.Get("Retry-After")
- if err := sleep(retry, 1); err != nil {
- return nil, err
- }
- continue
- }
- return nil, err
- }
- return res, nil
- }
-}
-
-// postJWS signs the body with the given key and POSTs it to the provided url.
-// The body argument must be JSON-serializable.
-func (c *Client) postJWS(ctx context.Context, key crypto.Signer, url string, body interface{}) (*http.Response, error) {
- nonce, err := c.popNonce(ctx, url)
- if err != nil {
- return nil, err
- }
- b, err := jwsEncodeJSON(body, key, nonce)
- if err != nil {
- return nil, err
- }
- res, err := c.post(ctx, url, "application/jose+json", bytes.NewReader(b))
- if err != nil {
- return nil, err
- }
- c.addNonce(res.Header)
- return res, nil
-}
-
// popNonce returns a nonce value previously stored with c.addNonce
// or fetches a fresh one from the given URL.
func (c *Client) popNonce(ctx context.Context, url string) (string, error) {
@@ -742,58 +721,12 @@ func (c *Client) addNonce(h http.Header) {
c.nonces[v] = struct{}{}
}
-func (c *Client) httpClient() *http.Client {
- if c.HTTPClient != nil {
- return c.HTTPClient
- }
- return http.DefaultClient
-}
-
-func (c *Client) get(ctx context.Context, urlStr string) (*http.Response, error) {
- req, err := http.NewRequest("GET", urlStr, nil)
- if err != nil {
- return nil, err
- }
- return c.do(ctx, req)
-}
-
-func (c *Client) head(ctx context.Context, urlStr string) (*http.Response, error) {
- req, err := http.NewRequest("HEAD", urlStr, nil)
- if err != nil {
- return nil, err
- }
- return c.do(ctx, req)
-}
-
-func (c *Client) post(ctx context.Context, urlStr, contentType string, body io.Reader) (*http.Response, error) {
- req, err := http.NewRequest("POST", urlStr, body)
- if err != nil {
- return nil, err
- }
- req.Header.Set("Content-Type", contentType)
- return c.do(ctx, req)
-}
-
-func (c *Client) do(ctx context.Context, req *http.Request) (*http.Response, error) {
- res, err := c.httpClient().Do(req.WithContext(ctx))
+func (c *Client) fetchNonce(ctx context.Context, url string) (string, error) {
+ r, err := http.NewRequest("HEAD", url, nil)
if err != nil {
- select {
- case <-ctx.Done():
- // Prefer the unadorned context error.
- // (The acme package had tests assuming this, previously from ctxhttp's
- // behavior, predating net/http supporting contexts natively)
- // TODO(bradfitz): reconsider this in the future. But for now this
- // requires no test updates.
- return nil, ctx.Err()
- default:
- return nil, err
- }
+ return "", err
}
- return res, nil
-}
-
-func (c *Client) fetchNonce(ctx context.Context, url string) (string, error) {
- resp, err := c.head(ctx, url)
+ resp, err := c.doNoRetry(ctx, r)
if err != nil {
return "", err
}
@@ -845,24 +778,6 @@ func (c *Client) responseCert(ctx context.Context, res *http.Response, bundle bo
return cert, nil
}
-// responseError creates an error of Error type from resp.
-func responseError(resp *http.Response) error {
- // don't care if ReadAll returns an error:
- // json.Unmarshal will fail in that case anyway
- b, _ := ioutil.ReadAll(resp.Body)
- e := &wireError{Status: resp.StatusCode}
- if err := json.Unmarshal(b, e); err != nil {
- // this is not a regular error response:
- // populate detail with anything we received,
- // e.Status will already contain HTTP response code value
- e.Detail = string(b)
- if e.Detail == "" {
- e.Detail = resp.Status
- }
- }
- return e.error(resp.Header)
-}
-
// chainCert fetches CA certificate chain recursively by following "up" links.
// Each recursive call increments the depth by 1, resulting in an error
// if the recursion level reaches maxChainLen.
@@ -873,14 +788,11 @@ func (c *Client) chainCert(ctx context.Context, url string, depth int) ([][]byte
return nil, errors.New("acme: certificate chain is too deep")
}
- res, err := c.get(ctx, url)
+ res, err := c.get(ctx, url, wantStatus(http.StatusOK))
if err != nil {
return nil, err
}
defer res.Body.Close()
- if res.StatusCode != http.StatusOK {
- return nil, responseError(res)
- }
b, err := ioutil.ReadAll(io.LimitReader(res.Body, maxCertSize+1))
if err != nil {
return nil, err
@@ -925,65 +837,6 @@ func linkHeader(h http.Header, rel string) []string {
return links
}
-// sleeper returns a function that accepts the Retry-After HTTP header value
-// and an increment that's used with backoff to increasingly sleep on
-// consecutive calls until the context is done. If the Retry-After header
-// cannot be parsed, then backoff is used with a maximum sleep time of 10
-// seconds.
-func sleeper(ctx context.Context) func(ra string, inc int) error {
- var count int
- return func(ra string, inc int) error {
- count += inc
- d := backoff(count, 10*time.Second)
- d = retryAfter(ra, d)
- wakeup := time.NewTimer(d)
- defer wakeup.Stop()
- select {
- case <-ctx.Done():
- return ctx.Err()
- case <-wakeup.C:
- return nil
- }
- }
-}
-
-// retryAfter parses a Retry-After HTTP header value,
-// trying to convert v into an int (seconds) or use http.ParseTime otherwise.
-// It returns d if v cannot be parsed.
-func retryAfter(v string, d time.Duration) time.Duration {
- if i, err := strconv.Atoi(v); err == nil {
- return time.Duration(i) * time.Second
- }
- t, err := http.ParseTime(v)
- if err != nil {
- return d
- }
- return t.Sub(timeNow())
-}
-
-// backoff computes a duration after which an n+1 retry iteration should occur
-// using truncated exponential backoff algorithm.
-//
-// The n argument is always bounded between 0 and 30.
-// The max argument defines upper bound for the returned value.
-func backoff(n int, max time.Duration) time.Duration {
- if n < 0 {
- n = 0
- }
- if n > 30 {
- n = 30
- }
- var d time.Duration
- if x, err := rand.Int(rand.Reader, big.NewInt(1000)); err == nil {
- d = time.Duration(x.Int64()) * time.Millisecond
- }
- d += time.Duration(1<<uint(n)) * time.Second
- if d > max {
- return max
- }
- return d
-}
-
// keyAuth generates a key authorization string for a given token.
func keyAuth(pub crypto.PublicKey, token string) (string, error) {
th, err := JWKThumbprint(pub)
@@ -993,15 +846,25 @@ func keyAuth(pub crypto.PublicKey, token string) (string, error) {
return fmt.Sprintf("%s.%s", token, th), nil
}
+// defaultTLSChallengeCertTemplate is a template used to create challenge certs for TLS challenges.
+func defaultTLSChallengeCertTemplate() *x509.Certificate {
+ return &x509.Certificate{
+ SerialNumber: big.NewInt(1),
+ NotBefore: time.Now(),
+ NotAfter: time.Now().Add(24 * time.Hour),
+ BasicConstraintsValid: true,
+ KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
+ ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
+ }
+}
+
// tlsChallengeCert creates a temporary certificate for TLS-SNI challenges
// with the given SANs and auto-generated public/private key pair.
// The Subject Common Name is set to the first SAN to aid debugging.
// To create a cert with a custom key pair, specify WithKey option.
func tlsChallengeCert(san []string, opt []CertOption) (tls.Certificate, error) {
- var (
- key crypto.Signer
- tmpl *x509.Certificate
- )
+ var key crypto.Signer
+ tmpl := defaultTLSChallengeCertTemplate()
for _, o := range opt {
switch o := o.(type) {
case *certOptKey:
@@ -1010,7 +873,7 @@ func tlsChallengeCert(san []string, opt []CertOption) (tls.Certificate, error) {
}
key = o.key
case *certOptTemplate:
- var t = *(*x509.Certificate)(o) // shallow copy is ok
+ t := *(*x509.Certificate)(o) // shallow copy is ok
tmpl = &t
default:
// package's fault, if we let this happen:
@@ -1023,16 +886,6 @@ func tlsChallengeCert(san []string, opt []CertOption) (tls.Certificate, error) {
return tls.Certificate{}, err
}
}
- if tmpl == nil {
- tmpl = &x509.Certificate{
- SerialNumber: big.NewInt(1),
- NotBefore: time.Now(),
- NotAfter: time.Now().Add(24 * time.Hour),
- BasicConstraintsValid: true,
- KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
- ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
- }
- }
tmpl.DNSNames = san
if len(san) > 0 {
tmpl.Subject.CommonName = san[0]
diff --git a/vendor/golang.org/x/crypto/acme/acme_test.go b/vendor/golang.org/x/crypto/acme/acme_test.go
index 89f2efa..ef1fe47 100644
--- a/vendor/golang.org/x/crypto/acme/acme_test.go
+++ b/vendor/golang.org/x/crypto/acme/acme_test.go
@@ -13,9 +13,9 @@ import (
"crypto/x509"
"crypto/x509/pkix"
"encoding/base64"
+ "encoding/hex"
"encoding/json"
"fmt"
- "io/ioutil"
"math/big"
"net/http"
"net/http/httptest"
@@ -485,95 +485,98 @@ func TestGetAuthorization(t *testing.T) {
}
func TestWaitAuthorization(t *testing.T) {
- var count int
- ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
- count++
- w.Header().Set("Retry-After", "0")
- if count > 1 {
- fmt.Fprintf(w, `{"status":"valid"}`)
- return
+ t.Run("wait loop", func(t *testing.T) {
+ var count int
+ authz, err := runWaitAuthorization(context.Background(), t, func(w http.ResponseWriter, r *http.Request) {
+ count++
+ w.Header().Set("Retry-After", "0")
+ if count > 1 {
+ fmt.Fprintf(w, `{"status":"valid"}`)
+ return
+ }
+ fmt.Fprintf(w, `{"status":"pending"}`)
+ })
+ if err != nil {
+ t.Fatalf("non-nil error: %v", err)
}
- fmt.Fprintf(w, `{"status":"pending"}`)
- }))
- defer ts.Close()
-
- type res struct {
- authz *Authorization
- err error
- }
- done := make(chan res)
- defer close(done)
- go func() {
- var client Client
- a, err := client.WaitAuthorization(context.Background(), ts.URL)
- done <- res{a, err}
- }()
-
- select {
- case <-time.After(5 * time.Second):
- t.Fatal("WaitAuthz took too long to return")
- case res := <-done:
- if res.err != nil {
- t.Fatalf("res.err = %v", res.err)
+ if authz == nil {
+ t.Fatal("authz is nil")
+ }
+ })
+ t.Run("invalid status", func(t *testing.T) {
+ _, err := runWaitAuthorization(context.Background(), t, func(w http.ResponseWriter, r *http.Request) {
+ fmt.Fprintf(w, `{"status":"invalid"}`)
+ })
+ if _, ok := err.(*AuthorizationError); !ok {
+ t.Errorf("err is %v (%T); want non-nil *AuthorizationError", err, err)
+ }
+ })
+ t.Run("non-retriable error", func(t *testing.T) {
+ const code = http.StatusBadRequest
+ _, err := runWaitAuthorization(context.Background(), t, func(w http.ResponseWriter, r *http.Request) {
+ w.WriteHeader(code)
+ })
+ res, ok := err.(*Error)
+ if !ok {
+ t.Fatalf("err is %v (%T); want a non-nil *Error", err, err)
}
- if res.authz == nil {
- t.Fatal("res.authz is nil")
+ if res.StatusCode != code {
+ t.Errorf("res.StatusCode = %d; want %d", res.StatusCode, code)
}
+ })
+ for _, code := range []int{http.StatusTooManyRequests, http.StatusInternalServerError} {
+ t.Run(fmt.Sprintf("retriable %d error", code), func(t *testing.T) {
+ var count int
+ authz, err := runWaitAuthorization(context.Background(), t, func(w http.ResponseWriter, r *http.Request) {
+ count++
+ w.Header().Set("Retry-After", "0")
+ if count > 1 {
+ fmt.Fprintf(w, `{"status":"valid"}`)
+ return
+ }
+ w.WriteHeader(code)
+ })
+ if err != nil {
+ t.Fatalf("non-nil error: %v", err)
+ }
+ if authz == nil {
+ t.Fatal("authz is nil")
+ }
+ })
}
-}
-
-func TestWaitAuthorizationInvalid(t *testing.T) {
- ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
- fmt.Fprintf(w, `{"status":"invalid"}`)
- }))
- defer ts.Close()
-
- res := make(chan error)
- defer close(res)
- go func() {
- var client Client
- _, err := client.WaitAuthorization(context.Background(), ts.URL)
- res <- err
- }()
-
- select {
- case <-time.After(3 * time.Second):
- t.Fatal("WaitAuthz took too long to return")
- case err := <-res:
+ t.Run("context cancel", func(t *testing.T) {
+ ctx, cancel := context.WithTimeout(context.Background(), 200*time.Millisecond)
+ defer cancel()
+ _, err := runWaitAuthorization(ctx, t, func(w http.ResponseWriter, r *http.Request) {
+ w.Header().Set("Retry-After", "60")
+ fmt.Fprintf(w, `{"status":"pending"}`)
+ })
if err == nil {
t.Error("err is nil")
}
- if _, ok := err.(*AuthorizationError); !ok {
- t.Errorf("err is %T; want *AuthorizationError", err)
- }
- }
+ })
}
-
-func TestWaitAuthorizationCancel(t *testing.T) {
- ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
- w.Header().Set("Retry-After", "60")
- fmt.Fprintf(w, `{"status":"pending"}`)
- }))
+func runWaitAuthorization(ctx context.Context, t *testing.T, h http.HandlerFunc) (*Authorization, error) {
+ t.Helper()
+ ts := httptest.NewServer(h)
defer ts.Close()
-
- res := make(chan error)
- defer close(res)
+ type res struct {
+ authz *Authorization
+ err error
+ }
+ ch := make(chan res, 1)
go func() {
var client Client
- ctx, cancel := context.WithTimeout(context.Background(), 200*time.Millisecond)
- defer cancel()
- _, err := client.WaitAuthorization(ctx, ts.URL)
- res <- err
+ a, err := client.WaitAuthorization(ctx, ts.URL)
+ ch <- res{a, err}
}()
-
select {
- case <-time.After(time.Second):
- t.Fatal("WaitAuthz took too long to return")
- case err := <-res:
- if err == nil {
- t.Error("err is nil")
- }
+ case <-time.After(3 * time.Second):
+ t.Fatal("WaitAuthorization took too long to return")
+ case v := <-ch:
+ return v.authz, v.err
}
+ panic("runWaitAuthorization: out of select")
}
func TestRevokeAuthorization(t *testing.T) {
@@ -600,7 +603,7 @@ func TestRevokeAuthorization(t *testing.T) {
t.Errorf("req.Delete is false")
}
case "/2":
- w.WriteHeader(http.StatusInternalServerError)
+ w.WriteHeader(http.StatusBadRequest)
}
}))
defer ts.Close()
@@ -821,7 +824,7 @@ func TestFetchCertRetry(t *testing.T) {
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if count < 1 {
w.Header().Set("Retry-After", "0")
- w.WriteHeader(http.StatusAccepted)
+ w.WriteHeader(http.StatusTooManyRequests)
count++
return
}
@@ -1068,44 +1071,6 @@ func TestNonce_postJWS(t *testing.T) {
}
}
-func TestRetryPostJWS(t *testing.T) {
- var count int
- ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
- count++
- w.Header().Set("Replay-Nonce", fmt.Sprintf("nonce%d", count))
- if r.Method == "HEAD" {
- // We expect the client to do 2 head requests to fetch
- // nonces, one to start and another after getting badNonce
- return
- }
-
- head, err := decodeJWSHead(r)
- if err != nil {
- t.Errorf("decodeJWSHead: %v", err)
- } else if head.Nonce == "" {
- t.Error("head.Nonce is empty")
- } else if head.Nonce == "nonce1" {
- // return a badNonce error to force the call to retry
- w.WriteHeader(http.StatusBadRequest)
- w.Write([]byte(`{"type":"urn:ietf:params:acme:error:badNonce"}`))
- return
- }
- // Make client.Authorize happy; we're not testing its result.
- w.WriteHeader(http.StatusCreated)
- w.Write([]byte(`{"status":"valid"}`))
- }))
- defer ts.Close()
-
- client := Client{Key: testKey, dir: &Directory{AuthzURL: ts.URL}}
- // This call will fail with badNonce, causing a retry
- if _, err := client.Authorize(context.Background(), "example.com"); err != nil {
- t.Errorf("client.Authorize 1: %v", err)
- }
- if count != 4 {
- t.Errorf("total requests count: %d; want 4", count)
- }
-}
-
func TestLinkHeader(t *testing.T) {
h := http.Header{"Link": {
`<https://example.com/acme/new-authz>;rel="next"`,
@@ -1129,37 +1094,6 @@ func TestLinkHeader(t *testing.T) {
}
}
-func TestErrorResponse(t *testing.T) {
- s := `{
- "status": 400,
- "type": "urn:acme:error:xxx",
- "detail": "text"
- }`
- res := &http.Response{
- StatusCode: 400,
- Status: "400 Bad Request",
- Body: ioutil.NopCloser(strings.NewReader(s)),
- Header: http.Header{"X-Foo": {"bar"}},
- }
- err := responseError(res)
- v, ok := err.(*Error)
- if !ok {
- t.Fatalf("err = %+v (%T); want *Error type", err, err)
- }
- if v.StatusCode != 400 {
- t.Errorf("v.StatusCode = %v; want 400", v.StatusCode)
- }
- if v.ProblemType != "urn:acme:error:xxx" {
- t.Errorf("v.ProblemType = %q; want urn:acme:error:xxx", v.ProblemType)
- }
- if v.Detail != "text" {
- t.Errorf("v.Detail = %q; want text", v.Detail)
- }
- if !reflect.DeepEqual(v.Header, res.Header) {
- t.Errorf("v.Header = %+v; want %+v", v.Header, res.Header)
- }
-}
-
func TestTLSSNI01ChallengeCert(t *testing.T) {
const (
token = "evaGxfADs6pSRb2LAv9IZf17Dt3juxGJ-PCt92wr-oA"
@@ -1227,6 +1161,58 @@ func TestTLSSNI02ChallengeCert(t *testing.T) {
}
}
+func TestTLSALPN01ChallengeCert(t *testing.T) {
+ const (
+ token = "evaGxfADs6pSRb2LAv9IZf17Dt3juxGJ-PCt92wr-oA"
+ keyAuth = "evaGxfADs6pSRb2LAv9IZf17Dt3juxGJ-PCt92wr-oA." + testKeyECThumbprint
+ // echo -n <token.testKeyECThumbprint> | shasum -a 256
+ h = "0420dbbd5eefe7b4d06eb9d1d9f5acb4c7cda27d320e4b30332f0b6cb441734ad7b0"
+ domain = "example.com"
+ )
+
+ extValue, err := hex.DecodeString(h)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ client := &Client{Key: testKeyEC}
+ tlscert, err := client.TLSALPN01ChallengeCert(token, domain)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ if n := len(tlscert.Certificate); n != 1 {
+ t.Fatalf("len(tlscert.Certificate) = %d; want 1", n)
+ }
+ cert, err := x509.ParseCertificate(tlscert.Certificate[0])
+ if err != nil {
+ t.Fatal(err)
+ }
+ names := []string{domain}
+ if !reflect.DeepEqual(cert.DNSNames, names) {
+ t.Fatalf("cert.DNSNames = %v;\nwant %v", cert.DNSNames, names)
+ }
+ if cn := cert.Subject.CommonName; cn != domain {
+ t.Errorf("CommonName = %q; want %q", cn, domain)
+ }
+ acmeExts := []pkix.Extension{}
+ for _, ext := range cert.Extensions {
+ if idPeACMEIdentifierV1.Equal(ext.Id) {
+ acmeExts = append(acmeExts, ext)
+ }
+ }
+ if len(acmeExts) != 1 {
+ t.Errorf("acmeExts = %v; want exactly one", acmeExts)
+ }
+ if !acmeExts[0].Critical {
+ t.Errorf("acmeExt.Critical = %v; want true", acmeExts[0].Critical)
+ }
+ if bytes.Compare(acmeExts[0].Value, extValue) != 0 {
+ t.Errorf("acmeExt.Value = %v; want %v", acmeExts[0].Value, extValue)
+ }
+
+}
+
func TestTLSChallengeCertOpt(t *testing.T) {
key, err := rsa.GenerateKey(rand.Reader, 512)
if err != nil {
@@ -1325,28 +1311,3 @@ func TestDNS01ChallengeRecord(t *testing.T) {
t.Errorf("val = %q; want %q", val, value)
}
}
-
-func TestBackoff(t *testing.T) {
- tt := []struct{ min, max time.Duration }{
- {time.Second, 2 * time.Second},
- {2 * time.Second, 3 * time.Second},
- {4 * time.Second, 5 * time.Second},
- {8 * time.Second, 9 * time.Second},
- }
- for i, test := range tt {
- d := backoff(i, time.Minute)
- if d < test.min || test.max < d {
- t.Errorf("%d: d = %v; want between %v and %v", i, d, test.min, test.max)
- }
- }
-
- min, max := time.Second, 2*time.Second
- if d := backoff(-1, time.Minute); d < min || max < d {
- t.Errorf("d = %v; want between %v and %v", d, min, max)
- }
-
- bound := 10 * time.Second
- if d := backoff(100, bound); d != bound {
- t.Errorf("d = %v; want %v", d, bound)
- }
-}
diff --git a/vendor/golang.org/x/crypto/acme/autocert/autocert.go b/vendor/golang.org/x/crypto/acme/autocert/autocert.go
index 453e722..c8fa4e6 100644
--- a/vendor/golang.org/x/crypto/acme/autocert/autocert.go
+++ b/vendor/golang.org/x/crypto/acme/autocert/autocert.go
@@ -27,7 +27,6 @@ import (
"net"
"net/http"
"path"
- "strconv"
"strings"
"sync"
"time"
@@ -99,11 +98,11 @@ type Manager struct {
// To always accept the terms, the callers can use AcceptTOS.
Prompt func(tosURL string) bool
- // Cache optionally stores and retrieves previously-obtained certificates.
- // If nil, certs will only be cached for the lifetime of the Manager.
+ // Cache optionally stores and retrieves previously-obtained certificates
+ // and other state. If nil, certs will only be cached for the lifetime of
+ // the Manager. Multiple Managers can share the same Cache.
//
- // Manager passes the Cache certificates data encoded in PEM, with private/public
- // parts combined in a single Cache.Put call, private key first.
+ // Using a persistent Cache, such as DirCache, is strongly recommended.
Cache Cache
// HostPolicy controls which domains the Manager will attempt
@@ -128,8 +127,10 @@ type Manager struct {
// Client is used to perform low-level operations, such as account registration
// and requesting new certificates.
+ //
// If Client is nil, a zero-value acme.Client is used with acme.LetsEncryptURL
- // directory endpoint and a newly-generated ECDSA P-256 key.
+ // as directory endpoint. If the Client.Key is nil, a new ECDSA P-256 key is
+ // generated and, if Cache is not nil, stored in cache.
//
// Mutating the field after the first call of GetCertificate method will have no effect.
Client *acme.Client
@@ -141,22 +142,30 @@ type Manager struct {
// If the Client's account key is already registered, Email is not used.
Email string
- // ForceRSA makes the Manager generate certificates with 2048-bit RSA keys.
+ // ForceRSA used to make the Manager generate RSA certificates. It is now ignored.
//
- // If false, a default is used. Currently the default
- // is EC-based keys using the P-256 curve.
+ // Deprecated: the Manager will request the correct type of certificate based
+ // on what each client supports.
ForceRSA bool
+ // ExtraExtensions are used when generating a new CSR (Certificate Request),
+ // thus allowing customization of the resulting certificate.
+ // For instance, TLS Feature Extension (RFC 7633) can be used
+ // to prevent an OCSP downgrade attack.
+ //
+ // The field value is passed to crypto/x509.CreateCertificateRequest
+ // in the template's ExtraExtensions field as is.
+ ExtraExtensions []pkix.Extension
+
clientMu sync.Mutex
client *acme.Client // initialized by acmeClient method
stateMu sync.Mutex
- state map[string]*certState // keyed by domain name
+ state map[certKey]*certState
// renewal tracks the set of domains currently running renewal timers.
- // It is keyed by domain name.
renewalMu sync.Mutex
- renewal map[string]*domainRenewal
+ renewal map[certKey]*domainRenewal
// tokensMu guards the rest of the fields: tryHTTP01, certTokens and httpTokens.
tokensMu sync.RWMutex
@@ -175,6 +184,23 @@ type Manager struct {
certTokens map[string]*tls.Certificate
}
+// certKey is the key by which certificates are tracked in state, renewal and cache.
+type certKey struct {
+ domain string // without trailing dot
+ isRSA bool // RSA cert for legacy clients (as opposed to default ECDSA)
+ isToken bool // tls-sni challenge token cert; key type is undefined regardless of isRSA
+}
+
+func (c certKey) String() string {
+ if c.isToken {
+ return c.domain + "+token"
+ }
+ if c.isRSA {
+ return c.domain + "+rsa"
+ }
+ return c.domain
+}
+
// GetCertificate implements the tls.Config.GetCertificate hook.
// It provides a TLS certificate for hello.ServerName host, including answering
// *.acme.invalid (TLS-SNI) challenges. All other fields of hello are ignored.
@@ -195,7 +221,7 @@ func (m *Manager) GetCertificate(hello *tls.ClientHelloInfo) (*tls.Certificate,
if !strings.Contains(strings.Trim(name, "."), ".") {
return nil, errors.New("acme/autocert: server name component count invalid")
}
- if strings.ContainsAny(name, `/\`) {
+ if strings.ContainsAny(name, `+/\`) {
return nil, errors.New("acme/autocert: server name contains invalid character")
}
@@ -211,7 +237,7 @@ func (m *Manager) GetCertificate(hello *tls.ClientHelloInfo) (*tls.Certificate,
if cert := m.certTokens[name]; cert != nil {
return cert, nil
}
- if cert, err := m.cacheGet(ctx, name); err == nil {
+ if cert, err := m.cacheGet(ctx, certKey{domain: name, isToken: true}); err == nil {
return cert, nil
}
// TODO: cache error results?
@@ -219,8 +245,11 @@ func (m *Manager) GetCertificate(hello *tls.ClientHelloInfo) (*tls.Certificate,
}
// regular domain
- name = strings.TrimSuffix(name, ".") // golang.org/issue/18114
- cert, err := m.cert(ctx, name)
+ ck := certKey{
+ domain: strings.TrimSuffix(name, "."), // golang.org/issue/18114
+ isRSA: !supportsECDSA(hello),
+ }
+ cert, err := m.cert(ctx, ck)
if err == nil {
return cert, nil
}
@@ -232,14 +261,60 @@ func (m *Manager) GetCertificate(hello *tls.ClientHelloInfo) (*tls.Certificate,
if err := m.hostPolicy()(ctx, name); err != nil {
return nil, err
}
- cert, err = m.createCert(ctx, name)
+ cert, err = m.createCert(ctx, ck)
if err != nil {
return nil, err
}
- m.cachePut(ctx, name, cert)
+ m.cachePut(ctx, ck, cert)
return cert, nil
}
+func supportsECDSA(hello *tls.ClientHelloInfo) bool {
+ // The "signature_algorithms" extension, if present, limits the key exchange
+ // algorithms allowed by the cipher suites. See RFC 5246, section 7.4.1.4.1.
+ if hello.SignatureSchemes != nil {
+ ecdsaOK := false
+ schemeLoop:
+ for _, scheme := range hello.SignatureSchemes {
+ const tlsECDSAWithSHA1 tls.SignatureScheme = 0x0203 // constant added in Go 1.10
+ switch scheme {
+ case tlsECDSAWithSHA1, tls.ECDSAWithP256AndSHA256,
+ tls.ECDSAWithP384AndSHA384, tls.ECDSAWithP521AndSHA512:
+ ecdsaOK = true
+ break schemeLoop
+ }
+ }
+ if !ecdsaOK {
+ return false
+ }
+ }
+ if hello.SupportedCurves != nil {
+ ecdsaOK := false
+ for _, curve := range hello.SupportedCurves {
+ if curve == tls.CurveP256 {
+ ecdsaOK = true
+ break
+ }
+ }
+ if !ecdsaOK {
+ return false
+ }
+ }
+ for _, suite := range hello.CipherSuites {
+ switch suite {
+ case tls.TLS_ECDHE_ECDSA_WITH_RC4_128_SHA,
+ tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA,
+ tls.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA,
+ tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256,
+ tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
+ tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
+ tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305:
+ return true
+ }
+ }
+ return false
+}
+
// HTTPHandler configures the Manager to provision ACME "http-01" challenge responses.
// It returns an http.Handler that responds to the challenges and must be
// running on port 80. If it receives a request that is not an ACME challenge,
@@ -305,16 +380,16 @@ func stripPort(hostport string) string {
// cert returns an existing certificate either from m.state or cache.
// If a certificate is found in cache but not in m.state, the latter will be filled
// with the cached value.
-func (m *Manager) cert(ctx context.Context, name string) (*tls.Certificate, error) {
+func (m *Manager) cert(ctx context.Context, ck certKey) (*tls.Certificate, error) {
m.stateMu.Lock()
- if s, ok := m.state[name]; ok {
+ if s, ok := m.state[ck]; ok {
m.stateMu.Unlock()
s.RLock()
defer s.RUnlock()
return s.tlscert()
}
defer m.stateMu.Unlock()
- cert, err := m.cacheGet(ctx, name)
+ cert, err := m.cacheGet(ctx, ck)
if err != nil {
return nil, err
}
@@ -323,25 +398,25 @@ func (m *Manager) cert(ctx context.Context, name string) (*tls.Certificate, erro
return nil, errors.New("acme/autocert: private key cannot sign")
}
if m.state == nil {
- m.state = make(map[string]*certState)
+ m.state = make(map[certKey]*certState)
}
s := &certState{
key: signer,
cert: cert.Certificate,
leaf: cert.Leaf,
}
- m.state[name] = s
- go m.renew(name, s.key, s.leaf.NotAfter)
+ m.state[ck] = s
+ go m.renew(ck, s.key, s.leaf.NotAfter)
return cert, nil
}
// 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 a cached certificate exists but is not valid, ErrCacheMiss is returned.
+func (m *Manager) cacheGet(ctx context.Context, ck certKey) (*tls.Certificate, error) {
if m.Cache == nil {
return nil, ErrCacheMiss
}
- data, err := m.Cache.Get(ctx, domain)
+ data, err := m.Cache.Get(ctx, ck.String())
if err != nil {
return nil, err
}
@@ -372,7 +447,7 @@ func (m *Manager) cacheGet(ctx context.Context, domain string) (*tls.Certificate
}
// verify and create TLS cert
- leaf, err := validCert(domain, pubDER, privKey)
+ leaf, err := validCert(ck, pubDER, privKey)
if err != nil {
return nil, ErrCacheMiss
}
@@ -384,7 +459,7 @@ func (m *Manager) cacheGet(ctx context.Context, domain string) (*tls.Certificate
return tlscert, nil
}
-func (m *Manager) cachePut(ctx context.Context, domain string, tlscert *tls.Certificate) error {
+func (m *Manager) cachePut(ctx context.Context, ck certKey, tlscert *tls.Certificate) error {
if m.Cache == nil {
return nil
}
@@ -416,7 +491,7 @@ func (m *Manager) cachePut(ctx context.Context, domain string, tlscert *tls.Cert
}
}
- return m.Cache.Put(ctx, domain, buf.Bytes())
+ return m.Cache.Put(ctx, ck.String(), buf.Bytes())
}
func encodeECDSAKey(w io.Writer, key *ecdsa.PrivateKey) error {
@@ -433,9 +508,9 @@ func encodeECDSAKey(w io.Writer, key *ecdsa.PrivateKey) error {
//
// If the domain is already being verified, it waits for the existing verification to complete.
// Either way, createCert blocks for the duration of the whole process.
-func (m *Manager) createCert(ctx context.Context, domain string) (*tls.Certificate, error) {
+func (m *Manager) createCert(ctx context.Context, ck certKey) (*tls.Certificate, error) {
// TODO: maybe rewrite this whole piece using sync.Once
- state, err := m.certState(domain)
+ state, err := m.certState(ck)
if err != nil {
return nil, err
}
@@ -453,44 +528,44 @@ func (m *Manager) createCert(ctx context.Context, domain string) (*tls.Certifica
defer state.Unlock()
state.locked = false
- der, leaf, err := m.authorizedCert(ctx, state.key, domain)
+ der, leaf, err := m.authorizedCert(ctx, state.key, ck)
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)
+ defer testDidRemoveState(ck)
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]
+ s, ok := m.state[ck]
if !ok {
return
}
- if _, err := validCert(domain, s.cert, s.key); err == nil {
+ if _, err := validCert(ck, s.cert, s.key); err == nil {
return
}
- delete(m.state, domain)
+ delete(m.state, ck)
})
return nil, err
}
state.cert = der
state.leaf = leaf
- go m.renew(domain, state.key, state.leaf.NotAfter)
+ go m.renew(ck, state.key, state.leaf.NotAfter)
return state.tlscert()
}
// certState returns a new or existing certState.
// If a new certState is returned, state.exist is false and the state is locked.
// The returned error is non-nil only in the case where a new state could not be created.
-func (m *Manager) certState(domain string) (*certState, error) {
+func (m *Manager) certState(ck certKey) (*certState, error) {
m.stateMu.Lock()
defer m.stateMu.Unlock()
if m.state == nil {
- m.state = make(map[string]*certState)
+ m.state = make(map[certKey]*certState)
}
// existing state
- if state, ok := m.state[domain]; ok {
+ if state, ok := m.state[ck]; ok {
return state, nil
}
@@ -499,7 +574,7 @@ func (m *Manager) certState(domain string) (*certState, error) {
err error
key crypto.Signer
)
- if m.ForceRSA {
+ if ck.isRSA {
key, err = rsa.GenerateKey(rand.Reader, 2048)
} else {
key, err = ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
@@ -513,22 +588,22 @@ func (m *Manager) certState(domain string) (*certState, error) {
locked: true,
}
state.Lock() // will be unlocked by m.certState caller
- m.state[domain] = state
+ m.state[ck] = state
return state, nil
}
// authorizedCert starts the 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) {
+func (m *Manager) authorizedCert(ctx context.Context, key crypto.Signer, ck certKey) (der [][]byte, leaf *x509.Certificate, err error) {
client, err := m.acmeClient(ctx)
if err != nil {
return nil, nil, err
}
- if err := m.verify(ctx, client, domain); err != nil {
+ if err := m.verify(ctx, client, ck.domain); err != nil {
return nil, nil, err
}
- csr, err := certRequest(key, domain)
+ csr, err := certRequest(key, ck.domain, m.ExtraExtensions)
if err != nil {
return nil, nil, err
}
@@ -536,13 +611,25 @@ func (m *Manager) authorizedCert(ctx context.Context, key crypto.Signer, domain
if err != nil {
return nil, nil, err
}
- leaf, err = validCert(domain, der, key)
+ leaf, err = validCert(ck, der, key)
if err != nil {
return nil, nil, err
}
return der, leaf, nil
}
+// revokePendingAuthz revokes all authorizations idenfied by the elements of uri slice.
+// It ignores revocation errors.
+func (m *Manager) revokePendingAuthz(ctx context.Context, uri []string) {
+ client, err := m.acmeClient(ctx)
+ if err != nil {
+ return
+ }
+ for _, u := range uri {
+ client.RevokeAuthorization(ctx, u)
+ }
+}
+
// verify runs the identifier (domain) authorization flow
// using each applicable ACME challenge type.
func (m *Manager) verify(ctx context.Context, client *acme.Client, domain string) error {
@@ -555,6 +642,24 @@ func (m *Manager) verify(ctx context.Context, client *acme.Client, domain string
}
m.tokensMu.RUnlock()
+ // Keep track of pending authzs and revoke the ones that did not validate.
+ pendingAuthzs := make(map[string]bool)
+ defer func() {
+ var uri []string
+ for k, pending := range pendingAuthzs {
+ if pending {
+ uri = append(uri, k)
+ }
+ }
+ if len(uri) > 0 {
+ // Use "detached" background context.
+ // The revocations need not happen in the current verification flow.
+ go m.revokePendingAuthz(context.Background(), uri)
+ }
+ }()
+
+ // errs accumulates challenge failure errors, printed if all fail
+ errs := make(map[*acme.Challenge]error)
var nextTyp int // challengeType index of the next challenge type to try
for {
// Start domain authorization and get the challenge.
@@ -571,6 +676,8 @@ func (m *Manager) verify(ctx context.Context, client *acme.Client, domain string
return fmt.Errorf("acme/autocert: invalid authorization %q", authz.URI)
}
+ pendingAuthzs[authz.URI] = true
+
// Pick the next preferred challenge.
var chal *acme.Challenge
for chal == nil && nextTyp < len(challengeTypes) {
@@ -578,21 +685,30 @@ func (m *Manager) verify(ctx context.Context, client *acme.Client, domain string
nextTyp++
}
if chal == nil {
- return fmt.Errorf("acme/autocert: unable to authorize %q; tried %q", domain, challengeTypes)
+ errorMsg := fmt.Sprintf("acme/autocert: unable to authorize %q", domain)
+ for chal, err := range errs {
+ errorMsg += fmt.Sprintf("; challenge %q failed with error: %v", chal.Type, err)
+ }
+ return errors.New(errorMsg)
}
cleanup, err := m.fulfill(ctx, client, chal)
if err != nil {
+ errs[chal] = err
continue
}
defer cleanup()
if _, err := client.Accept(ctx, chal); err != nil {
+ errs[chal] = err
continue
}
// A challenge is fulfilled and accepted: wait for the CA to validate.
- if _, err := client.WaitAuthorization(ctx, authz.URI); err == nil {
- return nil
+ if _, err := client.WaitAuthorization(ctx, authz.URI); err != nil {
+ errs[chal] = err
+ continue
}
+ delete(pendingAuthzs, authz.URI)
+ return nil
}
}
@@ -635,8 +751,8 @@ func pickChallenge(typ string, chal []*acme.Challenge) *acme.Challenge {
return nil
}
-// putCertToken stores the cert under the named key in both m.certTokens map
-// and m.Cache.
+// putCertToken stores the token certificate with the specified name
+// in both m.certTokens map and m.Cache.
func (m *Manager) putCertToken(ctx context.Context, name string, cert *tls.Certificate) {
m.tokensMu.Lock()
defer m.tokensMu.Unlock()
@@ -644,17 +760,18 @@ func (m *Manager) putCertToken(ctx context.Context, name string, cert *tls.Certi
m.certTokens = make(map[string]*tls.Certificate)
}
m.certTokens[name] = cert
- m.cachePut(ctx, name, cert)
+ m.cachePut(ctx, certKey{domain: name, isToken: true}, cert)
}
-// deleteCertToken removes the token certificate for the specified domain name
+// deleteCertToken removes the token certificate with the specified name
// from both m.certTokens map and m.Cache.
func (m *Manager) deleteCertToken(name string) {
m.tokensMu.Lock()
defer m.tokensMu.Unlock()
delete(m.certTokens, name)
if m.Cache != nil {
- m.Cache.Delete(context.Background(), name)
+ ck := certKey{domain: name, isToken: true}
+ m.Cache.Delete(context.Background(), ck.String())
}
}
@@ -705,7 +822,7 @@ func (m *Manager) deleteHTTPToken(tokenPath string) {
// httpTokenCacheKey returns a key at which an http-01 token value may be stored
// in the Manager's optional Cache.
func httpTokenCacheKey(tokenPath string) string {
- return "http-01-" + path.Base(tokenPath)
+ return path.Base(tokenPath) + "+http-01"
}
// renew starts a cert renewal timer loop, one per domain.
@@ -716,18 +833,18 @@ func httpTokenCacheKey(tokenPath string) string {
//
// The key argument is a certificate private key.
// The exp argument is the cert expiration time (NotAfter).
-func (m *Manager) renew(domain string, key crypto.Signer, exp time.Time) {
+func (m *Manager) renew(ck certKey, key crypto.Signer, exp time.Time) {
m.renewalMu.Lock()
defer m.renewalMu.Unlock()
- if m.renewal[domain] != nil {
+ if m.renewal[ck] != nil {
// another goroutine is already on it
return
}
if m.renewal == nil {
- m.renewal = make(map[string]*domainRenewal)
+ m.renewal = make(map[certKey]*domainRenewal)
}
- dr := &domainRenewal{m: m, domain: domain, key: key}
- m.renewal[domain] = dr
+ dr := &domainRenewal{m: m, ck: ck, key: key}
+ m.renewal[ck] = dr
dr.start(exp)
}
@@ -743,7 +860,10 @@ func (m *Manager) stopRenew() {
}
func (m *Manager) accountKey(ctx context.Context) (crypto.Signer, error) {
- const keyName = "acme_account.key"
+ const keyName = "acme_account+key"
+
+ // Previous versions of autocert stored the value under a different key.
+ const legacyKeyName = "acme_account.key"
genKey := func() (*ecdsa.PrivateKey, error) {
return ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
@@ -755,6 +875,9 @@ func (m *Manager) accountKey(ctx context.Context) (crypto.Signer, error) {
data, err := m.Cache.Get(ctx, keyName)
if err == ErrCacheMiss {
+ data, err = m.Cache.Get(ctx, legacyKeyName)
+ }
+ if err == ErrCacheMiss {
key, err := genKey()
if err != nil {
return nil, err
@@ -850,12 +973,12 @@ func (s *certState) tlscert() (*tls.Certificate, error) {
}, nil
}
-// certRequest creates a certificate request for the given common name cn
-// and optional SANs.
-func certRequest(key crypto.Signer, cn string, san ...string) ([]byte, error) {
+// certRequest generates a CSR for the given common name cn and optional SANs.
+func certRequest(key crypto.Signer, cn string, ext []pkix.Extension, san ...string) ([]byte, error) {
req := &x509.CertificateRequest{
- Subject: pkix.Name{CommonName: cn},
- DNSNames: san,
+ Subject: pkix.Name{CommonName: cn},
+ DNSNames: san,
+ ExtraExtensions: ext,
}
return x509.CreateCertificateRequest(rand.Reader, req, key)
}
@@ -886,12 +1009,12 @@ func parsePrivateKey(der []byte) (crypto.Signer, error) {
return nil, errors.New("acme/autocert: failed to parse private key")
}
-// validCert parses a cert chain provided as der argument and verifies the leaf, der[0],
-// corresponds to the private key, as well as the domain match and expiration dates.
-// It doesn't do any revocation checking.
+// validCert parses a cert chain provided as der argument and verifies the leaf and der[0]
+// correspond to the private key, the domain and key type match, and expiration dates
+// are valid. It doesn't do any revocation checking.
//
// The returned value is the verified leaf cert.
-func validCert(domain string, der [][]byte, key crypto.Signer) (leaf *x509.Certificate, err error) {
+func validCert(ck certKey, der [][]byte, key crypto.Signer) (leaf *x509.Certificate, err error) {
// parse public part(s)
var n int
for _, b := range der {
@@ -903,7 +1026,7 @@ func validCert(domain string, der [][]byte, key crypto.Signer) (leaf *x509.Certi
n += copy(pub[n:], b)
}
x509Cert, err := x509.ParseCertificates(pub)
- if len(x509Cert) == 0 {
+ if err != nil || len(x509Cert) == 0 {
return nil, errors.New("acme/autocert: no public key found")
}
// verify the leaf is not expired and matches the domain name
@@ -915,10 +1038,10 @@ func validCert(domain string, der [][]byte, key crypto.Signer) (leaf *x509.Certi
if now.After(leaf.NotAfter) {
return nil, errors.New("acme/autocert: expired certificate")
}
- if err := leaf.VerifyHostname(domain); err != nil {
+ if err := leaf.VerifyHostname(ck.domain); err != nil {
return nil, err
}
- // ensure the leaf corresponds to the private key
+ // ensure the leaf corresponds to the private key and matches the certKey type
switch pub := leaf.PublicKey.(type) {
case *rsa.PublicKey:
prv, ok := key.(*rsa.PrivateKey)
@@ -928,6 +1051,9 @@ func validCert(domain string, der [][]byte, key crypto.Signer) (leaf *x509.Certi
if pub.N.Cmp(prv.N) != 0 {
return nil, errors.New("acme/autocert: private key does not match public key")
}
+ if !ck.isRSA && !ck.isToken {
+ return nil, errors.New("acme/autocert: key type does not match expected value")
+ }
case *ecdsa.PublicKey:
prv, ok := key.(*ecdsa.PrivateKey)
if !ok {
@@ -936,22 +1062,15 @@ func validCert(domain string, der [][]byte, key crypto.Signer) (leaf *x509.Certi
if pub.X.Cmp(prv.X) != 0 || pub.Y.Cmp(prv.Y) != 0 {
return nil, errors.New("acme/autocert: private key does not match public key")
}
+ if ck.isRSA && !ck.isToken {
+ return nil, errors.New("acme/autocert: key type does not match expected value")
+ }
default:
return nil, errors.New("acme/autocert: unknown public key algorithm")
}
return leaf, nil
}
-func retryAfter(v string) time.Duration {
- if i, err := strconv.Atoi(v); err == nil {
- return time.Duration(i) * time.Second
- }
- if t, err := http.ParseTime(v); err == nil {
- return t.Sub(timeNow())
- }
- return time.Second
-}
-
type lockedMathRand struct {
sync.Mutex
rnd *mathrand.Rand
@@ -969,5 +1088,5 @@ var (
timeNow = time.Now
// Called when a state is removed.
- testDidRemoveState = func(domain string) {}
+ testDidRemoveState = func(certKey) {}
)
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 2da1912..48ccd35 100644
--- a/vendor/golang.org/x/crypto/acme/autocert/autocert_test.go
+++ b/vendor/golang.org/x/crypto/acme/autocert/autocert_test.go
@@ -5,6 +5,7 @@
package autocert
import (
+ "bytes"
"context"
"crypto"
"crypto/ecdsa"
@@ -14,6 +15,7 @@ import (
"crypto/tls"
"crypto/x509"
"crypto/x509/pkix"
+ "encoding/asn1"
"encoding/base64"
"encoding/json"
"fmt"
@@ -31,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",
@@ -64,6 +72,7 @@ var authzTmpl = template.Must(template.New("authz").Parse(`{
}`))
type memCache struct {
+ t *testing.T
mu sync.Mutex
keyData map[string][]byte
}
@@ -79,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()
@@ -95,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...)
}
@@ -137,53 +182,58 @@ 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 {
- t.Fatal(err)
- }
- man.Client = &acme.Client{
- Key: key,
- DirectoryURL: url,
- }
- hello := &tls.ClientHelloInfo{ServerName: "example.org"}
+ man.Client = &acme.Client{DirectoryURL: url}
+ hello := clientHelloInfo("example.org", true)
if _, err := man.GetCertificate(hello); err == nil {
t.Error("got certificate for example.org; wanted error")
}
@@ -197,7 +247,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)
@@ -209,16 +259,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) {
@@ -227,7 +277,6 @@ func TestGetCertificate_failedAttempt(t *testing.T) {
}))
defer ts.Close()
- const example = "example.org"
d := createCertRetryAfter
f := testDidRemoveState
defer func() {
@@ -236,51 +285,167 @@ 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)
}
- 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}
+ 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()
+ man1.Client = &acme.Client{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()
+ man.Client = &acme.Client{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()
+ man.Client = &acme.Client{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
}
@@ -362,8 +527,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 {
@@ -387,21 +551,13 @@ 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
- key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
- if err != nil {
- t.Fatal(err)
- }
- man.Client = &acme.Client{
- Key: key,
- DirectoryURL: url,
- }
+ man.Client = &acme.Client{DirectoryURL: url}
// simulate tls.Config.GetCertificate
var tlscert *tls.Certificate
+ var err error
done := make(chan struct{})
go func() {
tlscert, err = man.GetCertificate(hello)
@@ -445,7 +601,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)
}
}
@@ -505,18 +661,18 @@ func TestVerifyHTTP01(t *testing.T) {
}))
defer ca.Close()
- key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
- if err != nil {
- t.Fatal(err)
- }
m := &Manager{
Client: &acme.Client{
- Key: key,
DirectoryURL: ca.URL,
},
}
http01 = m.HTTPHandler(nil)
- if err := m.verify(context.Background(), m.Client, "example.org"); err != nil {
+ ctx := context.Background()
+ client, err := m.acmeClient(ctx)
+ if err != nil {
+ t.Fatalf("m.acmeClient: %v", err)
+ }
+ if err := m.verify(ctx, client, "example.org"); err != nil {
t.Errorf("m.verify: %v", err)
}
// Only tls-sni-01, tls-sni-02 and http-01 must be accepted
@@ -529,6 +685,111 @@ func TestVerifyHTTP01(t *testing.T) {
}
}
+func TestRevokeFailedAuthz(t *testing.T) {
+ // Prefill authorization URIs expected to be revoked.
+ // The challenges are selected in a specific order,
+ // each tried within a newly created authorization.
+ // This means each authorization URI corresponds to a different challenge type.
+ revokedAuthz := map[string]bool{
+ "/authz/0": false, // tls-sni-02
+ "/authz/1": false, // tls-sni-01
+ "/authz/2": false, // no viable challenge, but authz is created
+ }
+
+ var authzCount int // num. of created authorizations
+ var revokeCount int // num. of revoked authorizations
+ done := make(chan struct{}) // closed when revokeCount is 3
+
+ // ACME CA server stub, only the needed bits.
+ // TODO: Merge this with startACMEServerStub, making it a configurable CA for testing.
+ var ca *httptest.Server
+ ca = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ w.Header().Set("Replay-Nonce", "nonce")
+ if r.Method == "HEAD" {
+ // a nonce request
+ return
+ }
+
+ switch r.URL.Path {
+ // Discovery.
+ case "/":
+ if err := discoTmpl.Execute(w, ca.URL); err != nil {
+ t.Errorf("discoTmpl: %v", err)
+ }
+ // Client key registration.
+ case "/new-reg":
+ w.Write([]byte("{}"))
+ // New domain authorization.
+ case "/new-authz":
+ w.Header().Set("Location", fmt.Sprintf("%s/authz/%d", ca.URL, authzCount))
+ w.WriteHeader(http.StatusCreated)
+ if err := authzTmpl.Execute(w, ca.URL); err != nil {
+ t.Errorf("authzTmpl: %v", err)
+ }
+ authzCount++
+ // tls-sni-02 challenge "accept" request.
+ case "/challenge/2":
+ // Refuse.
+ http.Error(w, "won't accept tls-sni-02 challenge", http.StatusBadRequest)
+ // tls-sni-01 challenge "accept" request.
+ case "/challenge/1":
+ // Accept but the authorization will be "expired".
+ w.Write([]byte("{}"))
+ // Authorization requests.
+ case "/authz/0", "/authz/1", "/authz/2":
+ // Revocation requests.
+ if r.Method == "POST" {
+ var req struct{ Status string }
+ if err := decodePayload(&req, r.Body); err != nil {
+ t.Errorf("%s: decodePayload: %v", r.URL, err)
+ }
+ switch req.Status {
+ case "deactivated":
+ revokedAuthz[r.URL.Path] = true
+ revokeCount++
+ if revokeCount >= 3 {
+ // Last authorization is revoked.
+ defer close(done)
+ }
+ default:
+ t.Errorf("%s: req.Status = %q; want 'deactivated'", r.URL, req.Status)
+ }
+ w.Write([]byte(`{"status": "invalid"}`))
+ return
+ }
+ // Authorization status requests.
+ // Simulate abandoned authorization, deleted by the CA.
+ w.WriteHeader(http.StatusNotFound)
+ default:
+ http.NotFound(w, r)
+ t.Errorf("unrecognized r.URL.Path: %s", r.URL.Path)
+ }
+ }))
+ defer ca.Close()
+
+ m := &Manager{
+ Client: &acme.Client{DirectoryURL: ca.URL},
+ }
+ // Should fail and revoke 3 authorizations.
+ // 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(), exampleCertKey); err == nil {
+ t.Errorf("m.createCert returned nil error")
+ }
+ select {
+ case <-time.After(3 * time.Second):
+ t.Error("revocations took too long")
+ case <-done:
+ // revokeCount is at least 3.
+ }
+ for uri, ok := range revokedAuthz {
+ if !ok {
+ t.Errorf("%q authorization was not revoked", uri)
+ }
+ }
+}
+
func TestHTTPHandlerDefaultFallback(t *testing.T) {
tt := []struct {
method, url string
@@ -571,7 +832,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 {
@@ -587,36 +848,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)
+ }
+ ecdsaCert := &tls.Certificate{
+ Certificate: [][]byte{cert},
+ PrivateKey: ecdsaKey,
}
- pub, err := x509.CreateCertificate(rand.Reader, tmpl, tmpl, &privKey.PublicKey, privKey)
+
+ 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 {
- t.Fatal("res is nil")
+ 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 || !bytes.Equal(res.Certificate[0], rsaCert.Certificate[0]) {
+ t.Errorf("man.cacheGet = %+v; want %+v", res, rsaCert)
}
}
@@ -680,26 +962,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)
}
@@ -748,10 +1032,99 @@ 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)
}
}
}
+
+func TestCertRequest(t *testing.T) {
+ key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
+ if err != nil {
+ t.Fatal(err)
+ }
+ // An extension from RFC7633. Any will do.
+ ext := pkix.Extension{
+ Id: asn1.ObjectIdentifier{1, 3, 6, 1, 5, 5, 7, 1},
+ Value: []byte("dummy"),
+ }
+ b, err := certRequest(key, "example.org", []pkix.Extension{ext}, "san.example.org")
+ if err != nil {
+ t.Fatalf("certRequest: %v", err)
+ }
+ r, err := x509.ParseCertificateRequest(b)
+ if err != nil {
+ t.Fatalf("ParseCertificateRequest: %v", err)
+ }
+ var found bool
+ for _, v := range r.Extensions {
+ if v.Id.Equal(ext.Id) {
+ found = true
+ break
+ }
+ }
+ if !found {
+ 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)
+ }
+ }
+}
diff --git a/vendor/golang.org/x/crypto/acme/autocert/cache.go b/vendor/golang.org/x/crypto/acme/autocert/cache.go
index 61a5fd2..aa9aa84 100644
--- a/vendor/golang.org/x/crypto/acme/autocert/cache.go
+++ b/vendor/golang.org/x/crypto/acme/autocert/cache.go
@@ -16,10 +16,10 @@ import (
var ErrCacheMiss = errors.New("acme/autocert: certificate cache miss")
// Cache is used by Manager to store and retrieve previously obtained certificates
-// as opaque data.
+// and other account data as opaque blobs.
//
-// The key argument of the methods refers to a domain name but need not be an FQDN.
-// Cache implementations should not rely on the key naming pattern.
+// Cache implementations should not rely on the key naming pattern. Keys can
+// include any printable ASCII characters, except the following: \/:*?"<>|
type Cache interface {
// Get returns a certificate data for the specified key.
// If there's no such key, Get returns ErrCacheMiss.
diff --git a/vendor/golang.org/x/crypto/acme/autocert/renewal.go b/vendor/golang.org/x/crypto/acme/autocert/renewal.go
index 6c5da2b..ef3e44e 100644
--- a/vendor/golang.org/x/crypto/acme/autocert/renewal.go
+++ b/vendor/golang.org/x/crypto/acme/autocert/renewal.go
@@ -17,9 +17,9 @@ const renewJitter = time.Hour
// domainRenewal tracks the state used by the periodic timers
// renewing a single domain's cert.
type domainRenewal struct {
- m *Manager
- domain string
- key crypto.Signer
+ m *Manager
+ ck certKey
+ key crypto.Signer
timerMu sync.Mutex
timer *time.Timer
@@ -71,25 +71,43 @@ func (dr *domainRenewal) renew() {
testDidRenewLoop(next, err)
}
+// updateState locks and replaces the relevant Manager.state item with the given
+// state. It additionally updates dr.key with the given state's key.
+func (dr *domainRenewal) updateState(state *certState) {
+ dr.m.stateMu.Lock()
+ defer dr.m.stateMu.Unlock()
+ dr.key = state.key
+ dr.m.state[dr.ck] = state
+}
+
// do is similar to Manager.createCert but it doesn't lock a Manager.state item.
// Instead, it requests a new certificate independently and, upon success,
// replaces dr.m.state item with a new one and updates cache for the given domain.
//
-// It may return immediately if the expiration date of the currently cached cert
-// is far enough in the future.
+// It may lock and update the Manager.state if the expiration date of the currently
+// cached cert is far enough in the future.
//
// The returned value is a time interval after which the renewal should occur again.
func (dr *domainRenewal) do(ctx context.Context) (time.Duration, error) {
// a race is likely unavoidable in a distributed environment
// but we try nonetheless
- if tlscert, err := dr.m.cacheGet(ctx, dr.domain); err == nil {
+ if tlscert, err := dr.m.cacheGet(ctx, dr.ck); err == nil {
next := dr.next(tlscert.Leaf.NotAfter)
if next > dr.m.renewBefore()+renewJitter {
- return next, nil
+ signer, ok := tlscert.PrivateKey.(crypto.Signer)
+ if ok {
+ state := &certState{
+ key: signer,
+ cert: tlscert.Certificate,
+ leaf: tlscert.Leaf,
+ }
+ dr.updateState(state)
+ return next, nil
+ }
}
}
- der, leaf, err := dr.m.authorizedCert(ctx, dr.key, dr.domain)
+ der, leaf, err := dr.m.authorizedCert(ctx, dr.key, dr.ck)
if err != nil {
return 0, err
}
@@ -102,11 +120,10 @@ func (dr *domainRenewal) do(ctx context.Context) (time.Duration, error) {
if err != nil {
return 0, err
}
- dr.m.cachePut(ctx, dr.domain, tlscert)
- dr.m.stateMu.Lock()
- defer dr.m.stateMu.Unlock()
- // m.state is guaranteed to be non-nil at this point
- dr.m.state[dr.domain] = state
+ if err := dr.m.cachePut(ctx, dr.ck, tlscert); err != nil {
+ return 0, err
+ }
+ dr.updateState(state)
return dr.next(leaf.NotAfter), nil
}
diff --git a/vendor/golang.org/x/crypto/acme/autocert/renewal_test.go b/vendor/golang.org/x/crypto/acme/autocert/renewal_test.go
index 11d40ff..634305a 100644
--- a/vendor/golang.org/x/crypto/acme/autocert/renewal_test.go
+++ b/vendor/golang.org/x/crypto/acme/autocert/renewal_test.go
@@ -48,8 +48,6 @@ func TestRenewalNext(t *testing.T) {
}
func TestRenewFromCache(t *testing.T) {
- const domain = "example.org"
-
// ACME CA server stub
var ca *httptest.Server
ca = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
@@ -84,7 +82,7 @@ func TestRenewFromCache(t *testing.T) {
if err != nil {
t.Fatalf("new-cert: CSR: %v", err)
}
- der, err := dummyCert(csr.PublicKey, domain)
+ der, err := dummyCert(csr.PublicKey, exampleDomain)
if err != nil {
t.Fatalf("new-cert: dummyCert: %v", err)
}
@@ -105,30 +103,28 @@ func TestRenewFromCache(t *testing.T) {
}))
defer ca.Close()
- // use EC key to run faster on 386
- key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
- if err != nil {
- t.Fatal(err)
- }
man := &Manager{
Prompt: AcceptTOS,
- Cache: newMemCache(),
+ Cache: newMemCache(t),
RenewBefore: 24 * time.Hour,
Client: &acme.Client{
- Key: key,
DirectoryURL: ca.URL,
},
}
defer man.stopRenew()
// cache an almost expired cert
+ key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
+ if err != nil {
+ t.Fatal(err)
+ }
now := time.Now()
- cert, err := dateDummyCert(key.Public(), now.Add(-2*time.Hour), now.Add(time.Minute), domain)
+ cert, err := dateDummyCert(key.Public(), now.Add(-2*time.Hour), now.Add(time.Minute), exampleDomain)
if err != nil {
t.Fatal(err)
}
tlscert := &tls.Certificate{PrivateKey: key, Certificate: [][]byte{cert}}
- if err := man.cachePut(context.Background(), domain, tlscert); err != nil {
+ if err := man.cachePut(context.Background(), exampleCertKey, tlscert); err != nil {
t.Fatal(err)
}
@@ -152,7 +148,7 @@ func TestRenewFromCache(t *testing.T) {
// ensure the new cert is cached
after := time.Now().Add(future)
- tlscert, err := man.cacheGet(context.Background(), domain)
+ tlscert, err := man.cacheGet(context.Background(), exampleCertKey)
if err != nil {
t.Fatalf("man.cacheGet: %v", err)
}
@@ -163,9 +159,9 @@ func TestRenewFromCache(t *testing.T) {
// verify the old cert is also replaced in memory
man.stateMu.Lock()
defer man.stateMu.Unlock()
- s := man.state[domain]
+ s := man.state[exampleCertKey]
if s == nil {
- t.Fatalf("m.state[%q] is nil", domain)
+ t.Fatalf("m.state[%q] is nil", exampleCertKey)
}
tlscert, err = s.tlscert()
if err != nil {
@@ -177,7 +173,7 @@ func TestRenewFromCache(t *testing.T) {
}
// trigger renew
- hello := &tls.ClientHelloInfo{ServerName: domain}
+ hello := clientHelloInfo(exampleDomain, true)
if _, err := man.GetCertificate(hello); err != nil {
t.Fatal(err)
}
@@ -189,3 +185,145 @@ func TestRenewFromCache(t *testing.T) {
case <-done:
}
}
+
+func TestRenewFromCacheAlreadyRenewed(t *testing.T) {
+ man := &Manager{
+ Prompt: AcceptTOS,
+ Cache: newMemCache(t),
+ RenewBefore: 24 * time.Hour,
+ Client: &acme.Client{
+ DirectoryURL: "invalid",
+ },
+ }
+ defer man.stopRenew()
+
+ // cache a recently renewed cert with a different private key
+ newKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
+ if err != nil {
+ t.Fatal(err)
+ }
+ now := time.Now()
+ newCert, err := dateDummyCert(newKey.Public(), now.Add(-2*time.Hour), now.Add(time.Hour*24*90), exampleDomain)
+ if err != nil {
+ t.Fatal(err)
+ }
+ newLeaf, err := validCert(exampleCertKey, [][]byte{newCert}, newKey)
+ if err != nil {
+ t.Fatal(err)
+ }
+ newTLSCert := &tls.Certificate{PrivateKey: newKey, Certificate: [][]byte{newCert}, Leaf: newLeaf}
+ if err := man.cachePut(context.Background(), exampleCertKey, newTLSCert); err != nil {
+ t.Fatal(err)
+ }
+
+ // set internal state to an almost expired cert
+ key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
+ if err != nil {
+ t.Fatal(err)
+ }
+ oldCert, err := dateDummyCert(key.Public(), now.Add(-2*time.Hour), now.Add(time.Minute), exampleDomain)
+ if err != nil {
+ t.Fatal(err)
+ }
+ oldLeaf, err := validCert(exampleCertKey, [][]byte{oldCert}, key)
+ if err != nil {
+ t.Fatal(err)
+ }
+ man.stateMu.Lock()
+ if man.state == nil {
+ man.state = make(map[certKey]*certState)
+ }
+ s := &certState{
+ key: key,
+ cert: [][]byte{oldCert},
+ leaf: oldLeaf,
+ }
+ man.state[exampleCertKey] = s
+ man.stateMu.Unlock()
+
+ // veriy the renewal accepted the newer cached cert
+ defer func() {
+ testDidRenewLoop = func(next time.Duration, err error) {}
+ }()
+ done := make(chan struct{})
+ testDidRenewLoop = func(next time.Duration, err error) {
+ defer close(done)
+ if err != nil {
+ t.Errorf("testDidRenewLoop: %v", err)
+ }
+ // Next should be about 90 days
+ // Previous expiration was within 1 min.
+ future := 88 * 24 * time.Hour
+ if next < future {
+ t.Errorf("testDidRenewLoop: next = %v; want >= %v", next, future)
+ }
+
+ // ensure the cached cert was not modified
+ tlscert, err := man.cacheGet(context.Background(), exampleCertKey)
+ if err != nil {
+ t.Fatalf("man.cacheGet: %v", err)
+ }
+ if !tlscert.Leaf.NotAfter.Equal(newLeaf.NotAfter) {
+ t.Errorf("cache leaf.NotAfter = %v; want == %v", tlscert.Leaf.NotAfter, newLeaf.NotAfter)
+ }
+
+ // verify the old cert is also replaced in memory
+ man.stateMu.Lock()
+ defer man.stateMu.Unlock()
+ s := man.state[exampleCertKey]
+ if s == nil {
+ t.Fatalf("m.state[%q] is nil", exampleCertKey)
+ }
+ stateKey := s.key.Public().(*ecdsa.PublicKey)
+ if stateKey.X.Cmp(newKey.X) != 0 || stateKey.Y.Cmp(newKey.Y) != 0 {
+ t.Fatalf("state key was not updated from cache x: %v y: %v; want x: %v y: %v", stateKey.X, stateKey.Y, newKey.X, newKey.Y)
+ }
+ tlscert, err = s.tlscert()
+ if err != nil {
+ t.Fatalf("s.tlscert: %v", err)
+ }
+ if !tlscert.Leaf.NotAfter.Equal(newLeaf.NotAfter) {
+ t.Errorf("state leaf.NotAfter = %v; want == %v", tlscert.Leaf.NotAfter, newLeaf.NotAfter)
+ }
+
+ // verify the private key is replaced in the renewal state
+ r := man.renewal[exampleCertKey]
+ if r == nil {
+ t.Fatalf("m.renewal[%q] is nil", exampleCertKey)
+ }
+ renewalKey := r.key.Public().(*ecdsa.PublicKey)
+ if renewalKey.X.Cmp(newKey.X) != 0 || renewalKey.Y.Cmp(newKey.Y) != 0 {
+ t.Fatalf("renewal private key was not updated from cache x: %v y: %v; want x: %v y: %v", renewalKey.X, renewalKey.Y, newKey.X, newKey.Y)
+ }
+
+ }
+
+ // assert the expiring cert is returned from state
+ hello := clientHelloInfo(exampleDomain, true)
+ tlscert, err := man.GetCertificate(hello)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if !oldLeaf.NotAfter.Equal(tlscert.Leaf.NotAfter) {
+ t.Errorf("state leaf.NotAfter = %v; want == %v", tlscert.Leaf.NotAfter, oldLeaf.NotAfter)
+ }
+
+ // trigger renew
+ go man.renew(exampleCertKey, s.key, s.leaf.NotAfter)
+
+ // wait for renew loop
+ select {
+ case <-time.After(10 * time.Second):
+ t.Fatal("renew took too long to occur")
+ case <-done:
+ // assert the new cert is returned from state after renew
+ hello := clientHelloInfo(exampleDomain, true)
+ tlscert, err := man.GetCertificate(hello)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if !newTLSCert.Leaf.NotAfter.Equal(tlscert.Leaf.NotAfter) {
+ t.Errorf("state leaf.NotAfter = %v; want == %v", tlscert.Leaf.NotAfter, newTLSCert.Leaf.NotAfter)
+ }
+ }
+}
diff --git a/vendor/golang.org/x/crypto/acme/http.go b/vendor/golang.org/x/crypto/acme/http.go
new file mode 100644
index 0000000..a43ce6a
--- /dev/null
+++ b/vendor/golang.org/x/crypto/acme/http.go
@@ -0,0 +1,281 @@
+// Copyright 2018 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package acme
+
+import (
+ "bytes"
+ "context"
+ "crypto"
+ "crypto/rand"
+ "encoding/json"
+ "fmt"
+ "io/ioutil"
+ "math/big"
+ "net/http"
+ "strconv"
+ "strings"
+ "time"
+)
+
+// retryTimer encapsulates common logic for retrying unsuccessful requests.
+// It is not safe for concurrent use.
+type retryTimer struct {
+ // backoffFn provides backoff delay sequence for retries.
+ // See Client.RetryBackoff doc comment.
+ backoffFn func(n int, r *http.Request, res *http.Response) time.Duration
+ // n is the current retry attempt.
+ n int
+}
+
+func (t *retryTimer) inc() {
+ t.n++
+}
+
+// backoff pauses the current goroutine as described in Client.RetryBackoff.
+func (t *retryTimer) backoff(ctx context.Context, r *http.Request, res *http.Response) error {
+ d := t.backoffFn(t.n, r, res)
+ if d <= 0 {
+ return fmt.Errorf("acme: no more retries for %s; tried %d time(s)", r.URL, t.n)
+ }
+ wakeup := time.NewTimer(d)
+ defer wakeup.Stop()
+ select {
+ case <-ctx.Done():
+ return ctx.Err()
+ case <-wakeup.C:
+ return nil
+ }
+}
+
+func (c *Client) retryTimer() *retryTimer {
+ f := c.RetryBackoff
+ if f == nil {
+ f = defaultBackoff
+ }
+ return &retryTimer{backoffFn: f}
+}
+
+// defaultBackoff provides default Client.RetryBackoff implementation
+// using a truncated exponential backoff algorithm,
+// as described in Client.RetryBackoff.
+//
+// The n argument is always bounded between 1 and 30.
+// The returned value is always greater than 0.
+func defaultBackoff(n int, r *http.Request, res *http.Response) time.Duration {
+ const max = 10 * time.Second
+ var jitter time.Duration
+ if x, err := rand.Int(rand.Reader, big.NewInt(1000)); err == nil {
+ // Set the minimum to 1ms to avoid a case where
+ // an invalid Retry-After value is parsed into 0 below,
+ // resulting in the 0 returned value which would unintentionally
+ // stop the retries.
+ jitter = (1 + time.Duration(x.Int64())) * time.Millisecond
+ }
+ if v, ok := res.Header["Retry-After"]; ok {
+ return retryAfter(v[0]) + jitter
+ }
+
+ if n < 1 {
+ n = 1
+ }
+ if n > 30 {
+ n = 30
+ }
+ d := time.Duration(1<<uint(n-1))*time.Second + jitter
+ if d > max {
+ return max
+ }
+ return d
+}
+
+// retryAfter parses a Retry-After HTTP header value,
+// trying to convert v into an int (seconds) or use http.ParseTime otherwise.
+// It returns zero value if v cannot be parsed.
+func retryAfter(v string) time.Duration {
+ if i, err := strconv.Atoi(v); err == nil {
+ return time.Duration(i) * time.Second
+ }
+ t, err := http.ParseTime(v)
+ if err != nil {
+ return 0
+ }
+ return t.Sub(timeNow())
+}
+
+// resOkay is a function that reports whether the provided response is okay.
+// It is expected to keep the response body unread.
+type resOkay func(*http.Response) bool
+
+// wantStatus returns a function which reports whether the code
+// matches the status code of a response.
+func wantStatus(codes ...int) resOkay {
+ return func(res *http.Response) bool {
+ for _, code := range codes {
+ if code == res.StatusCode {
+ return true
+ }
+ }
+ return false
+ }
+}
+
+// get issues an unsigned GET request to the specified URL.
+// It returns a non-error value only when ok reports true.
+//
+// get retries unsuccessful attempts according to c.RetryBackoff
+// until the context is done or a non-retriable error is received.
+func (c *Client) get(ctx context.Context, url string, ok resOkay) (*http.Response, error) {
+ retry := c.retryTimer()
+ for {
+ req, err := http.NewRequest("GET", url, nil)
+ if err != nil {
+ return nil, err
+ }
+ res, err := c.doNoRetry(ctx, req)
+ switch {
+ case err != nil:
+ return nil, err
+ case ok(res):
+ return res, nil
+ case isRetriable(res.StatusCode):
+ retry.inc()
+ resErr := responseError(res)
+ res.Body.Close()
+ // Ignore the error value from retry.backoff
+ // and return the one from last retry, as received from the CA.
+ if retry.backoff(ctx, req, res) != nil {
+ return nil, resErr
+ }
+ default:
+ defer res.Body.Close()
+ return nil, responseError(res)
+ }
+ }
+}
+
+// post issues a signed POST request in JWS format using the provided key
+// to the specified URL.
+// It returns a non-error value only when ok reports true.
+//
+// post retries unsuccessful attempts according to c.RetryBackoff
+// until the context is done or a non-retriable error is received.
+// It uses postNoRetry to make individual requests.
+func (c *Client) post(ctx context.Context, key crypto.Signer, url string, body interface{}, ok resOkay) (*http.Response, error) {
+ retry := c.retryTimer()
+ for {
+ res, req, err := c.postNoRetry(ctx, key, url, body)
+ if err != nil {
+ return nil, err
+ }
+ if ok(res) {
+ return res, nil
+ }
+ resErr := responseError(res)
+ res.Body.Close()
+ switch {
+ // Check for bad nonce before isRetriable because it may have been returned
+ // with an unretriable response code such as 400 Bad Request.
+ case isBadNonce(resErr):
+ // Consider any previously stored nonce values to be invalid.
+ c.clearNonces()
+ case !isRetriable(res.StatusCode):
+ return nil, resErr
+ }
+ retry.inc()
+ // Ignore the error value from retry.backoff
+ // and return the one from last retry, as received from the CA.
+ if err := retry.backoff(ctx, req, res); err != nil {
+ return nil, resErr
+ }
+ }
+}
+
+// postNoRetry signs the body with the given key and POSTs it to the provided url.
+// The body argument must be JSON-serializable.
+// It is used by c.post to retry unsuccessful attempts.
+func (c *Client) postNoRetry(ctx context.Context, key crypto.Signer, url string, body interface{}) (*http.Response, *http.Request, error) {
+ nonce, err := c.popNonce(ctx, url)
+ if err != nil {
+ return nil, nil, err
+ }
+ b, err := jwsEncodeJSON(body, key, nonce)
+ if err != nil {
+ return nil, nil, err
+ }
+ req, err := http.NewRequest("POST", url, bytes.NewReader(b))
+ if err != nil {
+ return nil, nil, err
+ }
+ req.Header.Set("Content-Type", "application/jose+json")
+ res, err := c.doNoRetry(ctx, req)
+ if err != nil {
+ return nil, nil, err
+ }
+ c.addNonce(res.Header)
+ return res, req, nil
+}
+
+// doNoRetry issues a request req, replacing its context (if any) with ctx.
+func (c *Client) doNoRetry(ctx context.Context, req *http.Request) (*http.Response, error) {
+ res, err := c.httpClient().Do(req.WithContext(ctx))
+ if err != nil {
+ select {
+ case <-ctx.Done():
+ // Prefer the unadorned context error.
+ // (The acme package had tests assuming this, previously from ctxhttp's
+ // behavior, predating net/http supporting contexts natively)
+ // TODO(bradfitz): reconsider this in the future. But for now this
+ // requires no test updates.
+ return nil, ctx.Err()
+ default:
+ return nil, err
+ }
+ }
+ return res, nil
+}
+
+func (c *Client) httpClient() *http.Client {
+ if c.HTTPClient != nil {
+ return c.HTTPClient
+ }
+ return http.DefaultClient
+}
+
+// isBadNonce reports whether err is an ACME "badnonce" error.
+func isBadNonce(err error) bool {
+ // According to the spec badNonce is urn:ietf:params:acme:error:badNonce.
+ // However, ACME servers in the wild return their versions of the error.
+ // See https://tools.ietf.org/html/draft-ietf-acme-acme-02#section-5.4
+ // and https://github.com/letsencrypt/boulder/blob/0e07eacb/docs/acme-divergences.md#section-66.
+ ae, ok := err.(*Error)
+ return ok && strings.HasSuffix(strings.ToLower(ae.ProblemType), ":badnonce")
+}
+
+// isRetriable reports whether a request can be retried
+// based on the response status code.
+//
+// Note that a "bad nonce" error is returned with a non-retriable 400 Bad Request code.
+// Callers should parse the response and check with isBadNonce.
+func isRetriable(code int) bool {
+ return code <= 399 || code >= 500 || code == http.StatusTooManyRequests
+}
+
+// responseError creates an error of Error type from resp.
+func responseError(resp *http.Response) error {
+ // don't care if ReadAll returns an error:
+ // json.Unmarshal will fail in that case anyway
+ b, _ := ioutil.ReadAll(resp.Body)
+ e := &wireError{Status: resp.StatusCode}
+ if err := json.Unmarshal(b, e); err != nil {
+ // this is not a regular error response:
+ // populate detail with anything we received,
+ // e.Status will already contain HTTP response code value
+ e.Detail = string(b)
+ if e.Detail == "" {
+ e.Detail = resp.Status
+ }
+ }
+ return e.error(resp.Header)
+}
diff --git a/vendor/golang.org/x/crypto/acme/http_test.go b/vendor/golang.org/x/crypto/acme/http_test.go
new file mode 100644
index 0000000..15e401b
--- /dev/null
+++ b/vendor/golang.org/x/crypto/acme/http_test.go
@@ -0,0 +1,209 @@
+// Copyright 2018 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package acme
+
+import (
+ "context"
+ "fmt"
+ "io/ioutil"
+ "net/http"
+ "net/http/httptest"
+ "reflect"
+ "strings"
+ "testing"
+ "time"
+)
+
+func TestDefaultBackoff(t *testing.T) {
+ tt := []struct {
+ nretry int
+ retryAfter string // Retry-After header
+ out time.Duration // expected min; max = min + jitter
+ }{
+ {-1, "", time.Second}, // verify the lower bound is 1
+ {0, "", time.Second}, // verify the lower bound is 1
+ {100, "", 10 * time.Second}, // verify the ceiling
+ {1, "3600", time.Hour}, // verify the header value is used
+ {1, "", 1 * time.Second},
+ {2, "", 2 * time.Second},
+ {3, "", 4 * time.Second},
+ {4, "", 8 * time.Second},
+ }
+ for i, test := range tt {
+ r := httptest.NewRequest("GET", "/", nil)
+ resp := &http.Response{Header: http.Header{}}
+ if test.retryAfter != "" {
+ resp.Header.Set("Retry-After", test.retryAfter)
+ }
+ d := defaultBackoff(test.nretry, r, resp)
+ max := test.out + time.Second // + max jitter
+ if d < test.out || max < d {
+ t.Errorf("%d: defaultBackoff(%v) = %v; want between %v and %v", i, test.nretry, d, test.out, max)
+ }
+ }
+}
+
+func TestErrorResponse(t *testing.T) {
+ s := `{
+ "status": 400,
+ "type": "urn:acme:error:xxx",
+ "detail": "text"
+ }`
+ res := &http.Response{
+ StatusCode: 400,
+ Status: "400 Bad Request",
+ Body: ioutil.NopCloser(strings.NewReader(s)),
+ Header: http.Header{"X-Foo": {"bar"}},
+ }
+ err := responseError(res)
+ v, ok := err.(*Error)
+ if !ok {
+ t.Fatalf("err = %+v (%T); want *Error type", err, err)
+ }
+ if v.StatusCode != 400 {
+ t.Errorf("v.StatusCode = %v; want 400", v.StatusCode)
+ }
+ if v.ProblemType != "urn:acme:error:xxx" {
+ t.Errorf("v.ProblemType = %q; want urn:acme:error:xxx", v.ProblemType)
+ }
+ if v.Detail != "text" {
+ t.Errorf("v.Detail = %q; want text", v.Detail)
+ }
+ if !reflect.DeepEqual(v.Header, res.Header) {
+ t.Errorf("v.Header = %+v; want %+v", v.Header, res.Header)
+ }
+}
+
+func TestPostWithRetries(t *testing.T) {
+ var count int
+ ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ count++
+ w.Header().Set("Replay-Nonce", fmt.Sprintf("nonce%d", count))
+ if r.Method == "HEAD" {
+ // We expect the client to do 2 head requests to fetch
+ // nonces, one to start and another after getting badNonce
+ return
+ }
+
+ head, err := decodeJWSHead(r)
+ switch {
+ case err != nil:
+ t.Errorf("decodeJWSHead: %v", err)
+ case head.Nonce == "":
+ t.Error("head.Nonce is empty")
+ case head.Nonce == "nonce1":
+ // Return a badNonce error to force the call to retry.
+ w.Header().Set("Retry-After", "0")
+ w.WriteHeader(http.StatusBadRequest)
+ w.Write([]byte(`{"type":"urn:ietf:params:acme:error:badNonce"}`))
+ return
+ }
+ // Make client.Authorize happy; we're not testing its result.
+ w.WriteHeader(http.StatusCreated)
+ w.Write([]byte(`{"status":"valid"}`))
+ }))
+ defer ts.Close()
+
+ client := &Client{Key: testKey, dir: &Directory{AuthzURL: ts.URL}}
+ // This call will fail with badNonce, causing a retry
+ if _, err := client.Authorize(context.Background(), "example.com"); err != nil {
+ t.Errorf("client.Authorize 1: %v", err)
+ }
+ if count != 4 {
+ t.Errorf("total requests count: %d; want 4", count)
+ }
+}
+
+func TestRetryErrorType(t *testing.T) {
+ ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ w.Header().Set("Replay-Nonce", "nonce")
+ w.WriteHeader(http.StatusTooManyRequests)
+ w.Write([]byte(`{"type":"rateLimited"}`))
+ }))
+ defer ts.Close()
+
+ client := &Client{
+ Key: testKey,
+ RetryBackoff: func(n int, r *http.Request, res *http.Response) time.Duration {
+ // Do no retries.
+ return 0
+ },
+ dir: &Directory{AuthzURL: ts.URL},
+ }
+
+ t.Run("post", func(t *testing.T) {
+ testRetryErrorType(t, func() error {
+ _, err := client.Authorize(context.Background(), "example.com")
+ return err
+ })
+ })
+ t.Run("get", func(t *testing.T) {
+ testRetryErrorType(t, func() error {
+ _, err := client.GetAuthorization(context.Background(), ts.URL)
+ return err
+ })
+ })
+}
+
+func testRetryErrorType(t *testing.T, callClient func() error) {
+ t.Helper()
+ err := callClient()
+ if err == nil {
+ t.Fatal("client.Authorize returned nil error")
+ }
+ acmeErr, ok := err.(*Error)
+ if !ok {
+ t.Fatalf("err is %v (%T); want *Error", err, err)
+ }
+ if acmeErr.StatusCode != http.StatusTooManyRequests {
+ t.Errorf("acmeErr.StatusCode = %d; want %d", acmeErr.StatusCode, http.StatusTooManyRequests)
+ }
+ if acmeErr.ProblemType != "rateLimited" {
+ t.Errorf("acmeErr.ProblemType = %q; want 'rateLimited'", acmeErr.ProblemType)
+ }
+}
+
+func TestRetryBackoffArgs(t *testing.T) {
+ const resCode = http.StatusInternalServerError
+ ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ w.Header().Set("Replay-Nonce", "test-nonce")
+ w.WriteHeader(resCode)
+ }))
+ defer ts.Close()
+
+ // Canceled in backoff.
+ ctx, cancel := context.WithCancel(context.Background())
+
+ var nretry int
+ backoff := func(n int, r *http.Request, res *http.Response) time.Duration {
+ nretry++
+ if n != nretry {
+ t.Errorf("n = %d; want %d", n, nretry)
+ }
+ if nretry == 3 {
+ cancel()
+ }
+
+ if r == nil {
+ t.Error("r is nil")
+ }
+ if res.StatusCode != resCode {
+ t.Errorf("res.StatusCode = %d; want %d", res.StatusCode, resCode)
+ }
+ return time.Millisecond
+ }
+
+ client := &Client{
+ Key: testKey,
+ RetryBackoff: backoff,
+ dir: &Directory{AuthzURL: ts.URL},
+ }
+ if _, err := client.Authorize(ctx, "example.com"); err == nil {
+ t.Error("err is nil")
+ }
+ if nretry != 3 {
+ t.Errorf("nretry = %d; want 3", nretry)
+ }
+}
diff --git a/vendor/golang.org/x/crypto/acme/types.go b/vendor/golang.org/x/crypto/acme/types.go
index 3e19974..54792c0 100644
--- a/vendor/golang.org/x/crypto/acme/types.go
+++ b/vendor/golang.org/x/crypto/acme/types.go
@@ -104,7 +104,7 @@ func RateLimit(err error) (time.Duration, bool) {
if e.Header == nil {
return 0, true
}
- return retryAfter(e.Header.Get("Retry-After"), 0), true
+ return retryAfter(e.Header.Get("Retry-After")), true
}
// Account is a user account. It is associated with a private key.
@@ -296,8 +296,8 @@ func (e *wireError) error(h http.Header) *Error {
}
}
-// CertOption is an optional argument type for the TLSSNIxChallengeCert methods for
-// customizing a temporary certificate for TLS-SNI challenges.
+// CertOption is an optional argument type for the TLS ChallengeCert methods for
+// customizing a temporary certificate for TLS-based challenges.
type CertOption interface {
privateCertOpt()
}
@@ -317,7 +317,7 @@ func (*certOptKey) privateCertOpt() {}
// WithTemplate creates an option for specifying a certificate template.
// See x509.CreateCertificate for template usage details.
//
-// In TLSSNIxChallengeCert methods, the template is also used as parent,
+// In TLS ChallengeCert methods, the template is also used as parent,
// resulting in a self-signed certificate.
// The DNSNames field of t is always overwritten for tls-sni challenge certs.
func WithTemplate(t *x509.Certificate) CertOption {