aboutsummaryrefslogtreecommitdiff
path: root/vendor/golang.org/x/crypto/acme
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/golang.org/x/crypto/acme')
-rw-r--r--vendor/golang.org/x/crypto/acme/acme.go154
-rw-r--r--vendor/golang.org/x/crypto/acme/autocert/autocert.go75
-rw-r--r--vendor/golang.org/x/crypto/acme/autocert/cache.go11
-rw-r--r--vendor/golang.org/x/crypto/acme/autocert/listener.go9
-rw-r--r--vendor/golang.org/x/crypto/acme/autocert/renewal.go10
-rw-r--r--vendor/golang.org/x/crypto/acme/jws.go2
-rw-r--r--vendor/golang.org/x/crypto/acme/types.go136
7 files changed, 280 insertions, 117 deletions
diff --git a/vendor/golang.org/x/crypto/acme/acme.go b/vendor/golang.org/x/crypto/acme/acme.go
index 140d422..e8388b0 100644
--- a/vendor/golang.org/x/crypto/acme/acme.go
+++ b/vendor/golang.org/x/crypto/acme/acme.go
@@ -51,38 +51,6 @@ const (
maxNonces = 100
)
-// CertOption is an optional argument type for Client methods which manipulate
-// certificate data.
-type CertOption interface {
- privateCertOpt()
-}
-
-// WithKey creates an option holding a private/public key pair.
-// The private part signs a certificate, and the public part represents the signee.
-func WithKey(key crypto.Signer) CertOption {
- return &certOptKey{key}
-}
-
-type certOptKey struct {
- key crypto.Signer
-}
-
-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,
-// 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 {
- return (*certOptTemplate)(t)
-}
-
-type certOptTemplate x509.Certificate
-
-func (*certOptTemplate) privateCertOpt() {}
-
// Client is an ACME client.
// The only required field is Key. An example of creating a client with a new key
// is as follows:
@@ -152,7 +120,7 @@ func (c *Client) Discover(ctx context.Context) (Directory, error) {
CAA []string `json:"caa-identities"`
}
}
- if json.NewDecoder(res.Body).Decode(&v); err != nil {
+ if err := json.NewDecoder(res.Body).Decode(&v); err != nil {
return Directory{}, err
}
c.dir = &Directory{
@@ -198,7 +166,7 @@ func (c *Client) CreateCert(ctx context.Context, csr []byte, exp time.Duration,
req.NotAfter = now.Add(exp).Format(time.RFC3339)
}
- res, err := c.postJWS(ctx, c.Key, c.dir.CertURL, req)
+ res, err := c.retryPostJWS(ctx, c.Key, c.dir.CertURL, req)
if err != nil {
return nil, "", err
}
@@ -207,7 +175,7 @@ func (c *Client) CreateCert(ctx context.Context, csr []byte, exp time.Duration,
return nil, "", responseError(res)
}
- curl := res.Header.Get("location") // cert permanent URL
+ 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)
@@ -240,7 +208,7 @@ func (c *Client) FetchCert(ctx context.Context, url string, bundle bool) ([][]by
if res.StatusCode > 299 {
return nil, responseError(res)
}
- d := retryAfter(res.Header.Get("retry-after"), 3*time.Second)
+ d := retryAfter(res.Header.Get("Retry-After"), 3*time.Second)
select {
case <-time.After(d):
// retry
@@ -273,7 +241,7 @@ func (c *Client) RevokeCert(ctx context.Context, key crypto.Signer, cert []byte,
if key == nil {
key = c.Key
}
- res, err := c.postJWS(ctx, key, c.dir.RevokeURL, body)
+ res, err := c.retryPostJWS(ctx, key, c.dir.RevokeURL, body)
if err != nil {
return err
}
@@ -289,7 +257,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 a argument is not modified.
+// It returns 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),
@@ -361,7 +329,7 @@ func (c *Client) Authorize(ctx context.Context, domain string) (*Authorization,
Resource: "new-authz",
Identifier: authzID{Type: "dns", Value: domain},
}
- res, err := c.postJWS(ctx, c.Key, c.dir.AuthzURL, req)
+ res, err := c.retryPostJWS(ctx, c.Key, c.dir.AuthzURL, req)
if err != nil {
return nil, err
}
@@ -419,7 +387,7 @@ func (c *Client) RevokeAuthorization(ctx context.Context, url string) error {
Status: "deactivated",
Delete: true,
}
- res, err := c.postJWS(ctx, c.Key, url, req)
+ res, err := c.retryPostJWS(ctx, c.Key, url, req)
if err != nil {
return err
}
@@ -436,29 +404,15 @@ func (c *Client) RevokeAuthorization(ctx context.Context, url string) error {
//
// 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 ErrAuthorizationFailed.
+// If the Status is StatusInvalid, the returned error is of type *AuthorizationError.
func (c *Client) WaitAuthorization(ctx context.Context, url string) (*Authorization, error) {
- var count int
- sleep := func(v string, inc int) error {
- count += inc
- d := backoff(count, 10*time.Second)
- d = retryAfter(v, d)
- wakeup := time.NewTimer(d)
- defer wakeup.Stop()
- select {
- case <-ctx.Done():
- return ctx.Err()
- case <-wakeup.C:
- return nil
- }
- }
-
+ sleep := sleeper(ctx)
for {
res, err := c.get(ctx, url)
if err != nil {
return nil, err
}
- retry := res.Header.Get("retry-after")
+ 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 {
@@ -479,7 +433,7 @@ func (c *Client) WaitAuthorization(ctx context.Context, url string) (*Authorizat
return raw.authorization(url), nil
}
if raw.Status == StatusInvalid {
- return nil, ErrAuthorizationFailed
+ return nil, raw.error(url)
}
if err := sleep(retry, 0); err != nil {
return nil, err
@@ -525,7 +479,7 @@ func (c *Client) Accept(ctx context.Context, chal *Challenge) (*Challenge, error
Type: chal.Type,
Auth: auth,
}
- res, err := c.postJWS(ctx, c.Key, chal.URI, req)
+ res, err := c.retryPostJWS(ctx, c.Key, chal.URI, req)
if err != nil {
return nil, err
}
@@ -658,7 +612,7 @@ func (c *Client) doReg(ctx context.Context, url string, typ string, acct *Accoun
req.Contact = acct.Contact
req.Agreement = acct.AgreedTerms
}
- res, err := c.postJWS(ctx, c.Key, url, req)
+ res, err := c.retryPostJWS(ctx, c.Key, url, req)
if err != nil {
return nil, err
}
@@ -695,6 +649,40 @@ 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) {
@@ -730,6 +718,13 @@ func (c *Client) popNonce(ctx context.Context, url string) (string, error) {
return nonce, nil
}
+// clearNonces clears any stored nonces
+func (c *Client) clearNonces() {
+ c.noncesMu.Lock()
+ defer c.noncesMu.Unlock()
+ c.nonces = make(map[string]struct{})
+}
+
// addNonce stores a nonce value found in h (if any) for future use.
func (c *Client) addNonce(h http.Header) {
v := nonceFromHeader(h)
@@ -855,14 +850,8 @@ 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 := struct {
- Status int
- Type string
- Detail string
- }{
- Status: resp.StatusCode,
- }
- if err := json.Unmarshal(b, &e); err != nil {
+ 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
@@ -871,12 +860,7 @@ func responseError(resp *http.Response) error {
e.Detail = resp.Status
}
}
- return &Error{
- StatusCode: e.Status,
- ProblemType: e.Type,
- Detail: e.Detail,
- Header: resp.Header,
- }
+ return e.error(resp.Header)
}
// chainCert fetches CA certificate chain recursively by following "up" links.
@@ -941,6 +925,28 @@ 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.
diff --git a/vendor/golang.org/x/crypto/acme/autocert/autocert.go b/vendor/golang.org/x/crypto/acme/autocert/autocert.go
index f50c88e..b101020 100644
--- a/vendor/golang.org/x/crypto/acme/autocert/autocert.go
+++ b/vendor/golang.org/x/crypto/acme/autocert/autocert.go
@@ -33,6 +33,12 @@ import (
"golang.org/x/crypto/acme"
)
+// createCertRetryAfter is how much time to wait before removing a failed state
+// entry due to an unsuccessful createCert call.
+// This is a variable instead of a const for testing.
+// TODO: Consider making it configurable or an exp backoff?
+var createCertRetryAfter = time.Minute
+
// pseudoRand is safe for concurrent use.
var pseudoRand *lockedMathRand
@@ -77,20 +83,10 @@ func defaultHostPolicy(context.Context, string) error {
// It obtains and refreshes certificates automatically,
// as well as providing them to a TLS server via tls.Config.
//
-// A simple usage example:
-//
-// m := autocert.Manager{
-// Prompt: autocert.AcceptTOS,
-// HostPolicy: autocert.HostWhitelist("example.org"),
-// }
-// s := &http.Server{
-// Addr: ":https",
-// TLSConfig: &tls.Config{GetCertificate: m.GetCertificate},
-// }
-// s.ListenAndServeTLS("", "")
-//
-// To preserve issued certificates and improve overall performance,
-// use a cache implementation of Cache. For instance, DirCache.
+// You must specify a cache implementation, such as DirCache,
+// to reuse obtained certificates across program restarts.
+// Otherwise your server is very likely to exceed the certificate
+// issuer's request rate limits.
type Manager struct {
// Prompt specifies a callback function to conditionally accept a CA's Terms of Service (TOS).
// The registration may require the caller to agree to the CA's TOS.
@@ -124,7 +120,7 @@ type Manager struct {
// RenewBefore optionally specifies how early certificates should
// be renewed before they expire.
//
- // If zero, they're renewed 1 week before expiration.
+ // If zero, they're renewed 30 days before expiration.
RenewBefore time.Duration
// Client is used to perform low-level operations, such as account registration
@@ -174,10 +170,20 @@ type Manager struct {
// The error is propagated back to the caller of GetCertificate and is user-visible.
// This does not affect cached certs. See HostPolicy field description for more details.
func (m *Manager) GetCertificate(hello *tls.ClientHelloInfo) (*tls.Certificate, error) {
+ if m.Prompt == nil {
+ return nil, errors.New("acme/autocert: Manager.Prompt not set")
+ }
+
name := hello.ServerName
if name == "" {
return nil, errors.New("acme/autocert: missing server name")
}
+ if !strings.Contains(strings.Trim(name, "."), ".") {
+ return nil, errors.New("acme/autocert: server name component count invalid")
+ }
+ if strings.ContainsAny(name, `/\`) {
+ return nil, errors.New("acme/autocert: server name contains invalid character")
+ }
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute)
defer cancel()
@@ -252,6 +258,7 @@ func (m *Manager) cert(ctx context.Context, name string) (*tls.Certificate, erro
}
// 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 m.Cache == nil {
return nil, ErrCacheMiss
@@ -264,7 +271,7 @@ func (m *Manager) cacheGet(ctx context.Context, domain string) (*tls.Certificate
// private
priv, pub := pem.Decode(data)
if priv == nil || !strings.Contains(priv.Type, "PRIVATE") {
- return nil, errors.New("acme/autocert: no private key found in cache")
+ return nil, ErrCacheMiss
}
privKey, err := parsePrivateKey(priv.Bytes)
if err != nil {
@@ -282,13 +289,14 @@ func (m *Manager) cacheGet(ctx context.Context, domain string) (*tls.Certificate
pubDER = append(pubDER, b.Bytes)
}
if len(pub) > 0 {
- return nil, errors.New("acme/autocert: invalid public key")
+ // Leftover content not consumed by pem.Decode. Corrupt. Ignore.
+ return nil, ErrCacheMiss
}
// verify and create TLS cert
leaf, err := validCert(domain, pubDER, privKey)
if err != nil {
- return nil, err
+ return nil, ErrCacheMiss
}
tlscert := &tls.Certificate{
Certificate: pubDER,
@@ -369,6 +377,23 @@ func (m *Manager) createCert(ctx context.Context, domain string) (*tls.Certifica
der, leaf, err := m.authorizedCert(ctx, state.key, domain)
if err != nil {
+ // Remove the failed state after some time,
+ // making the manager call createCert again on the following TLS hello.
+ time.AfterFunc(createCertRetryAfter, func() {
+ defer testDidRemoveState(domain)
+ m.stateMu.Lock()
+ defer m.stateMu.Unlock()
+ // Verify the state hasn't changed and it's still invalid
+ // before deleting.
+ s, ok := m.state[domain]
+ if !ok {
+ return
+ }
+ if _, err := validCert(domain, s.cert, s.key); err == nil {
+ return
+ }
+ delete(m.state, domain)
+ })
return nil, err
}
state.cert = der
@@ -417,7 +442,6 @@ func (m *Manager) certState(domain string) (*certState, error) {
// authorizedCert starts domain ownership verification process and requests a new cert upon success.
// The key argument is the certificate private key.
func (m *Manager) authorizedCert(ctx context.Context, key crypto.Signer, domain string) (der [][]byte, leaf *x509.Certificate, err error) {
- // TODO: make m.verify retry or retry m.verify calls here
if err := m.verify(ctx, domain); err != nil {
return nil, nil, err
}
@@ -643,10 +667,10 @@ func (m *Manager) hostPolicy() HostPolicy {
}
func (m *Manager) renewBefore() time.Duration {
- if m.RenewBefore > maxRandRenew {
+ if m.RenewBefore > renewJitter {
return m.RenewBefore
}
- return 7 * 24 * time.Hour // 1 week
+ return 720 * time.Hour // 30 days
}
// certState is ready when its mutex is unlocked for reading.
@@ -788,5 +812,10 @@ func (r *lockedMathRand) int63n(max int64) int64 {
return n
}
-// for easier testing
-var timeNow = time.Now
+// For easier testing.
+var (
+ timeNow = time.Now
+
+ // Called when a state is removed.
+ testDidRemoveState = func(domain string) {}
+)
diff --git a/vendor/golang.org/x/crypto/acme/autocert/cache.go b/vendor/golang.org/x/crypto/acme/autocert/cache.go
index 9f3e9d1..61a5fd2 100644
--- a/vendor/golang.org/x/crypto/acme/autocert/cache.go
+++ b/vendor/golang.org/x/crypto/acme/autocert/cache.go
@@ -77,12 +77,13 @@ func (d DirCache) Put(ctx context.Context, name string, data []byte) error {
if tmp, err = d.writeTempFile(name, data); err != nil {
return
}
- // prevent overwriting the file if the context was cancelled
- if ctx.Err() != nil {
- return // no need to set err
+ select {
+ case <-ctx.Done():
+ // Don't overwrite the file if the context was canceled.
+ default:
+ newName := filepath.Join(string(d), name)
+ err = os.Rename(tmp, newName)
}
- name = filepath.Join(string(d), name)
- err = os.Rename(tmp, name)
}()
select {
case <-ctx.Done():
diff --git a/vendor/golang.org/x/crypto/acme/autocert/listener.go b/vendor/golang.org/x/crypto/acme/autocert/listener.go
index d4c93d2..d744df0 100644
--- a/vendor/golang.org/x/crypto/acme/autocert/listener.go
+++ b/vendor/golang.org/x/crypto/acme/autocert/listener.go
@@ -36,6 +36,9 @@ import (
// operating system-specific cache or temp directory. This may not
// be suitable for servers spanning multiple machines.
//
+// The returned listener uses a *tls.Config that enables HTTP/2, and
+// should only be used with servers that support HTTP/2.
+//
// The returned Listener also enables TCP keep-alives on the accepted
// connections. The returned *tls.Conn are returned before their TLS
// handshake has completed.
@@ -58,6 +61,9 @@ func NewListener(domains ...string) net.Listener {
// Listener listens on the standard TLS port (443) on all interfaces
// and returns a net.Listener returning *tls.Conn connections.
//
+// The returned listener uses a *tls.Config that enables HTTP/2, and
+// should only be used with servers that support HTTP/2.
+//
// The returned Listener also enables TCP keep-alives on the accepted
// connections. The returned *tls.Conn are returned before their TLS
// handshake has completed.
@@ -68,7 +74,8 @@ func (m *Manager) Listener() net.Listener {
ln := &listener{
m: m,
conf: &tls.Config{
- GetCertificate: m.GetCertificate, // bonus: panic on nil m
+ GetCertificate: m.GetCertificate, // bonus: panic on nil m
+ NextProtos: []string{"h2", "http/1.1"}, // Enable HTTP/2
},
}
ln.tcpListener, ln.tcpListenErr = net.Listen("tcp", ":443")
diff --git a/vendor/golang.org/x/crypto/acme/autocert/renewal.go b/vendor/golang.org/x/crypto/acme/autocert/renewal.go
index 14ac905..6c5da2b 100644
--- a/vendor/golang.org/x/crypto/acme/autocert/renewal.go
+++ b/vendor/golang.org/x/crypto/acme/autocert/renewal.go
@@ -11,8 +11,8 @@ import (
"time"
)
-// maxRandRenew is a maximum deviation from Manager.RenewBefore.
-const maxRandRenew = time.Hour
+// renewJitter is the maximum deviation from Manager.RenewBefore.
+const renewJitter = time.Hour
// domainRenewal tracks the state used by the periodic timers
// renewing a single domain's cert.
@@ -64,7 +64,7 @@ func (dr *domainRenewal) renew() {
// TODO: rotate dr.key at some point?
next, err := dr.do(ctx)
if err != nil {
- next = maxRandRenew / 2
+ next = renewJitter / 2
next += time.Duration(pseudoRand.int63n(int64(next)))
}
dr.timer = time.AfterFunc(next, dr.renew)
@@ -84,7 +84,7 @@ func (dr *domainRenewal) do(ctx context.Context) (time.Duration, error) {
// but we try nonetheless
if tlscert, err := dr.m.cacheGet(ctx, dr.domain); err == nil {
next := dr.next(tlscert.Leaf.NotAfter)
- if next > dr.m.renewBefore()+maxRandRenew {
+ if next > dr.m.renewBefore()+renewJitter {
return next, nil
}
}
@@ -113,7 +113,7 @@ func (dr *domainRenewal) do(ctx context.Context) (time.Duration, error) {
func (dr *domainRenewal) next(expiry time.Time) time.Duration {
d := expiry.Sub(timeNow()) - dr.m.renewBefore()
// add a bit of randomness to renew deadline
- n := pseudoRand.int63n(int64(maxRandRenew))
+ n := pseudoRand.int63n(int64(renewJitter))
d -= time.Duration(n)
if d < 0 {
return 0
diff --git a/vendor/golang.org/x/crypto/acme/jws.go b/vendor/golang.org/x/crypto/acme/jws.go
index 49ba313..6cbca25 100644
--- a/vendor/golang.org/x/crypto/acme/jws.go
+++ b/vendor/golang.org/x/crypto/acme/jws.go
@@ -134,7 +134,7 @@ func jwsHasher(key crypto.Signer) (string, crypto.Hash) {
return "ES256", crypto.SHA256
case "P-384":
return "ES384", crypto.SHA384
- case "P-512":
+ case "P-521":
return "ES512", crypto.SHA512
}
}
diff --git a/vendor/golang.org/x/crypto/acme/types.go b/vendor/golang.org/x/crypto/acme/types.go
index 0513b2e..3e19974 100644
--- a/vendor/golang.org/x/crypto/acme/types.go
+++ b/vendor/golang.org/x/crypto/acme/types.go
@@ -1,9 +1,17 @@
+// Copyright 2016 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 (
+ "crypto"
+ "crypto/x509"
"errors"
"fmt"
"net/http"
+ "strings"
+ "time"
)
// ACME server response statuses used to describe Authorization and Challenge states.
@@ -33,14 +41,8 @@ const (
CRLReasonAACompromise CRLReasonCode = 10
)
-var (
- // ErrAuthorizationFailed indicates that an authorization for an identifier
- // did not succeed.
- ErrAuthorizationFailed = errors.New("acme: identifier authorization failed")
-
- // ErrUnsupportedKey is returned when an unsupported key type is encountered.
- ErrUnsupportedKey = errors.New("acme: unknown key type; only RSA and ECDSA are supported")
-)
+// ErrUnsupportedKey is returned when an unsupported key type is encountered.
+var ErrUnsupportedKey = errors.New("acme: unknown key type; only RSA and ECDSA are supported")
// Error is an ACME error, defined in Problem Details for HTTP APIs doc
// http://tools.ietf.org/html/draft-ietf-appsawg-http-problem.
@@ -53,6 +55,7 @@ type Error struct {
// Detail is a human-readable explanation specific to this occurrence of the problem.
Detail string
// Header is the original server error response headers.
+ // It may be nil.
Header http.Header
}
@@ -60,6 +63,50 @@ func (e *Error) Error() string {
return fmt.Sprintf("%d %s: %s", e.StatusCode, e.ProblemType, e.Detail)
}
+// AuthorizationError indicates that an authorization for an identifier
+// did not succeed.
+// It contains all errors from Challenge items of the failed Authorization.
+type AuthorizationError struct {
+ // URI uniquely identifies the failed Authorization.
+ URI string
+
+ // Identifier is an AuthzID.Value of the failed Authorization.
+ Identifier string
+
+ // Errors is a collection of non-nil error values of Challenge items
+ // of the failed Authorization.
+ Errors []error
+}
+
+func (a *AuthorizationError) Error() string {
+ e := make([]string, len(a.Errors))
+ for i, err := range a.Errors {
+ e[i] = err.Error()
+ }
+ return fmt.Sprintf("acme: authorization error for %s: %s", a.Identifier, strings.Join(e, "; "))
+}
+
+// RateLimit reports whether err represents a rate limit error and
+// any Retry-After duration returned by the server.
+//
+// See the following for more details on rate limiting:
+// https://tools.ietf.org/html/draft-ietf-acme-acme-05#section-5.6
+func RateLimit(err error) (time.Duration, bool) {
+ e, ok := err.(*Error)
+ if !ok {
+ return 0, false
+ }
+ // Some CA implementations may return incorrect values.
+ // Use case-insensitive comparison.
+ if !strings.HasSuffix(strings.ToLower(e.ProblemType), ":ratelimited") {
+ return 0, false
+ }
+ if e.Header == nil {
+ return 0, true
+ }
+ return retryAfter(e.Header.Get("Retry-After"), 0), true
+}
+
// Account is a user account. It is associated with a private key.
type Account struct {
// URI is the account unique ID, which is also a URL used to retrieve
@@ -118,6 +165,8 @@ type Directory struct {
}
// Challenge encodes a returned CA challenge.
+// Its Error field may be non-nil if the challenge is part of an Authorization
+// with StatusInvalid.
type Challenge struct {
// Type is the challenge type, e.g. "http-01", "tls-sni-02", "dns-01".
Type string
@@ -130,6 +179,11 @@ type Challenge struct {
// Status identifies the status of this challenge.
Status string
+
+ // Error indicates the reason for an authorization failure
+ // when this challenge was used.
+ // The type of a non-nil value is *Error.
+ Error error
}
// Authorization encodes an authorization response.
@@ -187,12 +241,26 @@ func (z *wireAuthz) authorization(uri string) *Authorization {
return a
}
+func (z *wireAuthz) error(uri string) *AuthorizationError {
+ err := &AuthorizationError{
+ URI: uri,
+ Identifier: z.Identifier.Value,
+ }
+ for _, raw := range z.Challenges {
+ if raw.Error != nil {
+ err.Errors = append(err.Errors, raw.Error.error(nil))
+ }
+ }
+ return err
+}
+
// wireChallenge is ACME JSON challenge representation.
type wireChallenge struct {
URI string `json:"uri"`
Type string
Token string
Status string
+ Error *wireError
}
func (c *wireChallenge) challenge() *Challenge {
@@ -205,5 +273,57 @@ func (c *wireChallenge) challenge() *Challenge {
if v.Status == "" {
v.Status = StatusPending
}
+ if c.Error != nil {
+ v.Error = c.Error.error(nil)
+ }
return v
}
+
+// wireError is a subset of fields of the Problem Details object
+// as described in https://tools.ietf.org/html/rfc7807#section-3.1.
+type wireError struct {
+ Status int
+ Type string
+ Detail string
+}
+
+func (e *wireError) error(h http.Header) *Error {
+ return &Error{
+ StatusCode: e.Status,
+ ProblemType: e.Type,
+ Detail: e.Detail,
+ Header: h,
+ }
+}
+
+// CertOption is an optional argument type for the TLSSNIxChallengeCert methods for
+// customizing a temporary certificate for TLS-SNI challenges.
+type CertOption interface {
+ privateCertOpt()
+}
+
+// WithKey creates an option holding a private/public key pair.
+// The private part signs a certificate, and the public part represents the signee.
+func WithKey(key crypto.Signer) CertOption {
+ return &certOptKey{key}
+}
+
+type certOptKey struct {
+ key crypto.Signer
+}
+
+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,
+// 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 {
+ return (*certOptTemplate)(t)
+}
+
+type certOptTemplate x509.Certificate
+
+func (*certOptTemplate) privateCertOpt() {}