diff options
Diffstat (limited to 'vendor/golang.org/x/crypto/acme/acme.go')
-rw-r--r-- | vendor/golang.org/x/crypto/acme/acme.go | 441 |
1 files changed, 72 insertions, 369 deletions
diff --git a/vendor/golang.org/x/crypto/acme/acme.go b/vendor/golang.org/x/crypto/acme/acme.go index 73b19ef3..f2d23f69 100644 --- a/vendor/golang.org/x/crypto/acme/acme.go +++ b/vendor/golang.org/x/crypto/acme/acme.go @@ -3,17 +3,20 @@ // license that can be found in the LICENSE file. // Package acme provides an implementation of the -// Automatic Certificate Management Environment (ACME) spec. -// The initial implementation was based on ACME draft-02 and -// is now being extended to comply with RFC 8555. -// See https://tools.ietf.org/html/draft-ietf-acme-acme-02 -// and https://tools.ietf.org/html/rfc8555 for details. +// Automatic Certificate Management Environment (ACME) spec, +// most famously used by Let's Encrypt. +// +// The initial implementation of this package was based on an early version +// of the spec. The current implementation supports only the modern +// RFC 8555 but some of the old API surface remains for compatibility. +// While code using the old API will still compile, it will return an error. +// Note the deprecation comments to update your code. +// +// See https://tools.ietf.org/html/rfc8555 for the spec. // // Most common scenarios will want to use autocert subdirectory instead, // which provides automatic access to certificates from Let's Encrypt // and any other ACME-based CA. -// -// This package is a work in progress and makes no API stability promises. package acme import ( @@ -33,8 +36,6 @@ import ( "encoding/pem" "errors" "fmt" - "io" - "io/ioutil" "math/big" "net/http" "strings" @@ -72,6 +73,7 @@ const ( ) // Client is an ACME client. +// // The only required field is Key. An example of creating a client with a new key // is as follows: // @@ -125,7 +127,9 @@ type Client struct { cacheMu sync.Mutex dir *Directory // cached result of Client's Discover method - kid keyID // cached Account.URI obtained from registerRFC or getAccountRFC + // KID is the key identifier provided by the CA. If not provided it will be + // retrieved from the CA by making a call to the registration endpoint. + KID KeyID noncesMu sync.Mutex nonces map[string]struct{} // nonces collected from previous responses @@ -140,23 +144,22 @@ type Client struct { // // When in pre-RFC mode or when c.getRegRFC responds with an error, accountKID // returns noKeyID. -func (c *Client) accountKID(ctx context.Context) keyID { +func (c *Client) accountKID(ctx context.Context) KeyID { c.cacheMu.Lock() defer c.cacheMu.Unlock() - if !c.dir.rfcCompliant() { - return noKeyID - } - if c.kid != noKeyID { - return c.kid + if c.KID != noKeyID { + return c.KID } a, err := c.getRegRFC(ctx) if err != nil { return noKeyID } - c.kid = keyID(a.URI) - return c.kid + c.KID = KeyID(a.URI) + return c.KID } +var errPreRFC = errors.New("acme: server does not support the RFC 8555 version of ACME") + // Discover performs ACME server discovery using c.DirectoryURL. // // It caches successful result. So, subsequent calls will not result in @@ -177,53 +180,36 @@ func (c *Client) Discover(ctx context.Context) (Directory, error) { c.addNonce(res.Header) var v struct { - Reg string `json:"new-reg"` - RegRFC string `json:"newAccount"` - Authz string `json:"new-authz"` - AuthzRFC string `json:"newAuthz"` - OrderRFC string `json:"newOrder"` - Cert string `json:"new-cert"` - Revoke string `json:"revoke-cert"` - RevokeRFC string `json:"revokeCert"` - NonceRFC string `json:"newNonce"` - KeyChangeRFC string `json:"keyChange"` - Meta struct { - Terms string `json:"terms-of-service"` - TermsRFC string `json:"termsOfService"` - WebsiteRFC string `json:"website"` - CAA []string `json:"caa-identities"` - CAARFC []string `json:"caaIdentities"` - ExternalAcctRFC bool `json:"externalAccountRequired"` + Reg string `json:"newAccount"` + Authz string `json:"newAuthz"` + Order string `json:"newOrder"` + Revoke string `json:"revokeCert"` + Nonce string `json:"newNonce"` + KeyChange string `json:"keyChange"` + Meta struct { + Terms string `json:"termsOfService"` + Website string `json:"website"` + CAA []string `json:"caaIdentities"` + ExternalAcct bool `json:"externalAccountRequired"` } } if err := json.NewDecoder(res.Body).Decode(&v); err != nil { return Directory{}, err } - if v.OrderRFC == "" { - // Non-RFC compliant ACME CA. - c.dir = &Directory{ - RegURL: v.Reg, - AuthzURL: v.Authz, - CertURL: v.Cert, - RevokeURL: v.Revoke, - Terms: v.Meta.Terms, - Website: v.Meta.WebsiteRFC, - CAA: v.Meta.CAA, - } - return *c.dir, nil + if v.Order == "" { + return Directory{}, errPreRFC } - // RFC compliant ACME CA. c.dir = &Directory{ - RegURL: v.RegRFC, - AuthzURL: v.AuthzRFC, - OrderURL: v.OrderRFC, - RevokeURL: v.RevokeRFC, - NonceURL: v.NonceRFC, - KeyChangeURL: v.KeyChangeRFC, - Terms: v.Meta.TermsRFC, - Website: v.Meta.WebsiteRFC, - CAA: v.Meta.CAARFC, - ExternalAccountRequired: v.Meta.ExternalAcctRFC, + RegURL: v.Reg, + AuthzURL: v.Authz, + OrderURL: v.Order, + RevokeURL: v.Revoke, + NonceURL: v.Nonce, + KeyChangeURL: v.KeyChange, + Terms: v.Meta.Terms, + Website: v.Meta.Website, + CAA: v.Meta.CAA, + ExternalAccountRequired: v.Meta.ExternalAcct, } return *c.dir, nil } @@ -235,55 +221,11 @@ func (c *Client) directoryURL() string { return LetsEncryptURL } -// CreateCert requests a new certificate using the Certificate Signing Request csr encoded in DER format. -// It is incompatible with RFC 8555. Callers should use CreateOrderCert when interfacing -// with an RFC-compliant CA. +// CreateCert was part of the old version of ACME. It is incompatible with RFC 8555. // -// The exp argument indicates the desired certificate validity duration. CA may issue a certificate -// with a different duration. -// If the bundle argument is true, the returned value will also contain the CA (issuer) certificate chain. -// -// 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 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. +// Deprecated: this was for the pre-RFC 8555 version of ACME. Callers should use CreateOrderCert. func (c *Client) CreateCert(ctx context.Context, csr []byte, exp time.Duration, bundle bool) (der [][]byte, certURL string, err error) { - if _, err := c.Discover(ctx); err != nil { - return nil, "", err - } - - req := struct { - Resource string `json:"resource"` - CSR string `json:"csr"` - NotBefore string `json:"notBefore,omitempty"` - NotAfter string `json:"notAfter,omitempty"` - }{ - Resource: "new-cert", - CSR: base64.RawURLEncoding.EncodeToString(csr), - } - now := timeNow() - req.NotBefore = now.Format(time.RFC3339) - if exp > 0 { - req.NotAfter = now.Add(exp).Format(time.RFC3339) - } - - res, err := c.post(ctx, nil, c.dir.CertURL, req, wantStatus(http.StatusCreated)) - if err != nil { - return nil, "", err - } - defer res.Body.Close() - - curl := res.Header.Get("Location") // cert permanent URL - if res.ContentLength == 0 { - // no cert in the body; poll until we get it - cert, err := c.FetchCert(ctx, curl, bundle) - return cert, curl, err - } - // slurp issued cert and CA chain, if requested - cert, err := c.responseCert(ctx, res, bundle) - return cert, curl, err + return nil, "", errPreRFC } // FetchCert retrieves already issued certificate from the given url, in DER format. @@ -297,20 +239,10 @@ 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) { - dir, err := c.Discover(ctx) - if err != nil { - return nil, err - } - if dir.rfcCompliant() { - return c.fetchCertRFC(ctx, url, bundle) - } - - // Legacy non-authenticated GET request. - res, err := c.get(ctx, url, wantStatus(http.StatusOK)) - if err != nil { + if _, err := c.Discover(ctx); err != nil { return nil, err } - return c.responseCert(ctx, res, bundle) + return c.fetchCertRFC(ctx, url, bundle) } // RevokeCert revokes a previously issued certificate cert, provided in DER format. @@ -320,30 +252,10 @@ func (c *Client) FetchCert(ctx context.Context, url string, bundle bool) ([][]by // For instance, the key pair of the certificate may be authorized. // If the key is nil, c.Key is used instead. func (c *Client) RevokeCert(ctx context.Context, key crypto.Signer, cert []byte, reason CRLReasonCode) error { - dir, err := c.Discover(ctx) - if err != nil { - return err - } - if dir.rfcCompliant() { - return c.revokeCertRFC(ctx, key, cert, reason) - } - - // Legacy CA. - body := &struct { - Resource string `json:"resource"` - Cert string `json:"certificate"` - Reason int `json:"reason"` - }{ - Resource: "revoke-cert", - Cert: base64.RawURLEncoding.EncodeToString(cert), - Reason: int(reason), - } - res, err := c.post(ctx, key, dir.RevokeURL, body, wantStatus(http.StatusOK)) - if err != nil { + if _, err := c.Discover(ctx); err != nil { return err } - defer res.Body.Close() - return nil + return c.revokeCertRFC(ctx, key, cert, reason) } // AcceptTOS always returns true to indicate the acceptance of a CA's Terms of Service @@ -366,75 +278,33 @@ func (c *Client) Register(ctx context.Context, acct *Account, prompt func(tosURL if c.Key == nil { return nil, errors.New("acme: client.Key must be set to Register") } - - dir, err := c.Discover(ctx) - if err != nil { - return nil, err - } - if dir.rfcCompliant() { - return c.registerRFC(ctx, acct, prompt) - } - - // Legacy ACME draft registration flow. - a, err := c.doReg(ctx, dir.RegURL, "new-reg", acct) - if err != nil { + if _, err := c.Discover(ctx); err != nil { return nil, err } - var accept bool - if a.CurrentTerms != "" && a.CurrentTerms != a.AgreedTerms { - accept = prompt(a.CurrentTerms) - } - if accept { - a.AgreedTerms = a.CurrentTerms - a, err = c.UpdateReg(ctx, a) - } - return a, err + return c.registerRFC(ctx, acct, prompt) } // GetReg retrieves an existing account associated with c.Key. // -// The url argument is an Account URI used with pre-RFC 8555 CAs. -// It is ignored when interfacing with an RFC-compliant CA. +// The url argument is a legacy artifact of the pre-RFC 8555 API +// and is ignored. func (c *Client) GetReg(ctx context.Context, url string) (*Account, error) { - dir, err := c.Discover(ctx) - if err != nil { - return nil, err - } - if dir.rfcCompliant() { - return c.getRegRFC(ctx) - } - - // Legacy CA. - a, err := c.doReg(ctx, url, "reg", nil) - if err != nil { + if _, err := c.Discover(ctx); err != nil { return nil, err } - a.URI = url - return a, nil + return c.getRegRFC(ctx) } // UpdateReg updates an existing registration. // It returns an updated account copy. The provided account is not modified. // -// When interfacing with RFC-compliant CAs, a.URI is ignored and the account URL -// associated with c.Key is used instead. +// The account's URI is ignored and the account URL associated with +// c.Key is used instead. func (c *Client) UpdateReg(ctx context.Context, acct *Account) (*Account, error) { - dir, err := c.Discover(ctx) - if err != nil { - return nil, err - } - if dir.rfcCompliant() { - return c.updateRegRFC(ctx, acct) - } - - // Legacy CA. - uri := acct.URI - a, err := c.doReg(ctx, uri, "reg", acct) - if err != nil { + if _, err := c.Discover(ctx); err != nil { return nil, err } - a.URI = uri - return a, nil + return c.updateRegRFC(ctx, acct) } // Authorize performs the initial step in the pre-authorization flow, @@ -503,17 +373,11 @@ func (c *Client) authorize(ctx context.Context, typ, val 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) { - dir, err := c.Discover(ctx) - if err != nil { + if _, err := c.Discover(ctx); err != nil { return nil, err } - var res *http.Response - if dir.rfcCompliant() { - res, err = c.postAsGet(ctx, url, wantStatus(http.StatusOK)) - } else { - res, err = c.get(ctx, url, wantStatus(http.StatusOK, http.StatusAccepted)) - } + res, err := c.postAsGet(ctx, url, wantStatus(http.StatusOK)) if err != nil { return nil, err } @@ -535,7 +399,6 @@ func (c *Client) GetAuthorization(ctx context.Context, url string) (*Authorizati // // It does not revoke existing certificates. func (c *Client) RevokeAuthorization(ctx context.Context, url string) error { - // Required for c.accountKID() when in RFC mode. if _, err := c.Discover(ctx); err != nil { return err } @@ -565,18 +428,11 @@ func (c *Client) RevokeAuthorization(ctx context.Context, url string) error { // 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) { - // Required for c.accountKID() when in RFC mode. - dir, err := c.Discover(ctx) - if err != nil { + if _, err := c.Discover(ctx); err != nil { return nil, err } - getfn := c.postAsGet - if !dir.rfcCompliant() { - getfn = c.get - } - for { - res, err := getfn(ctx, url, wantStatus(http.StatusOK, http.StatusAccepted)) + res, err := c.postAsGet(ctx, url, wantStatus(http.StatusOK, http.StatusAccepted)) if err != nil { return nil, err } @@ -619,17 +475,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) { - // Required for c.accountKID() when in RFC mode. - dir, err := c.Discover(ctx) - if err != nil { + if _, err := c.Discover(ctx); err != nil { return nil, err } - getfn := c.postAsGet - if !dir.rfcCompliant() { - getfn = c.get - } - res, err := getfn(ctx, url, wantStatus(http.StatusOK, http.StatusAccepted)) + res, err := c.postAsGet(ctx, url, wantStatus(http.StatusOK, http.StatusAccepted)) if err != nil { return nil, err } @@ -647,29 +497,11 @@ func (c *Client) GetChallenge(ctx context.Context, url string) (*Challenge, erro // // The server will then perform the validation asynchronously. func (c *Client) Accept(ctx context.Context, chal *Challenge) (*Challenge, error) { - // Required for c.accountKID() when in RFC mode. - dir, err := c.Discover(ctx) - if err != nil { + if _, err := c.Discover(ctx); err != nil { return nil, err } - var req interface{} = json.RawMessage("{}") // RFC-compliant CA - if !dir.rfcCompliant() { - auth, err := keyAuth(c.Key.Public(), chal.Token) - if err != nil { - return nil, err - } - req = struct { - Resource string `json:"resource"` - Type string `json:"type"` - Auth string `json:"keyAuthorization"` - }{ - Resource: "challenge", - Type: chal.Type, - Auth: auth, - } - } - res, err := c.post(ctx, nil, chal.URI, req, wantStatus( + res, err := c.post(ctx, nil, chal.URI, json.RawMessage("{}"), wantStatus( http.StatusOK, // according to the spec http.StatusAccepted, // Let's Encrypt: see https://goo.gl/WsJ7VT (acme-divergences.md) )) @@ -720,7 +552,7 @@ func (c *Client) HTTP01ChallengePath(token string) string { // TLSSNI01ChallengeCert creates a certificate for TLS-SNI-01 challenge response. // -// Deprecated: This challenge type is unused in both draft-02 and RFC versions of ACME spec. +// Deprecated: This challenge type is unused in both draft-02 and RFC versions of the ACME spec. 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 { @@ -738,7 +570,7 @@ func (c *Client) TLSSNI01ChallengeCert(token string, opt ...CertOption) (cert tl // TLSSNI02ChallengeCert creates a certificate for TLS-SNI-02 challenge response. // -// Deprecated: This challenge type is unused in both draft-02 and RFC versions of ACME spec. +// Deprecated: This challenge type is unused in both draft-02 and RFC versions of the ACME spec. func (c *Client) TLSSNI02ChallengeCert(token string, opt ...CertOption) (cert tls.Certificate, name string, err error) { b := sha256.Sum256([]byte(token)) h := hex.EncodeToString(b[:]) @@ -805,63 +637,6 @@ func (c *Client) TLSALPN01ChallengeCert(token, domain string, opt ...CertOption) return tlsChallengeCert([]string{domain}, newOpt) } -// doReg sends all types of registration requests the old way (pre-RFC world). -// The type of request is identified by typ argument, which is a "resource" -// in the ACME spec terms. -// -// A non-nil acct argument indicates whether the intention is to mutate data -// of the Account. Only Contact and Agreement of its fields are used -// in such cases. -func (c *Client) doReg(ctx context.Context, url string, typ string, acct *Account) (*Account, error) { - req := struct { - Resource string `json:"resource"` - Contact []string `json:"contact,omitempty"` - Agreement string `json:"agreement,omitempty"` - }{ - Resource: typ, - } - if acct != nil { - req.Contact = acct.Contact - req.Agreement = acct.AgreedTerms - } - res, err := c.post(ctx, nil, url, req, wantStatus( - http.StatusOK, // updates and deletes - http.StatusCreated, // new account creation - http.StatusAccepted, // Let's Encrypt divergent implementation - )) - if err != nil { - return nil, err - } - defer res.Body.Close() - - var v struct { - Contact []string - Agreement string - Authorizations string - Certificates string - } - if err := json.NewDecoder(res.Body).Decode(&v); err != nil { - return nil, fmt.Errorf("acme: invalid response: %v", err) - } - var tos string - if v := linkHeader(res.Header, "terms-of-service"); len(v) > 0 { - tos = v[0] - } - var authz string - if v := linkHeader(res.Header, "next"); len(v) > 0 { - authz = v[0] - } - return &Account{ - URI: res.Header.Get("Location"), - Contact: v.Contact, - AgreedTerms: v.Agreement, - CurrentTerms: tos, - Authz: authz, - Authorizations: v.Authorizations, - Certificates: v.Certificates, - }, nil -} - // popNonce returns a nonce value previously stored with c.addNonce // or fetches a fresh one from c.dir.NonceURL. // If NonceURL is empty, it first tries c.directoryURL() and, failing that, @@ -936,78 +711,6 @@ func nonceFromHeader(h http.Header) string { return h.Get("Replay-Nonce") } -func (c *Client) responseCert(ctx context.Context, res *http.Response, bundle bool) ([][]byte, error) { - b, err := ioutil.ReadAll(io.LimitReader(res.Body, maxCertSize+1)) - if err != nil { - return nil, fmt.Errorf("acme: response stream: %v", err) - } - if len(b) > maxCertSize { - return nil, errors.New("acme: certificate is too big") - } - cert := [][]byte{b} - if !bundle { - return cert, nil - } - - // Append CA chain cert(s). - // At least one is required according to the spec: - // https://tools.ietf.org/html/draft-ietf-acme-acme-03#section-6.3.1 - up := linkHeader(res.Header, "up") - if len(up) == 0 { - return nil, errors.New("acme: rel=up link not found") - } - if len(up) > maxChainLen { - return nil, errors.New("acme: rel=up link is too large") - } - for _, url := range up { - cc, err := c.chainCert(ctx, url, 0) - if err != nil { - return nil, err - } - cert = append(cert, cc...) - } - return cert, nil -} - -// 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. -// -// First chainCert call starts with depth of 0. -func (c *Client) chainCert(ctx context.Context, url string, depth int) ([][]byte, error) { - if depth >= maxChainLen { - return nil, errors.New("acme: certificate chain is too deep") - } - - res, err := c.get(ctx, url, wantStatus(http.StatusOK)) - if err != nil { - return nil, err - } - defer res.Body.Close() - b, err := ioutil.ReadAll(io.LimitReader(res.Body, maxCertSize+1)) - if err != nil { - return nil, err - } - if len(b) > maxCertSize { - return nil, errors.New("acme: certificate is too big") - } - chain := [][]byte{b} - - uplink := linkHeader(res.Header, "up") - if len(uplink) > maxChainLen { - return nil, errors.New("acme: certificate chain is too large") - } - for _, up := range uplink { - cc, err := c.chainCert(ctx, up, depth+1) - if err != nil { - return nil, err - } - chain = append(chain, cc...) - } - - return chain, nil -} - // linkHeader returns URI-Reference values of all Link headers // with relation-type rel. // See https://tools.ietf.org/html/rfc5988#section-5 for details. @@ -1098,5 +801,5 @@ func encodePEM(typ string, b []byte) []byte { return pem.EncodeToMemory(pb) } -// timeNow is useful for testing for fixed current time. +// timeNow is time.Now, except in tests which can mess with it. var timeNow = time.Now |