glide up
This commit is contained in:
203
vendor/golang.org/x/crypto/acme/acme.go
generated
vendored
203
vendor/golang.org/x/crypto/acme/acme.go
generated
vendored
@@ -15,6 +15,7 @@ package acme
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto"
|
||||
"crypto/ecdsa"
|
||||
"crypto/elliptic"
|
||||
@@ -36,9 +37,6 @@ import (
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"golang.org/x/net/context"
|
||||
"golang.org/x/net/context/ctxhttp"
|
||||
)
|
||||
|
||||
// LetsEncryptURL is the Directory endpoint of Let's Encrypt CA.
|
||||
@@ -133,7 +131,7 @@ func (c *Client) Discover(ctx context.Context) (Directory, error) {
|
||||
if dirURL == "" {
|
||||
dirURL = LetsEncryptURL
|
||||
}
|
||||
res, err := ctxhttp.Get(ctx, c.HTTPClient, dirURL)
|
||||
res, err := c.get(ctx, dirURL)
|
||||
if err != nil {
|
||||
return Directory{}, err
|
||||
}
|
||||
@@ -154,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{
|
||||
@@ -200,7 +198,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
|
||||
}
|
||||
@@ -216,7 +214,7 @@ func (c *Client) CreateCert(ctx context.Context, csr []byte, exp time.Duration,
|
||||
return cert, curl, err
|
||||
}
|
||||
// slurp issued cert and CA chain, if requested
|
||||
cert, err := responseCert(ctx, c.HTTPClient, res, bundle)
|
||||
cert, err := c.responseCert(ctx, res, bundle)
|
||||
return cert, curl, err
|
||||
}
|
||||
|
||||
@@ -231,13 +229,13 @@ func (c *Client) CreateCert(ctx context.Context, csr []byte, exp time.Duration,
|
||||
// and has expected features.
|
||||
func (c *Client) FetchCert(ctx context.Context, url string, bundle bool) ([][]byte, error) {
|
||||
for {
|
||||
res, err := ctxhttp.Get(ctx, c.HTTPClient, url)
|
||||
res, err := c.get(ctx, url)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer res.Body.Close()
|
||||
if res.StatusCode == http.StatusOK {
|
||||
return responseCert(ctx, c.HTTPClient, res, bundle)
|
||||
return c.responseCert(ctx, res, bundle)
|
||||
}
|
||||
if res.StatusCode > 299 {
|
||||
return nil, responseError(res)
|
||||
@@ -275,7 +273,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
|
||||
}
|
||||
@@ -363,7 +361,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
|
||||
}
|
||||
@@ -387,7 +385,7 @@ func (c *Client) Authorize(ctx context.Context, domain string) (*Authorization,
|
||||
// If a caller needs to poll an authorization until its status is final,
|
||||
// see the WaitAuthorization method.
|
||||
func (c *Client) GetAuthorization(ctx context.Context, url string) (*Authorization, error) {
|
||||
res, err := ctxhttp.Get(ctx, c.HTTPClient, url)
|
||||
res, err := c.get(ctx, url)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -421,7 +419,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
|
||||
}
|
||||
@@ -438,25 +436,11 @@ 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 := ctxhttp.Get(ctx, c.HTTPClient, url)
|
||||
res, err := c.get(ctx, url)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -481,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
|
||||
@@ -493,7 +477,7 @@ func (c *Client) WaitAuthorization(ctx context.Context, url string) (*Authorizat
|
||||
//
|
||||
// A client typically polls a challenge status using this method.
|
||||
func (c *Client) GetChallenge(ctx context.Context, url string) (*Challenge, error) {
|
||||
res, err := ctxhttp.Get(ctx, c.HTTPClient, url)
|
||||
res, err := c.get(ctx, url)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -527,7 +511,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
|
||||
}
|
||||
@@ -660,7 +644,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
|
||||
}
|
||||
@@ -697,6 +681,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) {
|
||||
@@ -708,7 +726,7 @@ func (c *Client) postJWS(ctx context.Context, key crypto.Signer, url string, bod
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res, err := ctxhttp.Post(ctx, c.HTTPClient, url, "application/jose+json", bytes.NewReader(b))
|
||||
res, err := c.post(ctx, url, "application/jose+json", bytes.NewReader(b))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -722,7 +740,7 @@ func (c *Client) popNonce(ctx context.Context, url string) (string, error) {
|
||||
c.noncesMu.Lock()
|
||||
defer c.noncesMu.Unlock()
|
||||
if len(c.nonces) == 0 {
|
||||
return fetchNonce(ctx, c.HTTPClient, url)
|
||||
return c.fetchNonce(ctx, url)
|
||||
}
|
||||
var nonce string
|
||||
for nonce = range c.nonces {
|
||||
@@ -732,6 +750,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)
|
||||
@@ -749,8 +774,58 @@ func (c *Client) addNonce(h http.Header) {
|
||||
c.nonces[v] = struct{}{}
|
||||
}
|
||||
|
||||
func fetchNonce(ctx context.Context, client *http.Client, url string) (string, error) {
|
||||
resp, err := ctxhttp.Head(ctx, client, url)
|
||||
func (c *Client) httpClient() *http.Client {
|
||||
if c.HTTPClient != nil {
|
||||
return c.HTTPClient
|
||||
}
|
||||
return http.DefaultClient
|
||||
}
|
||||
|
||||
func (c *Client) get(ctx context.Context, urlStr string) (*http.Response, error) {
|
||||
req, err := http.NewRequest("GET", urlStr, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return c.do(ctx, req)
|
||||
}
|
||||
|
||||
func (c *Client) head(ctx context.Context, urlStr string) (*http.Response, error) {
|
||||
req, err := http.NewRequest("HEAD", urlStr, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return c.do(ctx, req)
|
||||
}
|
||||
|
||||
func (c *Client) post(ctx context.Context, urlStr, contentType string, body io.Reader) (*http.Response, error) {
|
||||
req, err := http.NewRequest("POST", urlStr, body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req.Header.Set("Content-Type", contentType)
|
||||
return c.do(ctx, req)
|
||||
}
|
||||
|
||||
func (c *Client) do(ctx context.Context, req *http.Request) (*http.Response, error) {
|
||||
res, err := c.httpClient().Do(req.WithContext(ctx))
|
||||
if err != nil {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
// Prefer the unadorned context error.
|
||||
// (The acme package had tests assuming this, previously from ctxhttp's
|
||||
// behavior, predating net/http supporting contexts natively)
|
||||
// TODO(bradfitz): reconsider this in the future. But for now this
|
||||
// requires no test updates.
|
||||
return nil, ctx.Err()
|
||||
default:
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func (c *Client) fetchNonce(ctx context.Context, url string) (string, error) {
|
||||
resp, err := c.head(ctx, url)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
@@ -769,7 +844,7 @@ func nonceFromHeader(h http.Header) string {
|
||||
return h.Get("Replay-Nonce")
|
||||
}
|
||||
|
||||
func responseCert(ctx context.Context, client *http.Client, res *http.Response, bundle bool) ([][]byte, error) {
|
||||
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)
|
||||
@@ -793,7 +868,7 @@ func responseCert(ctx context.Context, client *http.Client, res *http.Response,
|
||||
return nil, errors.New("acme: rel=up link is too large")
|
||||
}
|
||||
for _, url := range up {
|
||||
cc, err := chainCert(ctx, client, url, 0)
|
||||
cc, err := c.chainCert(ctx, url, 0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -807,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
|
||||
@@ -823,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.
|
||||
@@ -836,12 +900,12 @@ func responseError(resp *http.Response) error {
|
||||
// if the recursion level reaches maxChainLen.
|
||||
//
|
||||
// First chainCert call starts with depth of 0.
|
||||
func chainCert(ctx context.Context, client *http.Client, url string, depth int) ([][]byte, error) {
|
||||
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 := ctxhttp.Get(ctx, client, url)
|
||||
res, err := c.get(ctx, url)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -863,7 +927,7 @@ func chainCert(ctx context.Context, client *http.Client, url string, depth int)
|
||||
return nil, errors.New("acme: certificate chain is too large")
|
||||
}
|
||||
for _, up := range uplink {
|
||||
cc, err := chainCert(ctx, client, up, depth+1)
|
||||
cc, err := c.chainCert(ctx, up, depth+1)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -893,6 +957,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.
|
||||
@@ -974,7 +1060,8 @@ func tlsChallengeCert(san []string, opt []CertOption) (tls.Certificate, error) {
|
||||
NotBefore: time.Now(),
|
||||
NotAfter: time.Now().Add(24 * time.Hour),
|
||||
BasicConstraintsValid: true,
|
||||
KeyUsage: x509.KeyUsageKeyEncipherment,
|
||||
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
|
||||
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
|
||||
}
|
||||
}
|
||||
tmpl.DNSNames = san
|
||||
|
50
vendor/golang.org/x/crypto/acme/acme_test.go
generated
vendored
50
vendor/golang.org/x/crypto/acme/acme_test.go
generated
vendored
@@ -6,6 +6,7 @@ package acme
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"crypto/tls"
|
||||
@@ -23,8 +24,6 @@ import (
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
// Decodes a JWS-encoded request and unmarshals the decoded JSON into a provided
|
||||
@@ -544,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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -981,7 +983,8 @@ func TestNonce_fetch(t *testing.T) {
|
||||
defer ts.Close()
|
||||
for ; i < len(tests); i++ {
|
||||
test := tests[i]
|
||||
n, err := fetchNonce(context.Background(), http.DefaultClient, ts.URL)
|
||||
c := &Client{}
|
||||
n, err := c.fetchNonce(context.Background(), ts.URL)
|
||||
if n != test.nonce {
|
||||
t.Errorf("%d: n=%q; want %q", i, n, test.nonce)
|
||||
}
|
||||
@@ -999,7 +1002,8 @@ func TestNonce_fetchError(t *testing.T) {
|
||||
w.WriteHeader(http.StatusTooManyRequests)
|
||||
}))
|
||||
defer ts.Close()
|
||||
_, err := fetchNonce(context.Background(), http.DefaultClient, ts.URL)
|
||||
c := &Client{}
|
||||
_, err := c.fetchNonce(context.Background(), ts.URL)
|
||||
e, ok := err.(*Error)
|
||||
if !ok {
|
||||
t.Fatalf("err is %T; want *Error", err)
|
||||
@@ -1064,6 +1068,44 @@ func TestNonce_postJWS(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestRetryPostJWS(t *testing.T) {
|
||||
var count int
|
||||
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
count++
|
||||
w.Header().Set("replay-nonce", fmt.Sprintf("nonce%d", count))
|
||||
if r.Method == "HEAD" {
|
||||
// We expect the client to do 2 head requests to fetch
|
||||
// nonces, one to start and another after getting badNonce
|
||||
return
|
||||
}
|
||||
|
||||
head, err := decodeJWSHead(r)
|
||||
if err != nil {
|
||||
t.Errorf("decodeJWSHead: %v", err)
|
||||
} else if head.Nonce == "" {
|
||||
t.Error("head.Nonce is empty")
|
||||
} else if head.Nonce == "nonce1" {
|
||||
// return a badNonce error to force the call to retry
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
w.Write([]byte(`{"type":"urn:ietf:params:acme:error:badNonce"}`))
|
||||
return
|
||||
}
|
||||
// Make client.Authorize happy; we're not testing its result.
|
||||
w.WriteHeader(http.StatusCreated)
|
||||
w.Write([]byte(`{"status":"valid"}`))
|
||||
}))
|
||||
defer ts.Close()
|
||||
|
||||
client := Client{Key: testKey, dir: &Directory{AuthzURL: ts.URL}}
|
||||
// This call will fail with badNonce, causing a retry
|
||||
if _, err := client.Authorize(context.Background(), "example.com"); err != nil {
|
||||
t.Errorf("client.Authorize 1: %v", err)
|
||||
}
|
||||
if count != 4 {
|
||||
t.Errorf("total requests count: %d; want 4", count)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLinkHeader(t *testing.T) {
|
||||
h := http.Header{"Link": {
|
||||
`<https://example.com/acme/new-authz>;rel="next"`,
|
||||
|
104
vendor/golang.org/x/crypto/acme/autocert/autocert.go
generated
vendored
104
vendor/golang.org/x/crypto/acme/autocert/autocert.go
generated
vendored
@@ -10,6 +10,7 @@ package autocert
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto"
|
||||
"crypto/ecdsa"
|
||||
"crypto/elliptic"
|
||||
@@ -30,9 +31,14 @@ import (
|
||||
"time"
|
||||
|
||||
"golang.org/x/crypto/acme"
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
// 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
|
||||
|
||||
@@ -41,8 +47,9 @@ func init() {
|
||||
pseudoRand = &lockedMathRand{rnd: mathrand.New(src)}
|
||||
}
|
||||
|
||||
// AcceptTOS always returns true to indicate the acceptance of a CA Terms of Service
|
||||
// during account registration.
|
||||
// AcceptTOS is a Manager.Prompt function that always returns true to
|
||||
// indicate acceptance of the CA's Terms of Service during account
|
||||
// registration.
|
||||
func AcceptTOS(tosURL string) bool { return true }
|
||||
|
||||
// HostPolicy specifies which host names the Manager is allowed to respond to.
|
||||
@@ -76,18 +83,6 @@ 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.
|
||||
type Manager struct {
|
||||
@@ -123,7 +118,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
|
||||
@@ -173,10 +168,23 @@ 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()
|
||||
|
||||
// check whether this is a token cert requested for TLS-SNI challenge
|
||||
if strings.HasSuffix(name, ".acme.invalid") {
|
||||
@@ -185,7 +193,7 @@ func (m *Manager) GetCertificate(hello *tls.ClientHelloInfo) (*tls.Certificate,
|
||||
if cert := m.tokenCert[name]; cert != nil {
|
||||
return cert, nil
|
||||
}
|
||||
if cert, err := m.cacheGet(name); err == nil {
|
||||
if cert, err := m.cacheGet(ctx, name); err == nil {
|
||||
return cert, nil
|
||||
}
|
||||
// TODO: cache error results?
|
||||
@@ -194,7 +202,7 @@ func (m *Manager) GetCertificate(hello *tls.ClientHelloInfo) (*tls.Certificate,
|
||||
|
||||
// regular domain
|
||||
name = strings.TrimSuffix(name, ".") // golang.org/issue/18114
|
||||
cert, err := m.cert(name)
|
||||
cert, err := m.cert(ctx, name)
|
||||
if err == nil {
|
||||
return cert, nil
|
||||
}
|
||||
@@ -203,7 +211,6 @@ func (m *Manager) GetCertificate(hello *tls.ClientHelloInfo) (*tls.Certificate,
|
||||
}
|
||||
|
||||
// first-time
|
||||
ctx := context.Background() // TODO: use a deadline?
|
||||
if err := m.hostPolicy()(ctx, name); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -211,14 +218,14 @@ func (m *Manager) GetCertificate(hello *tls.ClientHelloInfo) (*tls.Certificate,
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
m.cachePut(name, cert)
|
||||
m.cachePut(ctx, name, cert)
|
||||
return cert, nil
|
||||
}
|
||||
|
||||
// cert returns an existing certificate either from m.state or cache.
|
||||
// If a certificate is found in cache but not in m.state, the latter will be filled
|
||||
// with the cached value.
|
||||
func (m *Manager) cert(name string) (*tls.Certificate, error) {
|
||||
func (m *Manager) cert(ctx context.Context, name string) (*tls.Certificate, error) {
|
||||
m.stateMu.Lock()
|
||||
if s, ok := m.state[name]; ok {
|
||||
m.stateMu.Unlock()
|
||||
@@ -227,7 +234,7 @@ func (m *Manager) cert(name string) (*tls.Certificate, error) {
|
||||
return s.tlscert()
|
||||
}
|
||||
defer m.stateMu.Unlock()
|
||||
cert, err := m.cacheGet(name)
|
||||
cert, err := m.cacheGet(ctx, name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -249,12 +256,11 @@ func (m *Manager) cert(name string) (*tls.Certificate, error) {
|
||||
}
|
||||
|
||||
// cacheGet always returns a valid certificate, or an error otherwise.
|
||||
func (m *Manager) cacheGet(domain string) (*tls.Certificate, error) {
|
||||
// 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
|
||||
}
|
||||
// TODO: might want to define a cache timeout on m
|
||||
ctx := context.Background()
|
||||
data, err := m.Cache.Get(ctx, domain)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -263,7 +269,7 @@ func (m *Manager) cacheGet(domain string) (*tls.Certificate, error) {
|
||||
// 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 {
|
||||
@@ -281,13 +287,14 @@ func (m *Manager) cacheGet(domain string) (*tls.Certificate, error) {
|
||||
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,
|
||||
@@ -297,7 +304,7 @@ func (m *Manager) cacheGet(domain string) (*tls.Certificate, error) {
|
||||
return tlscert, nil
|
||||
}
|
||||
|
||||
func (m *Manager) cachePut(domain string, tlscert *tls.Certificate) error {
|
||||
func (m *Manager) cachePut(ctx context.Context, domain string, tlscert *tls.Certificate) error {
|
||||
if m.Cache == nil {
|
||||
return nil
|
||||
}
|
||||
@@ -329,8 +336,6 @@ func (m *Manager) cachePut(domain string, tlscert *tls.Certificate) error {
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: might want to define a cache timeout on m
|
||||
ctx := context.Background()
|
||||
return m.Cache.Put(ctx, domain, buf.Bytes())
|
||||
}
|
||||
|
||||
@@ -370,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
|
||||
@@ -418,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
|
||||
}
|
||||
@@ -494,7 +515,7 @@ func (m *Manager) verify(ctx context.Context, domain string) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
m.putTokenCert(name, &cert)
|
||||
m.putTokenCert(ctx, name, &cert)
|
||||
defer func() {
|
||||
// verification has ended at this point
|
||||
// don't need token cert anymore
|
||||
@@ -512,14 +533,14 @@ func (m *Manager) verify(ctx context.Context, domain string) error {
|
||||
|
||||
// putTokenCert stores the cert under the named key in both m.tokenCert map
|
||||
// and m.Cache.
|
||||
func (m *Manager) putTokenCert(name string, cert *tls.Certificate) {
|
||||
func (m *Manager) putTokenCert(ctx context.Context, name string, cert *tls.Certificate) {
|
||||
m.tokenCertMu.Lock()
|
||||
defer m.tokenCertMu.Unlock()
|
||||
if m.tokenCert == nil {
|
||||
m.tokenCert = make(map[string]*tls.Certificate)
|
||||
}
|
||||
m.tokenCert[name] = cert
|
||||
m.cachePut(name, cert)
|
||||
m.cachePut(ctx, name, cert)
|
||||
}
|
||||
|
||||
// deleteTokenCert removes the token certificate for the specified domain name
|
||||
@@ -644,10 +665,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.
|
||||
@@ -789,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) {}
|
||||
)
|
||||
|
226
vendor/golang.org/x/crypto/acme/autocert/autocert_test.go
generated
vendored
226
vendor/golang.org/x/crypto/acme/autocert/autocert_test.go
generated
vendored
@@ -5,6 +5,7 @@
|
||||
package autocert
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto"
|
||||
"crypto/ecdsa"
|
||||
"crypto/elliptic"
|
||||
@@ -27,7 +28,6 @@ import (
|
||||
"time"
|
||||
|
||||
"golang.org/x/crypto/acme"
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
var discoTmpl = template.Must(template.New("disco").Parse(`{
|
||||
@@ -150,7 +150,7 @@ func TestGetCertificate_ForceRSA(t *testing.T) {
|
||||
hello := &tls.ClientHelloInfo{ServerName: "example.org"}
|
||||
testGetCertificate(t, man, "example.org", hello)
|
||||
|
||||
cert, err := man.cacheGet("example.org")
|
||||
cert, err := man.cacheGet(context.Background(), "example.org")
|
||||
if err != nil {
|
||||
t.Fatalf("man.cacheGet: %v", err)
|
||||
}
|
||||
@@ -159,9 +159,110 @@ func TestGetCertificate_ForceRSA(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// tests man.GetCertificate flow using the provided hello argument.
|
||||
func TestGetCertificate_nilPrompt(t *testing.T) {
|
||||
man := &Manager{}
|
||||
defer man.stopRenew()
|
||||
url, finish := startACMEServerStub(t, man, "example.org")
|
||||
defer finish()
|
||||
key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
man.Client = &acme.Client{
|
||||
Key: key,
|
||||
DirectoryURL: url,
|
||||
}
|
||||
hello := &tls.ClientHelloInfo{ServerName: "example.org"}
|
||||
if _, err := man.GetCertificate(hello); err == nil {
|
||||
t.Error("got certificate for example.org; wanted error")
|
||||
}
|
||||
}
|
||||
|
||||
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 testGetCertificate(t *testing.T, man *Manager, domain string, hello *tls.ClientHelloInfo) {
|
||||
func startACMEServerStub(t *testing.T, man *Manager, domain string) (url string, finish func()) {
|
||||
// echo token-02 | shasum -a 256
|
||||
// then divide result in 2 parts separated by dot
|
||||
tokenCertName := "4e8eb87631187e9ff2153b56b13a4dec.13a35d002e485d60ff37354b32f665d9.token.acme.invalid"
|
||||
@@ -187,7 +288,7 @@ func testGetCertificate(t *testing.T, man *Manager, domain string, hello *tls.Cl
|
||||
// discovery
|
||||
case "/":
|
||||
if err := discoTmpl.Execute(w, ca.URL); err != nil {
|
||||
t.Fatalf("discoTmpl: %v", err)
|
||||
t.Errorf("discoTmpl: %v", err)
|
||||
}
|
||||
// client key registration
|
||||
case "/new-reg":
|
||||
@@ -197,7 +298,7 @@ func testGetCertificate(t *testing.T, man *Manager, domain string, hello *tls.Cl
|
||||
w.Header().Set("location", ca.URL+"/authz/1")
|
||||
w.WriteHeader(http.StatusCreated)
|
||||
if err := authzTmpl.Execute(w, ca.URL); err != nil {
|
||||
t.Fatalf("authzTmpl: %v", err)
|
||||
t.Errorf("authzTmpl: %v", err)
|
||||
}
|
||||
// accept tls-sni-02 challenge
|
||||
case "/challenge/2":
|
||||
@@ -215,14 +316,14 @@ func testGetCertificate(t *testing.T, man *Manager, domain string, hello *tls.Cl
|
||||
b, _ := base64.RawURLEncoding.DecodeString(req.CSR)
|
||||
csr, err := x509.ParseCertificateRequest(b)
|
||||
if err != nil {
|
||||
t.Fatalf("new-cert: CSR: %v", err)
|
||||
t.Errorf("new-cert: CSR: %v", err)
|
||||
}
|
||||
if csr.Subject.CommonName != domain {
|
||||
t.Errorf("CommonName in CSR = %q; want %q", csr.Subject.CommonName, domain)
|
||||
}
|
||||
der, err := dummyCert(csr.PublicKey, domain)
|
||||
if err != nil {
|
||||
t.Fatalf("new-cert: dummyCert: %v", err)
|
||||
t.Errorf("new-cert: dummyCert: %v", err)
|
||||
}
|
||||
chainUp := fmt.Sprintf("<%s/ca-cert>; rel=up", ca.URL)
|
||||
w.Header().Set("link", chainUp)
|
||||
@@ -232,14 +333,51 @@ func testGetCertificate(t *testing.T, man *Manager, domain string, hello *tls.Cl
|
||||
case "/ca-cert":
|
||||
der, err := dummyCert(nil, "ca")
|
||||
if err != nil {
|
||||
t.Fatalf("ca-cert: dummyCert: %v", err)
|
||||
t.Errorf("ca-cert: dummyCert: %v", err)
|
||||
}
|
||||
w.Write(der)
|
||||
default:
|
||||
t.Errorf("unrecognized r.URL.Path: %s", r.URL.Path)
|
||||
}
|
||||
}))
|
||||
defer ca.Close()
|
||||
finish = func() {
|
||||
ca.Close()
|
||||
|
||||
// make sure token cert was removed
|
||||
cancel := make(chan struct{})
|
||||
done := make(chan struct{})
|
||||
go func() {
|
||||
defer close(done)
|
||||
tick := time.NewTicker(100 * time.Millisecond)
|
||||
defer tick.Stop()
|
||||
for {
|
||||
hello := &tls.ClientHelloInfo{ServerName: tokenCertName}
|
||||
if _, err := man.GetCertificate(hello); err != nil {
|
||||
return
|
||||
}
|
||||
select {
|
||||
case <-tick.C:
|
||||
case <-cancel:
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
select {
|
||||
case <-done:
|
||||
case <-time.After(5 * time.Second):
|
||||
close(cancel)
|
||||
t.Error("token cert was not removed")
|
||||
<-done
|
||||
}
|
||||
}
|
||||
return ca.URL, finish
|
||||
}
|
||||
|
||||
// tests man.GetCertificate flow using the provided hello argument.
|
||||
// The domain argument is the expected domain name of a certificate request.
|
||||
func testGetCertificate(t *testing.T, man *Manager, domain string, hello *tls.ClientHelloInfo) {
|
||||
url, finish := startACMEServerStub(t, man, domain)
|
||||
defer finish()
|
||||
|
||||
// use EC key to run faster on 386
|
||||
key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
||||
@@ -248,7 +386,7 @@ func testGetCertificate(t *testing.T, man *Manager, domain string, hello *tls.Cl
|
||||
}
|
||||
man.Client = &acme.Client{
|
||||
Key: key,
|
||||
DirectoryURL: ca.URL,
|
||||
DirectoryURL: url,
|
||||
}
|
||||
|
||||
// simulate tls.Config.GetCertificate
|
||||
@@ -279,23 +417,6 @@ func testGetCertificate(t *testing.T, man *Manager, domain string, hello *tls.Cl
|
||||
t.Errorf("cert.DNSNames = %v; want %q", cert.DNSNames, domain)
|
||||
}
|
||||
|
||||
// make sure token cert was removed
|
||||
done = make(chan struct{})
|
||||
go func() {
|
||||
for {
|
||||
hello := &tls.ClientHelloInfo{ServerName: tokenCertName}
|
||||
if _, err := man.GetCertificate(hello); err != nil {
|
||||
break
|
||||
}
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
}
|
||||
close(done)
|
||||
}()
|
||||
select {
|
||||
case <-time.After(5 * time.Second):
|
||||
t.Error("token cert was not removed")
|
||||
case <-done:
|
||||
}
|
||||
}
|
||||
|
||||
func TestAccountKeyCache(t *testing.T) {
|
||||
@@ -335,10 +456,11 @@ func TestCache(t *testing.T) {
|
||||
|
||||
man := &Manager{Cache: newMemCache()}
|
||||
defer man.stopRenew()
|
||||
if err := man.cachePut("example.org", tlscert); err != nil {
|
||||
ctx := context.Background()
|
||||
if err := man.cachePut(ctx, "example.org", tlscert); err != nil {
|
||||
t.Fatalf("man.cachePut: %v", err)
|
||||
}
|
||||
res, err := man.cacheGet("example.org")
|
||||
res, err := man.cacheGet(ctx, "example.org")
|
||||
if err != nil {
|
||||
t.Fatalf("man.cacheGet: %v", err)
|
||||
}
|
||||
@@ -438,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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
14
vendor/golang.org/x/crypto/acme/autocert/cache.go
generated
vendored
14
vendor/golang.org/x/crypto/acme/autocert/cache.go
generated
vendored
@@ -5,12 +5,11 @@
|
||||
package autocert
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
// ErrCacheMiss is returned when a certificate is not found in cache.
|
||||
@@ -78,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():
|
||||
|
3
vendor/golang.org/x/crypto/acme/autocert/cache_test.go
generated
vendored
3
vendor/golang.org/x/crypto/acme/autocert/cache_test.go
generated
vendored
@@ -5,13 +5,12 @@
|
||||
package autocert
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
// make sure DirCache satisfies Cache interface
|
||||
|
34
vendor/golang.org/x/crypto/acme/autocert/example_test.go
generated
vendored
Normal file
34
vendor/golang.org/x/crypto/acme/autocert/example_test.go
generated
vendored
Normal file
@@ -0,0 +1,34 @@
|
||||
// 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 autocert_test
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
|
||||
"golang.org/x/crypto/acme/autocert"
|
||||
)
|
||||
|
||||
func ExampleNewListener() {
|
||||
mux := http.NewServeMux()
|
||||
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
||||
fmt.Fprintf(w, "Hello, TLS user! Your config: %+v", r.TLS)
|
||||
})
|
||||
log.Fatal(http.Serve(autocert.NewListener("example.com"), mux))
|
||||
}
|
||||
|
||||
func ExampleManager() {
|
||||
m := autocert.Manager{
|
||||
Prompt: autocert.AcceptTOS,
|
||||
HostPolicy: autocert.HostWhitelist("example.org"),
|
||||
}
|
||||
s := &http.Server{
|
||||
Addr: ":https",
|
||||
TLSConfig: &tls.Config{GetCertificate: m.GetCertificate},
|
||||
}
|
||||
s.ListenAndServeTLS("", "")
|
||||
}
|
153
vendor/golang.org/x/crypto/acme/autocert/listener.go
generated
vendored
Normal file
153
vendor/golang.org/x/crypto/acme/autocert/listener.go
generated
vendored
Normal file
@@ -0,0 +1,153 @@
|
||||
// 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 autocert
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"log"
|
||||
"net"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"time"
|
||||
)
|
||||
|
||||
// NewListener returns a net.Listener that listens on the standard TLS
|
||||
// port (443) on all interfaces and returns *tls.Conn connections with
|
||||
// LetsEncrypt certificates for the provided domain or domains.
|
||||
//
|
||||
// It enables one-line HTTPS servers:
|
||||
//
|
||||
// log.Fatal(http.Serve(autocert.NewListener("example.com"), handler))
|
||||
//
|
||||
// NewListener is a convenience function for a common configuration.
|
||||
// More complex or custom configurations can use the autocert.Manager
|
||||
// type instead.
|
||||
//
|
||||
// Use of this function implies acceptance of the LetsEncrypt Terms of
|
||||
// Service. If domains is not empty, the provided domains are passed
|
||||
// to HostWhitelist. If domains is empty, the listener will do
|
||||
// LetsEncrypt challenges for any requested domain, which is not
|
||||
// recommended.
|
||||
//
|
||||
// Certificates are cached in a "golang-autocert" directory under an
|
||||
// operating system-specific cache or temp directory. This may not
|
||||
// be suitable for servers spanning multiple machines.
|
||||
//
|
||||
// The returned Listener also enables TCP keep-alives on the accepted
|
||||
// connections. The returned *tls.Conn are returned before their TLS
|
||||
// handshake has completed.
|
||||
func NewListener(domains ...string) net.Listener {
|
||||
m := &Manager{
|
||||
Prompt: AcceptTOS,
|
||||
}
|
||||
if len(domains) > 0 {
|
||||
m.HostPolicy = HostWhitelist(domains...)
|
||||
}
|
||||
dir := cacheDir()
|
||||
if err := os.MkdirAll(dir, 0700); err != nil {
|
||||
log.Printf("warning: autocert.NewListener not using a cache: %v", err)
|
||||
} else {
|
||||
m.Cache = DirCache(dir)
|
||||
}
|
||||
return m.Listener()
|
||||
}
|
||||
|
||||
// Listener listens on the standard TLS port (443) on all interfaces
|
||||
// and returns a net.Listener returning *tls.Conn connections.
|
||||
//
|
||||
// The returned Listener also enables TCP keep-alives on the accepted
|
||||
// connections. The returned *tls.Conn are returned before their TLS
|
||||
// handshake has completed.
|
||||
//
|
||||
// Unlike NewListener, it is the caller's responsibility to initialize
|
||||
// the Manager m's Prompt, Cache, HostPolicy, and other desired options.
|
||||
func (m *Manager) Listener() net.Listener {
|
||||
ln := &listener{
|
||||
m: m,
|
||||
conf: &tls.Config{
|
||||
GetCertificate: m.GetCertificate, // bonus: panic on nil m
|
||||
},
|
||||
}
|
||||
ln.tcpListener, ln.tcpListenErr = net.Listen("tcp", ":443")
|
||||
return ln
|
||||
}
|
||||
|
||||
type listener struct {
|
||||
m *Manager
|
||||
conf *tls.Config
|
||||
|
||||
tcpListener net.Listener
|
||||
tcpListenErr error
|
||||
}
|
||||
|
||||
func (ln *listener) Accept() (net.Conn, error) {
|
||||
if ln.tcpListenErr != nil {
|
||||
return nil, ln.tcpListenErr
|
||||
}
|
||||
conn, err := ln.tcpListener.Accept()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tcpConn := conn.(*net.TCPConn)
|
||||
|
||||
// Because Listener is a convenience function, help out with
|
||||
// this too. This is not possible for the caller to set once
|
||||
// we return a *tcp.Conn wrapping an inaccessible net.Conn.
|
||||
// If callers don't want this, they can do things the manual
|
||||
// way and tweak as needed. But this is what net/http does
|
||||
// itself, so copy that. If net/http changes, we can change
|
||||
// here too.
|
||||
tcpConn.SetKeepAlive(true)
|
||||
tcpConn.SetKeepAlivePeriod(3 * time.Minute)
|
||||
|
||||
return tls.Server(tcpConn, ln.conf), nil
|
||||
}
|
||||
|
||||
func (ln *listener) Addr() net.Addr {
|
||||
if ln.tcpListener != nil {
|
||||
return ln.tcpListener.Addr()
|
||||
}
|
||||
// net.Listen failed. Return something non-nil in case callers
|
||||
// call Addr before Accept:
|
||||
return &net.TCPAddr{IP: net.IP{0, 0, 0, 0}, Port: 443}
|
||||
}
|
||||
|
||||
func (ln *listener) Close() error {
|
||||
if ln.tcpListenErr != nil {
|
||||
return ln.tcpListenErr
|
||||
}
|
||||
return ln.tcpListener.Close()
|
||||
}
|
||||
|
||||
func homeDir() string {
|
||||
if runtime.GOOS == "windows" {
|
||||
return os.Getenv("HOMEDRIVE") + os.Getenv("HOMEPATH")
|
||||
}
|
||||
if h := os.Getenv("HOME"); h != "" {
|
||||
return h
|
||||
}
|
||||
return "/"
|
||||
}
|
||||
|
||||
func cacheDir() string {
|
||||
const base = "golang-autocert"
|
||||
switch runtime.GOOS {
|
||||
case "darwin":
|
||||
return filepath.Join(homeDir(), "Library", "Caches", base)
|
||||
case "windows":
|
||||
for _, ev := range []string{"APPDATA", "CSIDL_APPDATA", "TEMP", "TMP"} {
|
||||
if v := os.Getenv(ev); v != "" {
|
||||
return filepath.Join(v, base)
|
||||
}
|
||||
}
|
||||
// Worst case:
|
||||
return filepath.Join(homeDir(), base)
|
||||
}
|
||||
if xdg := os.Getenv("XDG_CACHE_HOME"); xdg != "" {
|
||||
return filepath.Join(xdg, base)
|
||||
}
|
||||
return filepath.Join(homeDir(), ".cache", base)
|
||||
}
|
17
vendor/golang.org/x/crypto/acme/autocert/renewal.go
generated
vendored
17
vendor/golang.org/x/crypto/acme/autocert/renewal.go
generated
vendored
@@ -5,15 +5,14 @@
|
||||
package autocert
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
// 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.
|
||||
@@ -65,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)
|
||||
@@ -83,9 +82,9 @@ func (dr *domainRenewal) renew() {
|
||||
func (dr *domainRenewal) do(ctx context.Context) (time.Duration, error) {
|
||||
// a race is likely unavoidable in a distributed environment
|
||||
// but we try nonetheless
|
||||
if tlscert, err := dr.m.cacheGet(dr.domain); err == nil {
|
||||
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
|
||||
}
|
||||
}
|
||||
@@ -103,7 +102,7 @@ func (dr *domainRenewal) do(ctx context.Context) (time.Duration, error) {
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
dr.m.cachePut(dr.domain, tlscert)
|
||||
dr.m.cachePut(ctx, dr.domain, tlscert)
|
||||
dr.m.stateMu.Lock()
|
||||
defer dr.m.stateMu.Unlock()
|
||||
// m.state is guaranteed to be non-nil at this point
|
||||
@@ -114,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
|
||||
|
7
vendor/golang.org/x/crypto/acme/autocert/renewal_test.go
generated
vendored
7
vendor/golang.org/x/crypto/acme/autocert/renewal_test.go
generated
vendored
@@ -5,6 +5,7 @@
|
||||
package autocert
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/ecdsa"
|
||||
"crypto/elliptic"
|
||||
"crypto/rand"
|
||||
@@ -31,7 +32,7 @@ func TestRenewalNext(t *testing.T) {
|
||||
expiry time.Time
|
||||
min, max time.Duration
|
||||
}{
|
||||
{now.Add(90 * 24 * time.Hour), 83*24*time.Hour - maxRandRenew, 83 * 24 * time.Hour},
|
||||
{now.Add(90 * 24 * time.Hour), 83*24*time.Hour - renewJitter, 83 * 24 * time.Hour},
|
||||
{now.Add(time.Hour), 0, 1},
|
||||
{now, 0, 1},
|
||||
{now.Add(-time.Hour), 0, 1},
|
||||
@@ -127,7 +128,7 @@ func TestRenewFromCache(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
tlscert := &tls.Certificate{PrivateKey: key, Certificate: [][]byte{cert}}
|
||||
if err := man.cachePut(domain, tlscert); err != nil {
|
||||
if err := man.cachePut(context.Background(), domain, tlscert); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
@@ -151,7 +152,7 @@ func TestRenewFromCache(t *testing.T) {
|
||||
|
||||
// ensure the new cert is cached
|
||||
after := time.Now().Add(future)
|
||||
tlscert, err := man.cacheGet(domain)
|
||||
tlscert, err := man.cacheGet(context.Background(), domain)
|
||||
if err != nil {
|
||||
t.Fatalf("man.cacheGet: %v", err)
|
||||
}
|
||||
|
2
vendor/golang.org/x/crypto/acme/jws.go
generated
vendored
2
vendor/golang.org/x/crypto/acme/jws.go
generated
vendored
@@ -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
|
||||
}
|
||||
}
|
||||
|
185
vendor/golang.org/x/crypto/acme/jws_test.go
generated
vendored
185
vendor/golang.org/x/crypto/acme/jws_test.go
generated
vendored
@@ -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))
|
||||
if d == nil {
|
||||
panic("no block found in testKeyPEM")
|
||||
}
|
||||
var err error
|
||||
testKey, err = x509.ParsePKCS1PrivateKey(d.Bytes)
|
||||
if err != nil {
|
||||
panic(err.Error())
|
||||
}
|
||||
testKey = parseRSA(testKeyPEM, "testKeyPEM")
|
||||
testKeyEC = parseEC(testKeyECPEM, "testKeyECPEM")
|
||||
testKeyEC384 = parseEC(testKeyEC384PEM, "testKeyEC384PEM")
|
||||
testKeyEC512 = parseEC(testKeyEC512PEM, "testKeyEC512PEM")
|
||||
}
|
||||
|
||||
if d, _ = pem.Decode([]byte(testKeyECPEM)); d == nil {
|
||||
panic("no block found in testKeyECPEM")
|
||||
func decodePEM(s, name string) []byte {
|
||||
d, _ := pem.Decode([]byte(s))
|
||||
if d == nil {
|
||||
panic("no block found in " + name)
|
||||
}
|
||||
testKeyEC, err = x509.ParseECPrivateKey(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
|
||||
}
|
||||
|
||||
func parseEC(s, name string) *ecdsa.PrivateKey {
|
||||
b := decodePEM(s, name)
|
||||
k, err := x509.ParseECPrivateKey(b)
|
||||
if err != nil {
|
||||
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"}
|
||||
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
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
102
vendor/golang.org/x/crypto/acme/types.go
generated
vendored
102
vendor/golang.org/x/crypto/acme/types.go
generated
vendored
@@ -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,
|
||||
}
|
||||
}
|
||||
|
63
vendor/golang.org/x/crypto/acme/types_test.go
generated
vendored
Normal file
63
vendor/golang.org/x/crypto/acme/types_test.go
generated
vendored
Normal file
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user