summaryrefslogtreecommitdiffstats
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.go23
-rw-r--r--vendor/golang.org/x/crypto/acme/acme_test.go3
-rw-r--r--vendor/golang.org/x/crypto/acme/autocert/autocert.go47
-rw-r--r--vendor/golang.org/x/crypto/acme/autocert/autocert_test.go126
-rw-r--r--vendor/golang.org/x/crypto/acme/jws.go2
-rw-r--r--vendor/golang.org/x/crypto/acme/jws_test.go177
-rw-r--r--vendor/golang.org/x/crypto/acme/types.go102
-rw-r--r--vendor/golang.org/x/crypto/acme/types_test.go63
8 files changed, 449 insertions, 94 deletions
diff --git a/vendor/golang.org/x/crypto/acme/acme.go b/vendor/golang.org/x/crypto/acme/acme.go
index d650604d3..a7b6ce4e9 100644
--- a/vendor/golang.org/x/crypto/acme/acme.go
+++ b/vendor/golang.org/x/crypto/acme/acme.go
@@ -152,7 +152,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{
@@ -436,7 +436,7 @@ 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) {
sleep := sleeper(ctx)
for {
@@ -465,7 +465,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
@@ -882,14 +882,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
@@ -898,12 +892,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.
diff --git a/vendor/golang.org/x/crypto/acme/acme_test.go b/vendor/golang.org/x/crypto/acme/acme_test.go
index 0210ce3df..a4d276db8 100644
--- a/vendor/golang.org/x/crypto/acme/acme_test.go
+++ b/vendor/golang.org/x/crypto/acme/acme_test.go
@@ -543,6 +543,9 @@ func TestWaitAuthorizationInvalid(t *testing.T) {
if err == nil {
t.Error("err is nil")
}
+ if _, ok := err.(*AuthorizationError); !ok {
+ t.Errorf("err is %T; want *AuthorizationError", err)
+ }
}
}
diff --git a/vendor/golang.org/x/crypto/acme/autocert/autocert.go b/vendor/golang.org/x/crypto/acme/autocert/autocert.go
index 98842b457..a478eff54 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
@@ -170,6 +176,12 @@ func (m *Manager) GetCertificate(hello *tls.ClientHelloInfo) (*tls.Certificate,
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()
@@ -244,6 +256,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
@@ -256,7 +269,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 {
@@ -274,13 +287,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,
@@ -361,6 +375,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
@@ -409,7 +440,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
}
@@ -780,5 +810,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/autocert_test.go b/vendor/golang.org/x/crypto/acme/autocert/autocert_test.go
index 6df4cf3bf..0352e340d 100644
--- a/vendor/golang.org/x/crypto/acme/autocert/autocert_test.go
+++ b/vendor/golang.org/x/crypto/acme/autocert/autocert_test.go
@@ -178,6 +178,88 @@ func TestGetCertificate_nilPrompt(t *testing.T) {
}
}
+func TestGetCertificate_expiredCache(t *testing.T) {
+ // Make an expired cert and cache it.
+ pk, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
+ if err != nil {
+ t.Fatal(err)
+ }
+ tmpl := &x509.Certificate{
+ SerialNumber: big.NewInt(1),
+ Subject: pkix.Name{CommonName: "example.org"},
+ NotAfter: time.Now(),
+ }
+ pub, err := x509.CreateCertificate(rand.Reader, tmpl, tmpl, &pk.PublicKey, pk)
+ if err != nil {
+ t.Fatal(err)
+ }
+ tlscert := &tls.Certificate{
+ Certificate: [][]byte{pub},
+ PrivateKey: pk,
+ }
+
+ man := &Manager{Prompt: AcceptTOS, Cache: newMemCache()}
+ defer man.stopRenew()
+ if err := man.cachePut(context.Background(), "example.org", tlscert); err != nil {
+ t.Fatalf("man.cachePut: %v", err)
+ }
+
+ // The expired cached cert should trigger a new cert issuance
+ // and return without an error.
+ hello := &tls.ClientHelloInfo{ServerName: "example.org"}
+ testGetCertificate(t, man, "example.org", hello)
+}
+
+func TestGetCertificate_failedAttempt(t *testing.T) {
+ ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ w.WriteHeader(http.StatusBadRequest)
+ }))
+ defer ts.Close()
+
+ const example = "example.org"
+ d := createCertRetryAfter
+ f := testDidRemoveState
+ defer func() {
+ createCertRetryAfter = d
+ testDidRemoveState = f
+ }()
+ createCertRetryAfter = 0
+ done := make(chan struct{})
+ testDidRemoveState = func(domain string) {
+ if domain != example {
+ t.Errorf("testDidRemoveState: domain = %q; want %q", domain, example)
+ }
+ close(done)
+ }
+
+ key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
+ if err != nil {
+ t.Fatal(err)
+ }
+ man := &Manager{
+ Prompt: AcceptTOS,
+ Client: &acme.Client{
+ Key: key,
+ DirectoryURL: ts.URL,
+ },
+ }
+ defer man.stopRenew()
+ hello := &tls.ClientHelloInfo{ServerName: example}
+ if _, err := man.GetCertificate(hello); err == nil {
+ t.Error("GetCertificate: err is nil")
+ }
+ select {
+ case <-time.After(5 * time.Second):
+ t.Errorf("took too long to remove the %q state", example)
+ case <-done:
+ man.stateMu.Lock()
+ defer man.stateMu.Unlock()
+ if v, exist := man.state[example]; exist {
+ t.Errorf("state exists for %q: %+v", example, v)
+ }
+ }
+}
+
// startACMEServerStub runs an ACME server
// The domain argument is the expected domain name of a certificate request.
func startACMEServerStub(t *testing.T, man *Manager, domain string) (url string, finish func()) {
@@ -478,3 +560,47 @@ func TestValidCert(t *testing.T) {
}
}
}
+
+type cacheGetFunc func(ctx context.Context, key string) ([]byte, error)
+
+func (f cacheGetFunc) Get(ctx context.Context, key string) ([]byte, error) {
+ return f(ctx, key)
+}
+
+func (f cacheGetFunc) Put(ctx context.Context, key string, data []byte) error {
+ return fmt.Errorf("unsupported Put of %q = %q", key, data)
+}
+
+func (f cacheGetFunc) Delete(ctx context.Context, key string) error {
+ return fmt.Errorf("unsupported Delete of %q", key)
+}
+
+func TestManagerGetCertificateBogusSNI(t *testing.T) {
+ m := Manager{
+ Prompt: AcceptTOS,
+ Cache: cacheGetFunc(func(ctx context.Context, key string) ([]byte, error) {
+ return nil, fmt.Errorf("cache.Get of %s", key)
+ }),
+ }
+ tests := []struct {
+ name string
+ wantErr string
+ }{
+ {"foo.com", "cache.Get of foo.com"},
+ {"foo.com.", "cache.Get of foo.com"},
+ {`a\b.com`, "acme/autocert: server name contains invalid character"},
+ {`a/b.com`, "acme/autocert: server name contains invalid character"},
+ {"", "acme/autocert: missing server name"},
+ {"foo", "acme/autocert: server name component count invalid"},
+ {".foo", "acme/autocert: server name component count invalid"},
+ {"foo.", "acme/autocert: server name component count invalid"},
+ {"fo.o", "cache.Get of fo.o"},
+ }
+ for _, tt := range tests {
+ _, err := m.GetCertificate(&tls.ClientHelloInfo{ServerName: tt.name})
+ got := fmt.Sprint(err)
+ if got != tt.wantErr {
+ t.Errorf("GetCertificate(SNI = %q) = %q; want %q", tt.name, got, tt.wantErr)
+ }
+ }
+}
diff --git a/vendor/golang.org/x/crypto/acme/jws.go b/vendor/golang.org/x/crypto/acme/jws.go
index 49ba313ca..6cbca25de 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/jws_test.go b/vendor/golang.org/x/crypto/acme/jws_test.go
index 1def87397..0ff0fb5a3 100644
--- a/vendor/golang.org/x/crypto/acme/jws_test.go
+++ b/vendor/golang.org/x/crypto/acme/jws_test.go
@@ -12,11 +12,13 @@ import (
"encoding/base64"
"encoding/json"
"encoding/pem"
+ "fmt"
"math/big"
"testing"
)
-const testKeyPEM = `
+const (
+ testKeyPEM = `
-----BEGIN RSA PRIVATE KEY-----
MIIEowIBAAKCAQEA4xgZ3eRPkwoRvy7qeRUbmMDe0V+xH9eWLdu0iheeLlrmD2mq
WXfP9IeSKApbn34g8TuAS9g5zhq8ELQ3kmjr+KV86GAMgI6VAcGlq3QrzpTCf/30
@@ -46,10 +48,9 @@ EQeIP6dZtv8IMgtGIb91QX9pXvP0aznzQKwYIA8nZgoENCPfiMTPiEDT9e/0lObO
-----END RSA PRIVATE KEY-----
`
-// This thumbprint is for the testKey defined above.
-const testKeyThumbprint = "6nicxzh6WETQlrvdchkz-U3e3DOQZ4heJKU63rfqMqQ"
+ // This thumbprint is for the testKey defined above.
+ testKeyThumbprint = "6nicxzh6WETQlrvdchkz-U3e3DOQZ4heJKU63rfqMqQ"
-const (
// openssl ecparam -name secp256k1 -genkey -noout
testKeyECPEM = `
-----BEGIN EC PRIVATE KEY-----
@@ -58,39 +59,78 @@ AwEHoUQDQgAE5lhEug5xK4xBDZ2nAbaxLtaLiv85bxJ7ePd1dkO23HThqIrvawF5
QAaS/RNouybCiRhRjI3EaxLkQwgrCw0gqQ==
-----END EC PRIVATE KEY-----
`
- // 1. opnessl ec -in key.pem -noout -text
+ // openssl ecparam -name secp384r1 -genkey -noout
+ testKeyEC384PEM = `
+-----BEGIN EC PRIVATE KEY-----
+MIGkAgEBBDAQ4lNtXRORWr1bgKR1CGysr9AJ9SyEk4jiVnlUWWUChmSNL+i9SLSD
+Oe/naPqXJ6CgBwYFK4EEACKhZANiAAQzKtj+Ms0vHoTX5dzv3/L5YMXOWuI5UKRj
+JigpahYCqXD2BA1j0E/2xt5vlPf+gm0PL+UHSQsCokGnIGuaHCsJAp3ry0gHQEke
+WYXapUUFdvaK1R2/2hn5O+eiQM8YzCg=
+-----END EC PRIVATE KEY-----
+`
+ // openssl ecparam -name secp521r1 -genkey -noout
+ testKeyEC512PEM = `
+-----BEGIN EC PRIVATE KEY-----
+MIHcAgEBBEIBSNZKFcWzXzB/aJClAb305ibalKgtDA7+70eEkdPt28/3LZMM935Z
+KqYHh/COcxuu3Kt8azRAUz3gyr4zZKhlKUSgBwYFK4EEACOhgYkDgYYABAHUNKbx
+7JwC7H6pa2sV0tERWhHhB3JmW+OP6SUgMWryvIKajlx73eS24dy4QPGrWO9/ABsD
+FqcRSkNVTXnIv6+0mAF25knqIBIg5Q8M9BnOu9GGAchcwt3O7RDHmqewnJJDrbjd
+GGnm6rb+NnWR9DIopM0nKNkToWoF/hzopxu4Ae/GsQ==
+-----END EC PRIVATE KEY-----
+`
+ // 1. openssl ec -in key.pem -noout -text
// 2. remove first byte, 04 (the header); the rest is X and Y
- // 3. covert each with: echo <val> | xxd -r -p | base64 | tr -d '=' | tr '/+' '_-'
- testKeyECPubX = "5lhEug5xK4xBDZ2nAbaxLtaLiv85bxJ7ePd1dkO23HQ"
- testKeyECPubY = "4aiK72sBeUAGkv0TaLsmwokYUYyNxGsS5EMIKwsNIKk"
+ // 3. convert each with: echo <val> | xxd -r -p | base64 -w 100 | tr -d '=' | tr '/+' '_-'
+ testKeyECPubX = "5lhEug5xK4xBDZ2nAbaxLtaLiv85bxJ7ePd1dkO23HQ"
+ testKeyECPubY = "4aiK72sBeUAGkv0TaLsmwokYUYyNxGsS5EMIKwsNIKk"
+ testKeyEC384PubX = "MyrY_jLNLx6E1-Xc79_y-WDFzlriOVCkYyYoKWoWAqlw9gQNY9BP9sbeb5T3_oJt"
+ testKeyEC384PubY = "Dy_lB0kLAqJBpyBrmhwrCQKd68tIB0BJHlmF2qVFBXb2itUdv9oZ-TvnokDPGMwo"
+ testKeyEC512PubX = "AdQ0pvHsnALsfqlraxXS0RFaEeEHcmZb44_pJSAxavK8gpqOXHvd5Lbh3LhA8atY738AGwMWpxFKQ1VNeci_r7SY"
+ testKeyEC512PubY = "AXbmSeogEiDlDwz0Gc670YYByFzC3c7tEMeap7CckkOtuN0Yaebqtv42dZH0MiikzSco2ROhagX-HOinG7gB78ax"
+
// echo -n '{"crv":"P-256","kty":"EC","x":"<testKeyECPubX>","y":"<testKeyECPubY>"}' | \
// openssl dgst -binary -sha256 | base64 | tr -d '=' | tr '/+' '_-'
testKeyECThumbprint = "zedj-Bd1Zshp8KLePv2MB-lJ_Hagp7wAwdkA0NUTniU"
)
var (
- testKey *rsa.PrivateKey
- testKeyEC *ecdsa.PrivateKey
+ testKey *rsa.PrivateKey
+ testKeyEC *ecdsa.PrivateKey
+ testKeyEC384 *ecdsa.PrivateKey
+ testKeyEC512 *ecdsa.PrivateKey
)
func init() {
- d, _ := pem.Decode([]byte(testKeyPEM))
+ testKey = parseRSA(testKeyPEM, "testKeyPEM")
+ testKeyEC = parseEC(testKeyECPEM, "testKeyECPEM")
+ testKeyEC384 = parseEC(testKeyEC384PEM, "testKeyEC384PEM")
+ testKeyEC512 = parseEC(testKeyEC512PEM, "testKeyEC512PEM")
+}
+
+func decodePEM(s, name string) []byte {
+ d, _ := pem.Decode([]byte(s))
if d == nil {
- panic("no block found in testKeyPEM")
+ panic("no block found in " + name)
}
- var err error
- testKey, err = x509.ParsePKCS1PrivateKey(d.Bytes)
+ return d.Bytes
+}
+
+func parseRSA(s, name string) *rsa.PrivateKey {
+ b := decodePEM(s, name)
+ k, err := x509.ParsePKCS1PrivateKey(b)
if err != nil {
- panic(err.Error())
+ panic(fmt.Sprintf("%s: %v", name, err))
}
+ return k
+}
- if d, _ = pem.Decode([]byte(testKeyECPEM)); d == nil {
- panic("no block found in testKeyECPEM")
- }
- testKeyEC, err = x509.ParseECPrivateKey(d.Bytes)
+func parseEC(s, name string) *ecdsa.PrivateKey {
+ b := decodePEM(s, name)
+ k, err := x509.ParseECPrivateKey(b)
if err != nil {
- panic(err.Error())
+ panic(fmt.Sprintf("%s: %v", name, err))
}
+ return k
}
func TestJWSEncodeJSON(t *testing.T) {
@@ -141,50 +181,63 @@ func TestJWSEncodeJSON(t *testing.T) {
}
func TestJWSEncodeJSONEC(t *testing.T) {
- claims := struct{ Msg string }{"Hello JWS"}
-
- b, err := jwsEncodeJSON(claims, testKeyEC, "nonce")
- if err != nil {
- t.Fatal(err)
- }
- var jws struct{ Protected, Payload, Signature string }
- if err := json.Unmarshal(b, &jws); err != nil {
- t.Fatal(err)
+ tt := []struct {
+ key *ecdsa.PrivateKey
+ x, y string
+ alg, crv string
+ }{
+ {testKeyEC, testKeyECPubX, testKeyECPubY, "ES256", "P-256"},
+ {testKeyEC384, testKeyEC384PubX, testKeyEC384PubY, "ES384", "P-384"},
+ {testKeyEC512, testKeyEC512PubX, testKeyEC512PubY, "ES512", "P-521"},
}
+ for i, test := range tt {
+ claims := struct{ Msg string }{"Hello JWS"}
+ b, err := jwsEncodeJSON(claims, test.key, "nonce")
+ if err != nil {
+ t.Errorf("%d: %v", i, err)
+ continue
+ }
+ var jws struct{ Protected, Payload, Signature string }
+ if err := json.Unmarshal(b, &jws); err != nil {
+ t.Errorf("%d: %v", i, err)
+ continue
+ }
- if b, err = base64.RawURLEncoding.DecodeString(jws.Protected); err != nil {
- t.Fatalf("jws.Protected: %v", err)
- }
- var head struct {
- Alg string
- Nonce string
- JWK struct {
- Crv string
- Kty string
- X string
- Y string
- } `json:"jwk"`
- }
- if err := json.Unmarshal(b, &head); err != nil {
- t.Fatalf("jws.Protected: %v", err)
- }
- if head.Alg != "ES256" {
- t.Errorf("head.Alg = %q; want ES256", head.Alg)
- }
- if head.Nonce != "nonce" {
- t.Errorf("head.Nonce = %q; want nonce", head.Nonce)
- }
- if head.JWK.Crv != "P-256" {
- t.Errorf("head.JWK.Crv = %q; want P-256", head.JWK.Crv)
- }
- if head.JWK.Kty != "EC" {
- t.Errorf("head.JWK.Kty = %q; want EC", head.JWK.Kty)
- }
- if head.JWK.X != testKeyECPubX {
- t.Errorf("head.JWK.X = %q; want %q", head.JWK.X, testKeyECPubX)
- }
- if head.JWK.Y != testKeyECPubY {
- t.Errorf("head.JWK.Y = %q; want %q", head.JWK.Y, testKeyECPubY)
+ b, err = base64.RawURLEncoding.DecodeString(jws.Protected)
+ if err != nil {
+ t.Errorf("%d: jws.Protected: %v", i, err)
+ }
+ var head struct {
+ Alg string
+ Nonce string
+ JWK struct {
+ Crv string
+ Kty string
+ X string
+ Y string
+ } `json:"jwk"`
+ }
+ if err := json.Unmarshal(b, &head); err != nil {
+ t.Errorf("%d: jws.Protected: %v", i, err)
+ }
+ if head.Alg != test.alg {
+ t.Errorf("%d: head.Alg = %q; want %q", i, head.Alg, test.alg)
+ }
+ if head.Nonce != "nonce" {
+ t.Errorf("%d: head.Nonce = %q; want nonce", i, head.Nonce)
+ }
+ if head.JWK.Crv != test.crv {
+ t.Errorf("%d: head.JWK.Crv = %q; want %q", i, head.JWK.Crv, test.crv)
+ }
+ if head.JWK.Kty != "EC" {
+ t.Errorf("%d: head.JWK.Kty = %q; want EC", i, head.JWK.Kty)
+ }
+ if head.JWK.X != test.x {
+ t.Errorf("%d: head.JWK.X = %q; want %q", i, head.JWK.X, test.x)
+ }
+ if head.JWK.Y != test.y {
+ t.Errorf("%d: head.JWK.Y = %q; want %q", i, head.JWK.Y, test.y)
+ }
}
}
diff --git a/vendor/golang.org/x/crypto/acme/types.go b/vendor/golang.org/x/crypto/acme/types.go
index 0513b2e55..ab4de0b88 100644
--- a/vendor/golang.org/x/crypto/acme/types.go
+++ b/vendor/golang.org/x/crypto/acme/types.go
@@ -1,9 +1,15 @@
+// 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 (
"errors"
"fmt"
"net/http"
+ "strings"
+ "time"
)
// ACME server response statuses used to describe Authorization and Challenge states.
@@ -33,14 +39,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 +53,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 +61,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 +163,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 +177,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 +239,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 +271,25 @@ 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,
+ }
+}
diff --git a/vendor/golang.org/x/crypto/acme/types_test.go b/vendor/golang.org/x/crypto/acme/types_test.go
new file mode 100644
index 000000000..a7553e6b7
--- /dev/null
+++ b/vendor/golang.org/x/crypto/acme/types_test.go
@@ -0,0 +1,63 @@
+// Copyright 2017 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 (
+ "errors"
+ "net/http"
+ "testing"
+ "time"
+)
+
+func TestRateLimit(t *testing.T) {
+ now := time.Date(2017, 04, 27, 10, 0, 0, 0, time.UTC)
+ f := timeNow
+ defer func() { timeNow = f }()
+ timeNow = func() time.Time { return now }
+
+ h120, hTime := http.Header{}, http.Header{}
+ h120.Set("Retry-After", "120")
+ hTime.Set("Retry-After", "Tue Apr 27 11:00:00 2017")
+
+ err1 := &Error{
+ ProblemType: "urn:ietf:params:acme:error:nolimit",
+ Header: h120,
+ }
+ err2 := &Error{
+ ProblemType: "urn:ietf:params:acme:error:rateLimited",
+ Header: h120,
+ }
+ err3 := &Error{
+ ProblemType: "urn:ietf:params:acme:error:rateLimited",
+ Header: nil,
+ }
+ err4 := &Error{
+ ProblemType: "urn:ietf:params:acme:error:rateLimited",
+ Header: hTime,
+ }
+
+ tt := []struct {
+ err error
+ res time.Duration
+ ok bool
+ }{
+ {nil, 0, false},
+ {errors.New("dummy"), 0, false},
+ {err1, 0, false},
+ {err2, 2 * time.Minute, true},
+ {err3, 0, true},
+ {err4, time.Hour, true},
+ }
+ for i, test := range tt {
+ res, ok := RateLimit(test.err)
+ if ok != test.ok {
+ t.Errorf("%d: RateLimit(%+v): ok = %v; want %v", i, test.err, ok, test.ok)
+ continue
+ }
+ if res != test.res {
+ t.Errorf("%d: RateLimit(%+v) = %v; want %v", i, test.err, res, test.res)
+ }
+ }
+}