summaryrefslogtreecommitdiffstats
path: root/vendor/golang.org/x/crypto/acme/acme.go
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/golang.org/x/crypto/acme/acme.go')
-rw-r--r--vendor/golang.org/x/crypto/acme/acme.go441
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