diff options
author | Niall Sheridan <nsheridan@gmail.com> | 2018-06-20 22:39:07 +0100 |
---|---|---|
committer | Niall Sheridan <nsheridan@gmail.com> | 2018-06-20 22:39:07 +0100 |
commit | de6d2c524430287c699aaa898c1325da6afea539 (patch) | |
tree | f78eb841208d667668a7bc92a9290d693cc7103b /vendor/golang.org/x/crypto/acme | |
parent | eb99016e1629e690e55633de6fc63a14c53e7ea2 (diff) |
Update dependencies
Diffstat (limited to 'vendor/golang.org/x/crypto/acme')
-rw-r--r-- | vendor/golang.org/x/crypto/acme/acme.go | 425 | ||||
-rw-r--r-- | vendor/golang.org/x/crypto/acme/autocert/autocert.go | 583 | ||||
-rw-r--r-- | vendor/golang.org/x/crypto/acme/autocert/cache.go | 6 | ||||
-rw-r--r-- | vendor/golang.org/x/crypto/acme/autocert/renewal.go | 43 | ||||
-rw-r--r-- | vendor/golang.org/x/crypto/acme/http.go | 276 | ||||
-rw-r--r-- | vendor/golang.org/x/crypto/acme/types.go | 8 |
6 files changed, 881 insertions, 460 deletions
diff --git a/vendor/golang.org/x/crypto/acme/acme.go b/vendor/golang.org/x/crypto/acme/acme.go index e8388b0..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"` @@ -142,7 +158,7 @@ func (c *Client) Discover(ctx context.Context) (Directory, error) { // // In the case where CA server does not provide the issued certificate in the response, // CreateCert will poll certURL using c.FetchCert, which will result in additional round-trips. -// In such scenario the caller can cancel the polling with ctx. +// In such a scenario, the caller can cancel the polling with ctx. // // CreateCert returns an error if the CA's response or chain was unreasonably large. // Callers are encouraged to parse the returned value to ensure the certificate is valid and has the expected features. @@ -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 } @@ -257,7 +252,7 @@ func (c *Client) RevokeCert(ctx context.Context, key crypto.Signer, cert []byte, func AcceptTOS(tosURL string) bool { return true } // Register creates a new account registration by following the "new-reg" flow. -// It returns registered account. The account is not modified. +// It returns the registered account. The account is not modified. // // The registration may require the caller to agree to the CA's Terms of Service (TOS). // If so, and the account has not indicated the acceptance of the terms (see Account for details), @@ -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,14 +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: @@ -1009,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: @@ -1022,17 +886,10 @@ 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] + } der, err := x509.CreateCertificate(rand.Reader, tmpl, tmpl, key.Public(), key) if err != nil { diff --git a/vendor/golang.org/x/crypto/acme/autocert/autocert.go b/vendor/golang.org/x/crypto/acme/autocert/autocert.go index b101020..c8fa4e6 100644 --- a/vendor/golang.org/x/crypto/acme/autocert/autocert.go +++ b/vendor/golang.org/x/crypto/acme/autocert/autocert.go @@ -24,8 +24,9 @@ import ( "fmt" "io" mathrand "math/rand" + "net" "net/http" - "strconv" + "path" "strings" "sync" "time" @@ -80,8 +81,9 @@ func defaultHostPolicy(context.Context, string) error { } // Manager is a stateful certificate manager built on top of acme.Client. -// It obtains and refreshes certificates automatically, -// as well as providing them to a TLS server via tls.Config. +// It obtains and refreshes certificates automatically using "tls-sni-01", +// "tls-sni-02" and "http-01" challenge types, as well as providing them +// to a TLS server via tls.Config. // // You must specify a cache implementation, such as DirCache, // to reuse obtained certificates across program restarts. @@ -96,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 @@ -125,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 @@ -138,27 +142,63 @@ 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 - - // tokenCert is keyed by token domain name, which matches server name - // of ClientHello. Keys always have ".acme.invalid" suffix. - tokenCertMu sync.RWMutex - tokenCert map[string]*tls.Certificate + 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 + // tryHTTP01 indicates whether the Manager should try "http-01" challenge type + // during the authorization flow. + tryHTTP01 bool + // httpTokens contains response body values for http-01 challenges + // and is keyed by the URL path at which a challenge response is expected + // to be provisioned. + // The entries are stored for the duration of the authorization flow. + httpTokens map[string][]byte + // certTokens contains temporary certificates for tls-sni challenges + // and is keyed by token domain name, which matches server name of ClientHello. + // Keys always have ".acme.invalid" suffix. + // The entries are stored for the duration of the authorization flow. + 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. @@ -181,21 +221,23 @@ 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") } + // In the worst-case scenario, the timeout needs to account for caching, host policy, + // domain ownership verification and certificate issuance. ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute) defer cancel() // check whether this is a token cert requested for TLS-SNI challenge if strings.HasSuffix(name, ".acme.invalid") { - m.tokenCertMu.RLock() - defer m.tokenCertMu.RUnlock() - if cert := m.tokenCert[name]; cert != nil { + m.tokensMu.RLock() + defer m.tokensMu.RUnlock() + 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? @@ -203,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 } @@ -216,27 +261,135 @@ 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, +// it delegates the request to the optional fallback handler. +// +// If fallback is nil, the returned handler redirects all GET and HEAD requests +// to the default TLS port 443 with 302 Found status code, preserving the original +// request path and query. It responds with 400 Bad Request to all other HTTP methods. +// The fallback is not protected by the optional HostPolicy. +// +// Because the fallback handler is run with unencrypted port 80 requests, +// the fallback should not serve TLS-only requests. +// +// If HTTPHandler is never called, the Manager will only use TLS SNI +// challenges for domain verification. +func (m *Manager) HTTPHandler(fallback http.Handler) http.Handler { + m.tokensMu.Lock() + defer m.tokensMu.Unlock() + m.tryHTTP01 = true + + if fallback == nil { + fallback = http.HandlerFunc(handleHTTPRedirect) + } + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if !strings.HasPrefix(r.URL.Path, "/.well-known/acme-challenge/") { + fallback.ServeHTTP(w, r) + return + } + // A reasonable context timeout for cache and host policy only, + // because we don't wait for a new certificate issuance here. + ctx, cancel := context.WithTimeout(r.Context(), time.Minute) + defer cancel() + if err := m.hostPolicy()(ctx, r.Host); err != nil { + http.Error(w, err.Error(), http.StatusForbidden) + return + } + data, err := m.httpToken(ctx, r.URL.Path) + if err != nil { + http.Error(w, err.Error(), http.StatusNotFound) + return + } + w.Write(data) + }) +} + +func handleHTTPRedirect(w http.ResponseWriter, r *http.Request) { + if r.Method != "GET" && r.Method != "HEAD" { + http.Error(w, "Use HTTPS", http.StatusBadRequest) + return + } + target := "https://" + stripPort(r.Host) + r.URL.RequestURI() + http.Redirect(w, r, target, http.StatusFound) +} + +func stripPort(hostport string) string { + host, _, err := net.SplitHostPort(hostport) + if err != nil { + return hostport + } + return net.JoinHostPort(host, "443") +} + // 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 } @@ -245,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 } @@ -294,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 } @@ -306,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 } @@ -338,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 { @@ -355,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 } @@ -371,48 +524,48 @@ func (m *Manager) createCert(ctx context.Context, domain string) (*tls.Certifica // We are the first; state is locked. // Unblock the readers when domain ownership is verified - // and the we got the cert or the process failed. + // and we got the cert or the process failed. 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 } @@ -421,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) @@ -435,21 +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 domain ownership verification process and requests a new cert upon success. +// 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) { - if err := m.verify(ctx, domain); err != nil { - return nil, nil, err - } +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 } - csr, err := certRequest(key, domain) + + if err := m.verify(ctx, client, ck.domain); err != nil { + return nil, nil, err + } + csr, err := certRequest(key, ck.domain, m.ExtraExtensions) if err != nil { return nil, nil, err } @@ -457,105 +611,220 @@ 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 } -// verify starts a new identifier (domain) authorization flow. -// It prepares a challenge response and then blocks until the authorization -// is marked as "completed" by the CA (either succeeded or failed). -// -// verify returns nil iff the verification was successful. -func (m *Manager) verify(ctx context.Context, domain string) error { +// 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 err + return } - - // start domain authorization and get the challenge - authz, err := client.Authorize(ctx, domain) - if err != nil { - return err + for _, u := range uri { + client.RevokeAuthorization(ctx, u) } - // maybe don't need to at all - if authz.Status == acme.StatusValid { - return nil +} + +// 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 { + // The list of challenge types we'll try to fulfill + // in this specific order. + challengeTypes := []string{"tls-sni-02", "tls-sni-01"} + m.tokensMu.RLock() + if m.tryHTTP01 { + challengeTypes = append(challengeTypes, "http-01") } + m.tokensMu.RUnlock() - // pick a challenge: prefer tls-sni-02 over tls-sni-01 - // TODO: consider authz.Combinations - var chal *acme.Challenge - for _, c := range authz.Challenges { - if c.Type == "tls-sni-02" { - chal = c - break + // 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 c.Type == "tls-sni-01" { - chal = c + 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. + authz, err := client.Authorize(ctx, domain) + if err != nil { + return err + } + // No point in accepting challenges if the authorization status + // is in a final state. + switch authz.Status { + case acme.StatusValid: + return nil // already authorized + case acme.StatusInvalid: + 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) { + chal = pickChallenge(challengeTypes[nextTyp], authz.Challenges) + nextTyp++ + } + if chal == nil { + 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 { + errs[chal] = err + continue + } + delete(pendingAuthzs, authz.URI) + return nil } - if chal == nil { - return errors.New("acme/autocert: no supported challenge type found") - } +} - // create a token cert for the challenge response - var ( - cert tls.Certificate - name string - ) +// fulfill provisions a response to the challenge chal. +// The cleanup is non-nil only if provisioning succeeded. +func (m *Manager) fulfill(ctx context.Context, client *acme.Client, chal *acme.Challenge) (cleanup func(), err error) { switch chal.Type { case "tls-sni-01": - cert, name, err = client.TLSSNI01ChallengeCert(chal.Token) + cert, name, err := client.TLSSNI01ChallengeCert(chal.Token) + if err != nil { + return nil, err + } + m.putCertToken(ctx, name, &cert) + return func() { go m.deleteCertToken(name) }, nil case "tls-sni-02": - cert, name, err = client.TLSSNI02ChallengeCert(chal.Token) - default: - err = fmt.Errorf("acme/autocert: unknown challenge type %q", chal.Type) + cert, name, err := client.TLSSNI02ChallengeCert(chal.Token) + if err != nil { + return nil, err + } + m.putCertToken(ctx, name, &cert) + return func() { go m.deleteCertToken(name) }, nil + case "http-01": + resp, err := client.HTTP01ChallengeResponse(chal.Token) + if err != nil { + return nil, err + } + p := client.HTTP01ChallengePath(chal.Token) + m.putHTTPToken(ctx, p, resp) + return func() { go m.deleteHTTPToken(p) }, nil } - if err != nil { - return err + return nil, fmt.Errorf("acme/autocert: unknown challenge type %q", chal.Type) +} + +func pickChallenge(typ string, chal []*acme.Challenge) *acme.Challenge { + for _, c := range chal { + if c.Type == typ { + return c + } } - m.putTokenCert(ctx, name, &cert) - defer func() { - // verification has ended at this point - // don't need token cert anymore - go m.deleteTokenCert(name) - }() + return nil +} - // ready to fulfill the challenge - if _, err := client.Accept(ctx, chal); err != nil { - return err +// 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() + if m.certTokens == nil { + m.certTokens = make(map[string]*tls.Certificate) + } + m.certTokens[name] = cert + m.cachePut(ctx, certKey{domain: name, isToken: true}, cert) +} + +// 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 { + ck := certKey{domain: name, isToken: true} + m.Cache.Delete(context.Background(), ck.String()) + } +} + +// httpToken retrieves an existing http-01 token value from an in-memory map +// or the optional cache. +func (m *Manager) httpToken(ctx context.Context, tokenPath string) ([]byte, error) { + m.tokensMu.RLock() + defer m.tokensMu.RUnlock() + if v, ok := m.httpTokens[tokenPath]; ok { + return v, nil + } + if m.Cache == nil { + return nil, fmt.Errorf("acme/autocert: no token at %q", tokenPath) } - // wait for the CA to validate - _, err = client.WaitAuthorization(ctx, authz.URI) - return err + return m.Cache.Get(ctx, httpTokenCacheKey(tokenPath)) } -// putTokenCert stores the cert under the named key in both m.tokenCert map -// and m.Cache. -func (m *Manager) putTokenCert(ctx context.Context, name string, cert *tls.Certificate) { - m.tokenCertMu.Lock() - defer m.tokenCertMu.Unlock() - if m.tokenCert == nil { - m.tokenCert = make(map[string]*tls.Certificate) +// putHTTPToken stores an http-01 token value using tokenPath as key +// in both in-memory map and the optional Cache. +// +// It ignores any error returned from Cache.Put. +func (m *Manager) putHTTPToken(ctx context.Context, tokenPath, val string) { + m.tokensMu.Lock() + defer m.tokensMu.Unlock() + if m.httpTokens == nil { + m.httpTokens = make(map[string][]byte) + } + b := []byte(val) + m.httpTokens[tokenPath] = b + if m.Cache != nil { + m.Cache.Put(ctx, httpTokenCacheKey(tokenPath), b) } - m.tokenCert[name] = cert - m.cachePut(ctx, name, cert) } -// deleteTokenCert removes the token certificate for the specified domain name -// from both m.tokenCert map and m.Cache. -func (m *Manager) deleteTokenCert(name string) { - m.tokenCertMu.Lock() - defer m.tokenCertMu.Unlock() - delete(m.tokenCert, name) +// deleteHTTPToken removes an http-01 token value from both in-memory map +// and the optional Cache, ignoring any error returned from the latter. +// +// If m.Cache is non-nil, it blocks until Cache.Delete returns without a timeout. +func (m *Manager) deleteHTTPToken(tokenPath string) { + m.tokensMu.Lock() + defer m.tokensMu.Unlock() + delete(m.httpTokens, tokenPath) if m.Cache != nil { - m.Cache.Delete(context.Background(), name) + m.Cache.Delete(context.Background(), httpTokenCacheKey(tokenPath)) } } +// 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 path.Base(tokenPath) + "+http-01" +} + // renew starts a cert renewal timer loop, one per domain. // // The loop is scheduled in two cases: @@ -564,18 +833,18 @@ func (m *Manager) deleteTokenCert(name 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) } @@ -591,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) @@ -603,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 @@ -698,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) } @@ -734,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 { @@ -751,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 @@ -763,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) @@ -776,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 { @@ -784,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 @@ -817,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/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/http.go b/vendor/golang.org/x/crypto/acme/http.go new file mode 100644 index 0000000..56ba53a --- /dev/null +++ b/vendor/golang.org/x/crypto/acme/http.go @@ -0,0 +1,276 @@ +// 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): + res.Body.Close() + retry.inc() + if err := retry.backoff(ctx, req, res); err != nil { + return nil, err + } + 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 + } + err = 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(err): + // Consider any previously stored nonce values to be invalid. + c.clearNonces() + case !isRetriable(res.StatusCode): + return nil, err + } + retry.inc() + if err := retry.backoff(ctx, req, res); err != nil { + return nil, err + } + } +} + +// 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/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 { |