Copy across WIP acme v2 golang package

This commit is contained in:
James Munnelly 2018-02-09 23:52:36 +00:00
parent b3ddc60331
commit 486cd3ae18
7 changed files with 3137 additions and 0 deletions

923
third_party/crypto/acme/acme.go vendored Normal file
View File

@ -0,0 +1,923 @@
// Copyright 2015 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 provides an implementation of the
// Automatic Certificate Management Environment (ACME) spec.
// See https://tools.ietf.org/html/draft-ietf-acme-acme-09 for details.
//
// Most common scenarios will want to use autocert subdirectory instead,
// which provides automatic access to certificates from Let's Encrypt
// and any other ACME-based CA.
//
// This package is a work in progress and makes no API stability promises.
package acme
import (
"bytes"
"context"
"crypto"
"crypto/rand"
"crypto/sha256"
"encoding/base64"
"encoding/json"
"encoding/pem"
"errors"
"fmt"
"io"
"io/ioutil"
"math/big"
"net/http"
"net/url"
"strconv"
"sync"
"time"
)
// LetsEncryptURL is the Directory endpoint of Let's Encrypt CA.
const LetsEncryptURL = "https://acme-v02.api.letsencrypt.org/directory"
const (
// max length of a certificate chain
maxChainLen = 5
// max size of a certificate chain response, in bytes
maxChainSize = (1 << 20) * maxChainLen
// Max number of collected nonces kept in memory.
// Expect usual peak of 1 or 2.
maxNonces = 100
)
// Client is an ACME client.
// The only required field is Key. An example of creating a client with a new key
// is as follows:
//
// key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
// if err != nil {
// log.Fatal(err)
// }
// client := &Client{Key: key}
//
type Client struct {
// Key is the account key used to register with a CA and sign requests.
// Key.Public() must return a *rsa.PublicKey or *ecdsa.PublicKey.
Key crypto.Signer
// HTTPClient optionally specifies an HTTP client to use
// instead of http.DefaultClient.
HTTPClient *http.Client
// DirectoryURL points to the CA directory endpoint.
// If empty, LetsEncryptURL is used.
// Mutating this value after a successful call of Client's Discover method
// will have no effect.
DirectoryURL string
noncesMu sync.Mutex
nonces map[string]struct{} // nonces collected from previous responses
urlMu sync.Mutex // urlMu guards writes to dir, accountURL, ordersURL
dir *Directory // cached result of Client's Discover method
accountURL string
ordersURL string
}
// Discover performs ACME server discovery using c.DirectoryURL.
//
// It caches successful result. So, subsequent calls will not result in
// a network round-trip. This also means mutating c.DirectoryURL after successful call
// of this method will have no effect.
func (c *Client) Discover(ctx context.Context) (Directory, error) {
c.urlMu.Lock()
defer c.urlMu.Unlock()
if c.dir != nil {
return *c.dir, nil
}
dirURL := c.DirectoryURL
if dirURL == "" {
dirURL = LetsEncryptURL
}
res, err := c.get(ctx, dirURL)
if err != nil {
return Directory{}, err
}
defer res.Body.Close()
c.addNonce(res.Header)
if res.StatusCode != http.StatusOK {
return Directory{}, responseError(res)
}
var v struct {
NewNonce string
NewAccount string
NewOrder string
NewAuthz string
RevokeCert string
KeyChange string
Meta struct {
TermsOfService string
Website string
CAAIdentities []string
ExternalAccountRequired bool
}
}
if err := json.NewDecoder(res.Body).Decode(&v); err != nil {
return Directory{}, err
}
c.dir = &Directory{
NewNonceURL: v.NewNonce,
NewAccountURL: v.NewAccount,
NewOrderURL: v.NewOrder,
NewAuthzURL: v.NewAuthz,
RevokeCertURL: v.RevokeCert,
KeyChangeURL: v.KeyChange,
Terms: v.Meta.TermsOfService,
Website: v.Meta.Website,
CAA: v.Meta.CAAIdentities,
ExternalAccountRequired: v.Meta.ExternalAccountRequired,
}
return *c.dir, nil
}
// CreateOrder creates a new certificate order. The input order argument is not
// modified and can be built using NewOrderWithDomains.
func (c *Client) CreateOrder(ctx context.Context, order *Order) (*Order, error) {
if _, err := c.Discover(ctx); err != nil {
return nil, err
}
req := struct {
Identifiers []wireAuthzID `json:"identifiers"`
NotBefore string `json:"notBefore,omitempty"`
NotAfter string `json:"notAfter,omitempty"`
}{
Identifiers: make([]wireAuthzID, len(order.Identifiers)),
}
for i, id := range order.Identifiers {
req.Identifiers[i] = wireAuthzID{
Type: id.Type,
Value: id.Value,
}
}
if !order.NotBefore.IsZero() {
req.NotBefore = order.NotBefore.Format(time.RFC3339)
}
if !order.NotAfter.IsZero() {
req.NotAfter = order.NotAfter.Format(time.RFC3339)
}
res, err := c.postWithJWSAccount(ctx, c.dir.NewOrderURL, req)
if err != nil {
return nil, err
}
defer res.Body.Close()
if res.StatusCode != http.StatusCreated {
return nil, responseError(res)
}
var v wireOrder
if err := json.NewDecoder(res.Body).Decode(&v); err != nil {
return nil, err
}
l, err := resolveLocation(c.dir.NewOrderURL, res.Header)
if err != nil {
return nil, err
}
o := v.order(l, "")
if o.Status == StatusInvalid {
return nil, OrderInvalidError{o}
}
return o, nil
}
// FinalizeOrder finalizes an order using the Certificate Signing Request csr
// encoded in DER format. If the order has not been fully authorized,
// an OrderPendingError will be returned.
//
// After requesting finalization, FinalizOrder polls the order using WaitOrder
// until it is finalized and then fetches the associated certificate and returns
// it.
//
// Callers are encouraged to parse the returned certificate chain to ensure it
// is valid and has the expected attributes.
func (c *Client) FinalizeOrder(ctx context.Context, finalizeURL string, csr []byte) (der [][]byte, err error) {
req := struct {
CSR string `json:"csr"`
}{
CSR: base64.RawURLEncoding.EncodeToString(csr),
}
res, err := c.postWithJWSAccount(ctx, finalizeURL, req)
if err != nil {
return nil, err
}
defer res.Body.Close()
if res.StatusCode != http.StatusOK {
return nil, responseError(res)
}
var v wireOrder
if err := json.NewDecoder(res.Body).Decode(&v); err != nil {
return nil, err
}
l, err := resolveLocation(finalizeURL, res.Header)
if err != nil {
return nil, err
}
o := v.order(l, res.Header.Get("Retry-After"))
if o.Status == StatusProcessing || o.Status == StatusPending {
o, err = c.WaitOrder(ctx, o.URL)
if err != nil {
return nil, err
}
}
if o.Status != StatusValid {
return nil, fmt.Errorf("acme: unexpected order status %q", o.Status)
}
return c.getCert(ctx, o.CertificateURL)
}
// GetOrder retrieves an order identified by url.
//
// If a caller needs to poll an order until its status is final,
// see the WaitOrder method.
func (c *Client) GetOrder(ctx context.Context, url string) (*Order, error) {
res, err := c.get(ctx, url)
if err != nil {
return nil, err
}
defer res.Body.Close()
if res.StatusCode != http.StatusOK {
err = responseError(res)
return nil, err
}
var v wireOrder
if err := json.NewDecoder(res.Body).Decode(&v); err != nil {
return nil, err
}
return v.order(url, res.Header.Get("Retry-After")), nil
}
// WaitOrder waits for an order to transition from StatusProcessing to a final
// state (StatusValid/StatusInvalid), it retries the request until the order is
// final, ctx is cancelled by the caller, or an error response is received.
//
// It returns a non-nil Order only if its Status is StatusValid. In all other
// cases WaitOrder returns an error. If the Status is StatusInvalid, the
// returned error will be of type OrderInvalidError. If the status is
// StatusPending, the returned error will be of type OrderPendingError.
func (c *Client) WaitOrder(ctx context.Context, url string) (*Order, error) {
sleep := timeSleeper(ctx)
for {
o, err := c.GetOrder(ctx, url)
if err != nil {
return nil, err
}
switch o.Status {
case StatusValid:
return o, nil
case StatusInvalid:
return nil, OrderInvalidError{o}
case StatusPending:
return nil, OrderPendingError{o}
case StatusProcessing: // continue retry loop
default:
return nil, fmt.Errorf("acme: unexpected order status %q", o.Status)
}
if err := sleep(o.RetryAfter); err != nil {
return nil, err
}
}
}
// RevokeCert revokes a previously issued certificate cert, provided in DER
// format.
//
// If key is nil, the account must have been used to issue the certificate or
// have valid authorizations for all of the identifiers in the certificate. If
// key is provided, it must be the certificate's private key.
func (c *Client) RevokeCert(ctx context.Context, key crypto.Signer, cert []byte, reason CRLReasonCode) error {
if _, err := c.Discover(ctx); err != nil {
return err
}
body := &struct {
Cert string `json:"certificate"`
Reason int `json:"reason"`
}{
Cert: base64.RawURLEncoding.EncodeToString(cert),
Reason: int(reason),
}
var res *http.Response
var err error
if key == nil {
res, err = c.postWithJWSAccount(ctx, c.dir.RevokeCertURL, body)
} else {
res, err = c.postWithJWSKey(ctx, key, c.dir.RevokeCertURL, body)
}
if err != nil {
return err
}
defer res.Body.Close()
if res.StatusCode != http.StatusOK {
return responseError(res)
}
return nil
}
// CreateAccount creates a new account. It returns the account details from the
// server and does not modify the account argument that it is called with.
func (c *Client) CreateAccount(ctx context.Context, a *Account) (*Account, error) {
if _, err := c.Discover(ctx); err != nil {
return nil, err
}
return c.doAccount(ctx, c.dir.NewAccountURL, false, a)
}
// GetAccount retrieves the account that the client is configured with.
func (c *Client) GetAccount(ctx context.Context) (*Account, error) {
if _, err := c.Discover(ctx); err != nil {
return nil, err
}
return c.doAccount(ctx, c.dir.NewAccountURL, true, nil)
}
// UpdateAccount updates an existing account. It returns an updated account
// copy. The provided account is not modified.
func (c *Client) UpdateAccount(ctx context.Context, a *Account) (*Account, error) {
return c.doAccount(ctx, a.URL, false, a)
}
// GetAuthorization retrieves an authorization identified by the given URL.
//
// 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 := c.get(ctx, url)
if err != nil {
return nil, err
}
defer res.Body.Close()
if res.StatusCode != http.StatusOK {
return nil, responseError(res)
}
var v wireAuthz
if err := json.NewDecoder(res.Body).Decode(&v); err != nil {
return nil, fmt.Errorf("acme: invalid response: %v", err)
}
return v.authorization(url), nil
}
// DeactivateAuthorization relinquishes an existing authorization identified by
// the given URL.
//
// If successful, the caller will be required to obtain a new authorization
// before a new certificate for the domain associated with the authorization is
// issued.
//
// It does not revoke existing certificates.
func (c *Client) DeactivateAuthorization(ctx context.Context, url string) error {
res, err := c.postWithJWSAccount(ctx, url, json.RawMessage(`{"status":"deactivated"}`))
if err != nil {
return err
}
defer res.Body.Close()
if res.StatusCode != http.StatusOK {
return responseError(res)
}
return nil
}
// WaitAuthorization retrieves authorization details. If the authorization is not in
// a final state (StatusValid/StatusInvalid), it retries the request until the authorization
// is final, ctx is cancelled by the caller, or an error response is received.
//
// 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 or StatusDeactivated, the returned error will be of type AuthorizationError.
func (c *Client) WaitAuthorization(ctx context.Context, url string) (*Authorization, error) {
sleep := sleeper(ctx)
for {
res, err := c.get(ctx, url)
if err != nil {
return nil, err
}
if res.StatusCode != http.StatusOK {
err = responseError(res)
res.Body.Close()
return nil, err
}
var raw wireAuthz
err = json.NewDecoder(res.Body).Decode(&raw)
res.Body.Close()
if err != nil {
return nil, err
}
switch raw.Status {
case StatusValid:
return raw.authorization(url), nil
case StatusInvalid, StatusDeactivated:
return nil, AuthorizationError{raw.authorization(url)}
case StatusPending, StatusProcessing: // fall through to sleep
default:
return nil, fmt.Errorf("acme: unknown authorization status %q", raw.Status)
}
if err := sleep(res.Header.Get("Retry-After")); err != nil {
return nil, err
}
}
}
// GetChallenge retrieves the current status of a challenge.
//
// A client typically polls a challenge status using this method.
func (c *Client) GetChallenge(ctx context.Context, url string) (*Challenge, error) {
res, err := c.get(ctx, url)
if err != nil {
return nil, err
}
defer res.Body.Close()
if res.StatusCode != http.StatusOK {
return nil, responseError(res)
}
v := wireChallenge{URL: url}
if err := json.NewDecoder(res.Body).Decode(&v); err != nil {
return nil, fmt.Errorf("acme: invalid response: %v", err)
}
return v.challenge(), nil
}
// AcceptChallenge informs the server that the client accepts one of its
// authorization challenges previously obtained with
// CreateOrder/GetAuthorization.
//
// The server will then perform the validation asynchronously.
func (c *Client) AcceptChallenge(ctx context.Context, chal *Challenge) (*Challenge, error) {
auth, err := keyAuth(c.Key.Public(), chal.Token)
if err != nil {
return nil, err
}
req := struct {
Auth string `json:"keyAuthorization"`
}{auth}
res, err := c.postWithJWSAccount(ctx, chal.URL, req)
if err != nil {
return nil, err
}
defer res.Body.Close()
if res.StatusCode != http.StatusOK {
return nil, responseError(res)
}
var v wireChallenge
if err := json.NewDecoder(res.Body).Decode(&v); err != nil {
return nil, fmt.Errorf("acme: invalid response: %v", err)
}
return v.challenge(), nil
}
// DNS01ChallengeRecord returns a DNS record value for a dns-01 challenge response.
// A TXT record containing the returned value must be provisioned under
// "_acme-challenge" name of the domain being validated.
//
// The token argument is a Challenge.Token value.
func (c *Client) DNS01ChallengeRecord(token string) (string, error) {
ka, err := keyAuth(c.Key.Public(), token)
if err != nil {
return "", err
}
b := sha256.Sum256([]byte(ka))
return base64.RawURLEncoding.EncodeToString(b[:]), nil
}
// HTTP01ChallengeResponse returns the response for an http-01 challenge.
// Servers should respond with the value to HTTP requests at the URL path
// provided by HTTP01ChallengePath to validate the challenge and prove control
// over a domain name.
//
// The token argument is a Challenge.Token value.
func (c *Client) HTTP01ChallengeResponse(token string) (string, error) {
return keyAuth(c.Key.Public(), token)
}
// HTTP01ChallengePath returns the URL path at which the response for an http-01 challenge
// should be provided by the servers.
// The response value can be obtained with HTTP01ChallengeResponse.
//
// The token argument is a Challenge.Token value.
func (c *Client) HTTP01ChallengePath(token string) string {
return "/.well-known/acme-challenge/" + token
}
// doAccount creates, updates, and reads accounts.
//
// A non-nil acct argument indicates whether the intention is to mutate data of
// the Account. Only the Contact field can be updated.
func (c *Client) doAccount(ctx context.Context, url string, getExistingWithKey bool, acct *Account) (*Account, error) {
req := struct {
Contact []string `json:"contact,omitempty"`
TermsAgreed bool `json:"termsOfServiceAgreed,omitempty"`
GetExisting bool `json:"onlyReturnExisting,omitempty"`
}{
GetExisting: getExistingWithKey,
}
var accountURL string
if url != c.dir.NewAccountURL {
accountURL = url
}
if acct != nil {
req.Contact = acct.Contact
req.TermsAgreed = acct.TermsAgreed
}
res, err := c.retryPostJWS(ctx, c.Key, accountURL, url, req)
if err != nil {
return nil, err
}
defer res.Body.Close()
if res.StatusCode < 200 || res.StatusCode > 299 {
return nil, responseError(res)
}
if getExistingWithKey {
l, err := resolveLocation(url, res.Header)
if err != nil {
return nil, err
}
return c.doAccount(ctx, l, false, nil)
}
var v struct {
Status string
Contact []string
Orders string
}
if err := json.NewDecoder(res.Body).Decode(&v); err != nil {
return nil, fmt.Errorf("acme: invalid response: %v", err)
}
l, err := resolveLocation(url, res.Header)
if err != nil {
return nil, err
}
a := &Account{
URL: l,
Status: v.Status,
Contact: v.Contact,
OrdersURL: v.Orders,
}
if a.URL == "" {
a.URL = url
}
c.urlMu.Lock()
defer c.urlMu.Unlock()
c.accountURL = a.URL
c.ordersURL = a.OrdersURL
return a, nil
}
// cacheAccount ensures that the account URL is cached and returns it.
func (c *Client) cacheAccountURL(ctx context.Context) (string, error) {
c.urlMu.Lock()
defer c.urlMu.Unlock()
if c.accountURL != "" {
return c.accountURL, nil
}
res, err := c.postWithJWSKey(ctx, c.Key, c.dir.NewAccountURL, json.RawMessage(`{"onlyReturnExisting":true}`))
if err != nil {
return "", err
}
defer res.Body.Close()
if res.StatusCode != http.StatusOK {
return "", responseError(res)
}
var v struct {
Orders string
}
if err := json.NewDecoder(res.Body).Decode(&v); err != nil {
return "", err
}
l, err := resolveLocation(c.dir.NewAccountURL, res.Header)
if err != nil {
return "", err
}
c.accountURL = l
c.ordersURL = v.Orders
return c.accountURL, nil
}
func (c *Client) postWithJWSKey(ctx context.Context, key crypto.Signer, url string, body interface{}) (*http.Response, error) {
return c.retryPostJWS(ctx, key, "", url, body)
}
func (c *Client) postWithJWSAccount(ctx context.Context, url string, body interface{}) (*http.Response, error) {
accountURL, err := c.cacheAccountURL(ctx)
if err != nil {
return nil, err
}
return c.retryPostJWS(ctx, c.Key, accountURL, url, body)
}
// 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, accountURL, url string, body interface{}) (*http.Response, error) {
sleep := sleeper(ctx)
for {
res, err := c.postJWS(ctx, key, accountURL, 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()
if ae, ok := err.(*Error); ok && ae.Type == "urn:ietf:params:acme:error: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); 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.
// The accountURL should be empty for account creation and certificate revocation.
func (c *Client) postJWS(ctx context.Context, key crypto.Signer, accountURL, url string, body interface{}) (*http.Response, error) {
nonce, err := c.popNonce(ctx)
if err != nil {
return nil, err
}
b, err := jwsEncodeJSON(body, key, accountURL, url, nonce)
if err != nil {
return nil, err
}
res, err := c.post(ctx, url, "application/jose+json", bytes.NewReader(b))
if err != nil {
return nil, err
}
c.addNonce(res.Header)
return res, nil
}
// popNonce returns a nonce value previously stored with c.addNonce
// or fetches a fresh one.
func (c *Client) popNonce(ctx context.Context) (string, error) {
c.noncesMu.Lock()
defer c.noncesMu.Unlock()
if len(c.nonces) == 0 {
return c.fetchNonce(ctx)
}
var nonce string
for nonce = range c.nonces {
delete(c.nonces, nonce)
break
}
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)
if v == "" {
return
}
c.noncesMu.Lock()
defer c.noncesMu.Unlock()
if len(c.nonces) >= maxNonces {
return
}
if c.nonces == nil {
c.nonces = make(map[string]struct{})
}
c.nonces[v] = struct{}{}
}
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) (string, error) {
resp, err := c.head(ctx, c.dir.NewNonceURL)
if err != nil {
return "", err
}
defer resp.Body.Close()
nonce := nonceFromHeader(resp.Header)
if nonce == "" {
if resp.StatusCode > 299 {
return "", responseError(resp)
}
return "", errors.New("acme: nonce not found")
}
return nonce, nil
}
func nonceFromHeader(h http.Header) string {
return h.Get("Replay-Nonce")
}
func (c *Client) getCert(ctx context.Context, url string) ([][]byte, error) {
res, err := c.get(ctx, url)
if err != nil {
return nil, err
}
defer res.Body.Close()
data, err := ioutil.ReadAll(io.LimitReader(res.Body, maxChainSize+1))
if err != nil {
return nil, fmt.Errorf("acme: error getting certificate: %v", err)
}
if len(data) > maxChainSize {
return nil, errors.New("acme: certificate chain is too big")
}
var chain [][]byte
for {
var p *pem.Block
p, data = pem.Decode(data)
if p == nil {
if len(chain) == 0 {
return nil, errors.New("acme: invalid PEM certificate chain")
}
break
}
if len(chain) == maxChainLen {
return nil, errors.New("acme: certificate chain is too long")
}
if p.Type != "CERTIFICATE" {
return nil, fmt.Errorf("acme: invalid PEM block type %q", p.Type)
}
chain = append(chain, p.Bytes)
}
return chain, nil
}
// responseError creates an error of Error type from resp.
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 := &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
e.Detail = string(b)
if e.Detail == "" {
e.Detail = resp.Status
}
}
return e.error(resp.Header)
}
// 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) error {
sleep := timeSleeper(ctx)
return func(ra string) error {
return sleep(retryAfter(ra))
}
}
func timeSleeper(ctx context.Context) func(time.Time) error {
var count int
return func(t time.Time) error {
d := backoff(count, 10*time.Second)
count++
if !t.IsZero() {
d = t.Sub(timeNow())
}
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.
func retryAfter(v string) time.Time {
if i, err := strconv.Atoi(v); err == nil {
return timeNow().Add(time.Duration(i) * time.Second)
}
t, err := http.ParseTime(v)
if err != nil {
return time.Time{}
}
return t
}
// backoff computes a duration after which an n+1 retry iteration should occur
// using truncated exponential backoff algorithm.
//
// The n argument is always bounded between 0 and 30.
// The max argument defines upper bound for the returned value.
func backoff(n int, max time.Duration) time.Duration {
if n < 0 {
n = 0
}
if n > 30 {
n = 30
}
var d time.Duration
if x, err := rand.Int(rand.Reader, big.NewInt(1000)); err == nil {
d = time.Duration(x.Int64()) * time.Millisecond
}
d += time.Duration(1<<uint(n)) * time.Second
if d > max {
return max
}
return d
}
// keyAuth generates a key authorization string for a given token.
func keyAuth(pub crypto.PublicKey, token string) (string, error) {
th, err := JWKThumbprint(pub)
if err != nil {
return "", err
}
return fmt.Sprintf("%s.%s", token, th), nil
}
func resolveLocation(base string, h http.Header) (string, error) {
u, err := url.Parse(base)
if err != nil {
return "", err
}
u, err = u.Parse(h.Get("Location"))
if err != nil {
return "", fmt.Errorf("acme: error parsing Location: %s", err)
}
return u.String(), nil
}
// timeNow is useful for testing for fixed current time.
var timeNow = time.Now

1123
third_party/crypto/acme/acme_test.go vendored Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,146 @@
// Copyright 2018 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.
// +build integration_test
package acme_test
import (
"context"
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"crypto/tls"
"crypto/x509"
"net"
"net/http"
"os"
"reflect"
"testing"
"golang.org/x/crypto/acme"
)
// This test works with Pebble and Let's Encrypt staging.
// For pebble use: ACME_DIRECTORY_URL=https://localhost:14000/dir go test
// For Let's Encrypt you'll need a publicly accessible HTTP server like `ngrok http 8080` and then
// TEST_HOST=xxx.ngrok.io:8080 ACME_DIRECTORY_URL=https://acme-staging-v02.api.letsencrypt.org/directory TEST_ACCOUNT_GET=1 TEST_REVOKE=1 go test
func TestIntegration(t *testing.T) {
dir := os.Getenv("ACME_DIRECTORY_URL")
testAccountGet := os.Getenv("TEST_ACCOUNT_GET") != ""
testRevoke := os.Getenv("TEST_REVOKE") != ""
testHost := os.Getenv("TEST_HOST")
if testHost == "" {
testHost = "localhost:5002"
}
testIdentifier, listenPort, _ := net.SplitHostPort(testHost)
if dir == "" {
t.Fatal("ACME_DIRECTORY_URL is required")
}
key, _ := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
c := &acme.Client{
Key: key,
DirectoryURL: dir,
HTTPClient: &http.Client{
Transport: &http.Transport{
TLSClientConfig: &tls.Config{
InsecureSkipVerify: true,
},
},
},
}
a := &acme.Account{
Contact: []string{"mailto:user@example.com"},
TermsAgreed: true,
}
na, err := c.CreateAccount(context.Background(), a)
if err != nil {
t.Fatal(err)
}
if !reflect.DeepEqual(a.Contact, na.Contact) {
t.Errorf("na.Contact = %q; want %q", na.Contact, a.Contact)
}
if na.URL == "" {
t.Fatal("empty na.URL")
}
// this endpoint is not supported by pebble, so put it behind a flag
if testAccountGet {
na, err = c.GetAccount(context.Background())
if err != nil {
t.Fatal(err)
}
if !reflect.DeepEqual(a.Contact, na.Contact) {
t.Errorf("na.Contact = %q; want %q", na.Contact, a.Contact)
}
}
order, err := c.CreateOrder(context.Background(), acme.NewOrder(testIdentifier))
if err != nil {
t.Fatal(err)
}
auth, err := c.GetAuthorization(context.Background(), order.Authorizations[0])
if err != nil {
t.Fatal(err)
}
var challenge *acme.Challenge
for _, ch := range auth.Challenges {
if ch.Type == "http-01" {
challenge = ch
break
}
}
if challenge == nil {
t.Fatal("missing http-01 challenge")
}
l, err := net.Listen("tcp", ":"+listenPort)
if err != nil {
t.Errorf("error listening for challenge: %s", err)
}
defer l.Close()
go http.Serve(l, http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
if req.URL.Path != c.HTTP01ChallengePath(challenge.Token) {
w.WriteHeader(404)
return
}
res, _ := c.HTTP01ChallengeResponse(challenge.Token)
w.Write([]byte(res))
}))
_, err = c.AcceptChallenge(context.Background(), challenge)
if err != nil {
t.Fatal(err)
}
_, err = c.WaitAuthorization(context.Background(), order.Authorizations[0])
if err != nil {
t.Fatal(err)
}
certKey, _ := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
csr, _ := x509.CreateCertificateRequest(rand.Reader, &x509.CertificateRequest{DNSNames: []string{testIdentifier}}, certKey)
der, err := c.FinalizeOrder(context.Background(), order.FinalizeURL, csr)
if err != nil {
t.Fatal(err)
}
cert, err := x509.ParseCertificate(der[0])
if err != nil {
t.Fatal(err)
}
if cert.DNSNames[0] != testIdentifier {
t.Errorf("unexpected DNSNames %v", cert.DNSNames)
}
if testRevoke {
if err := c.RevokeCert(context.Background(), certKey, der[0], acme.CRLReasonUnspecified); err != nil {
t.Fatal(err)
}
}
}

158
third_party/crypto/acme/jws.go vendored Normal file
View File

@ -0,0 +1,158 @@
// Copyright 2015 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package acme
import (
"crypto"
"crypto/ecdsa"
"crypto/rand"
"crypto/rsa"
"crypto/sha256"
_ "crypto/sha512" // need for EC keys
"encoding/base64"
"encoding/json"
"fmt"
"math/big"
)
// jwsEncodeJSON signs claimset using provided key and a nonce.
// The result is serialized in JSON format.
// See https://tools.ietf.org/html/rfc7515#section-7.
func jwsEncodeJSON(claimset interface{}, key crypto.Signer, accountURL, url, nonce string) ([]byte, error) {
alg, sha := jwsHasher(key)
if alg == "" || !sha.Available() {
return nil, ErrUnsupportedKey
}
var phead string
if accountURL == "" {
jwk, err := jwkEncode(key.Public())
if err != nil {
return nil, err
}
phead = fmt.Sprintf(`{"alg":%q,"jwk":%s,"nonce":%q,"url":%q}`, alg, jwk, nonce, url)
} else {
phead = fmt.Sprintf(`{"alg":%q,"kid":%q,"nonce":%q,"url":%q}`, alg, accountURL, nonce, url)
}
phead = base64.RawURLEncoding.EncodeToString([]byte(phead))
cs, err := json.Marshal(claimset)
if err != nil {
return nil, err
}
payload := base64.RawURLEncoding.EncodeToString(cs)
hash := sha.New()
hash.Write([]byte(phead + "." + payload))
sig, err := jwsSign(key, sha, hash.Sum(nil))
if err != nil {
return nil, err
}
enc := struct {
Protected string `json:"protected"`
Payload string `json:"payload"`
Sig string `json:"signature"`
}{
Protected: phead,
Payload: payload,
Sig: base64.RawURLEncoding.EncodeToString(sig),
}
return json.Marshal(&enc)
}
// jwkEncode encodes public part of an RSA or ECDSA key into a JWK.
// The result is also suitable for creating a JWK thumbprint.
// https://tools.ietf.org/html/rfc7517
func jwkEncode(pub crypto.PublicKey) (string, error) {
switch pub := pub.(type) {
case *rsa.PublicKey:
// https://tools.ietf.org/html/rfc7518#section-6.3.1
n := pub.N
e := big.NewInt(int64(pub.E))
// Field order is important.
// See https://tools.ietf.org/html/rfc7638#section-3.3 for details.
return fmt.Sprintf(`{"e":"%s","kty":"RSA","n":"%s"}`,
base64.RawURLEncoding.EncodeToString(e.Bytes()),
base64.RawURLEncoding.EncodeToString(n.Bytes()),
), nil
case *ecdsa.PublicKey:
// https://tools.ietf.org/html/rfc7518#section-6.2.1
p := pub.Curve.Params()
n := p.BitSize / 8
if p.BitSize%8 != 0 {
n++
}
x := pub.X.Bytes()
if n > len(x) {
x = append(make([]byte, n-len(x)), x...)
}
y := pub.Y.Bytes()
if n > len(y) {
y = append(make([]byte, n-len(y)), y...)
}
// Field order is important.
// See https://tools.ietf.org/html/rfc7638#section-3.3 for details.
return fmt.Sprintf(`{"crv":"%s","kty":"EC","x":"%s","y":"%s"}`,
p.Name,
base64.RawURLEncoding.EncodeToString(x),
base64.RawURLEncoding.EncodeToString(y),
), nil
}
return "", ErrUnsupportedKey
}
// jwsSign signs the digest using the given key.
// It returns ErrUnsupportedKey if the key type is unknown.
// The hash is used only for RSA keys.
func jwsSign(key crypto.Signer, hash crypto.Hash, digest []byte) ([]byte, error) {
switch key := key.(type) {
case *rsa.PrivateKey:
return key.Sign(rand.Reader, digest, hash)
case *ecdsa.PrivateKey:
r, s, err := ecdsa.Sign(rand.Reader, key, digest)
if err != nil {
return nil, err
}
rb, sb := r.Bytes(), s.Bytes()
size := key.Params().BitSize / 8
if size%8 > 0 {
size++
}
sig := make([]byte, size*2)
copy(sig[size-len(rb):], rb)
copy(sig[size*2-len(sb):], sb)
return sig, nil
}
return nil, ErrUnsupportedKey
}
// jwsHasher indicates suitable JWS algorithm name and a hash function
// to use for signing a digest with the provided key.
// It returns ("", 0) if the key is not supported.
func jwsHasher(key crypto.Signer) (string, crypto.Hash) {
switch key := key.(type) {
case *rsa.PrivateKey:
return "RS256", crypto.SHA256
case *ecdsa.PrivateKey:
switch key.Params().Name {
case "P-256":
return "ES256", crypto.SHA256
case "P-384":
return "ES384", crypto.SHA384
case "P-521":
return "ES512", crypto.SHA512
}
}
return "", 0
}
// JWKThumbprint creates a JWK thumbprint out of pub
// as specified in https://tools.ietf.org/html/rfc7638.
func JWKThumbprint(pub crypto.PublicKey) (string, error) {
jwk, err := jwkEncode(pub)
if err != nil {
return "", err
}
b := sha256.Sum256([]byte(jwk))
return base64.RawURLEncoding.EncodeToString(b[:]), nil
}

320
third_party/crypto/acme/jws_test.go vendored Normal file
View File

@ -0,0 +1,320 @@
// Copyright 2015 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package acme
import (
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rsa"
"crypto/x509"
"encoding/base64"
"encoding/json"
"encoding/pem"
"fmt"
"math/big"
"testing"
)
const (
testKeyPEM = `
-----BEGIN RSA PRIVATE KEY-----
MIIEowIBAAKCAQEA4xgZ3eRPkwoRvy7qeRUbmMDe0V+xH9eWLdu0iheeLlrmD2mq
WXfP9IeSKApbn34g8TuAS9g5zhq8ELQ3kmjr+KV86GAMgI6VAcGlq3QrzpTCf/30
Ab7+zawrfRaFONa1HwEzPY1KHnGVkxJc85gNkwYI9SY2RHXtvln3zs5wITNrdosq
EXeaIkVYBEhbhNu54pp3kxo6TuWLi9e6pXeWetEwmlBwtWZlPoib2j3TxLBksKZf
oyFyek380mHgJAumQ/I2fjj98/97mk3ihOY4AgVdCDj1z/GCoZkG5Rq7nbCGyosy
KWyDX00Zs+nNqVhoLeIvXC4nnWdJMZ6rogxyQQIDAQABAoIBACIEZTOI1Kao9nmV
9IeIsuaR1Y61b9neOF/MLmIVIZu+AAJFCMB4Iw11FV6sFodwpEyeZhx2WkpWVN+H
r19eGiLX3zsL0DOdqBJoSIHDWCCMxgnYJ6nvS0nRxX3qVrBp8R2g12Ub+gNPbmFm
ecf/eeERIVxfifd9VsyRu34eDEvcmKFuLYbElFcPh62xE3x12UZvV/sN7gXbawpP
G+w255vbE5MoaKdnnO83cTFlcHvhn24M/78qP7Te5OAeelr1R89kYxQLpuGe4fbS
zc6E3ym5Td6urDetGGrSY1Eu10/8sMusX+KNWkm+RsBRbkyKq72ks/qKpOxOa+c6
9gm+Y8ECgYEA/iNUyg1ubRdH11p82l8KHtFC1DPE0V1gSZsX29TpM5jS4qv46K+s
8Ym1zmrORM8x+cynfPx1VQZQ34EYeCMIX212ryJ+zDATl4NE0I4muMvSiH9vx6Xc
7FmhNnaYzPsBL5Tm9nmtQuP09YEn8poiOJFiDs/4olnD5ogA5O4THGkCgYEA5MIL
qWYBUuqbEWLRtMruUtpASclrBqNNsJEsMGbeqBJmoMxdHeSZckbLOrqm7GlMyNRJ
Ne/5uWRGSzaMYuGmwsPpERzqEvYFnSrpjW5YtXZ+JtxFXNVfm9Z1gLLgvGpOUCIU
RbpoDckDe1vgUuk3y5+DjZihs+rqIJ45XzXTzBkCgYBWuf3segruJZy5rEKhTv+o
JqeUvRn0jNYYKFpLBeyTVBrbie6GkbUGNIWbrK05pC+c3K9nosvzuRUOQQL1tJbd
4gA3oiD9U4bMFNr+BRTHyZ7OQBcIXdz3t1qhuHVKtnngIAN1p25uPlbRFUNpshnt
jgeVoHlsBhApcs5DUc+pyQKBgDzeHPg/+g4z+nrPznjKnktRY1W+0El93kgi+J0Q
YiJacxBKEGTJ1MKBb8X6sDurcRDm22wMpGfd9I5Cv2v4GsUsF7HD/cx5xdih+G73
c4clNj/k0Ff5Nm1izPUno4C+0IOl7br39IPmfpSuR6wH/h6iHQDqIeybjxyKvT1G
N0rRAoGBAKGD+4ZI/E1MoJ5CXB8cDDMHagbE3cq/DtmYzE2v1DFpQYu5I4PCm5c7
EQeIP6dZtv8IMgtGIb91QX9pXvP0aznzQKwYIA8nZgoENCPfiMTPiEDT9e/0lObO
9XWsXpbSTsRPj0sv1rB+UzBJ0PgjK4q2zOF0sNo7b1+6nlM3BWPx
-----END RSA PRIVATE KEY-----
`
// This thumbprint is for the testKey defined above.
testKeyThumbprint = "6nicxzh6WETQlrvdchkz-U3e3DOQZ4heJKU63rfqMqQ"
// openssl ecparam -name secp256k1 -genkey -noout
testKeyECPEM = `
-----BEGIN EC PRIVATE KEY-----
MHcCAQEEIK07hGLr0RwyUdYJ8wbIiBS55CjnkMD23DWr+ccnypWLoAoGCCqGSM49
AwEHoUQDQgAE5lhEug5xK4xBDZ2nAbaxLtaLiv85bxJ7ePd1dkO23HThqIrvawF5
QAaS/RNouybCiRhRjI3EaxLkQwgrCw0gqQ==
-----END EC PRIVATE KEY-----
`
// 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. 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
testKeyEC384 *ecdsa.PrivateKey
testKeyEC512 *ecdsa.PrivateKey
)
func init() {
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 " + name)
}
return d.Bytes
}
func parseRSA(s, name string) *rsa.PrivateKey {
b := decodePEM(s, name)
k, err := x509.ParsePKCS1PrivateKey(b)
if err != nil {
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) {
claims := struct{ Msg string }{"Hello JWS"}
// JWS signed with testKey and "nonce" as the nonce value
// JSON-serialized JWS fields are split for easier testing
const (
// {"alg":"RS256","jwk":{"e":"AQAB","kty":"RSA","n":"..."},"url":"https://example.com","nonce":"nonce"}
protected = "eyJhbGciOiJSUzI1NiIsImp3ayI6eyJlIjoiQVFBQiIsImt0eSI6" +
"IlJTQSIsIm4iOiI0eGdaM2VSUGt3b1J2eTdxZVJVYm1NRGUwVi14" +
"SDllV0xkdTBpaGVlTGxybUQybXFXWGZQOUllU0tBcGJuMzRnOFR1" +
"QVM5ZzV6aHE4RUxRM2ttanItS1Y4NkdBTWdJNlZBY0dscTNRcnpw" +
"VENmXzMwQWI3LXphd3JmUmFGT05hMUh3RXpQWTFLSG5HVmt4SmM4" +
"NWdOa3dZSTlTWTJSSFh0dmxuM3pzNXdJVE5yZG9zcUVYZWFJa1ZZ" +
"QkVoYmhOdTU0cHAza3hvNlR1V0xpOWU2cFhlV2V0RXdtbEJ3dFda" +
"bFBvaWIyajNUeExCa3NLWmZveUZ5ZWszODBtSGdKQXVtUV9JMmZq" +
"ajk4Xzk3bWszaWhPWTRBZ1ZkQ0RqMXpfR0NvWmtHNVJxN25iQ0d5" +
"b3N5S1d5RFgwMFpzLW5OcVZob0xlSXZYQzRubldkSk1aNnJvZ3h5" +
"UVEifSwibm9uY2UiOiJub25jZSIsInVybCI6Imh0dHBzOi8vZXhh" +
"bXBsZS5jb20ifQ"
// {"Msg":"Hello JWS"}
payload = "eyJNc2ciOiJIZWxsbyBKV1MifQ"
signature = "EtedUusG_N7NkuHRs9Ios6V0_VZdjYPut8vqRMDHvZQ3kZO0d5-9" +
"BivWINleGajAW29So64s4WYsITx2Y0g3obSw70Xsr8XVsVox2Wsx" +
"RJgd6KBNk1SGzqUW7-yEaS0fs0ax5SHPwpS9ek9WPCZ0MphfUH3d" +
"qK40x6dYbgY9mInfzf7L11QeRrQdJfGuef_74SJGTp6D4B5UrR2w" +
"m-AoSsRXY5A99U7J8YE9LFTg7pUQRSQWqqGZu-U9VDiB8bBvViVH" +
"1abbI5xHaSagDb1avfdIXqYv_QVeMXF67Nis8f963FOdX0zwjpob" +
"mpi-rsSmLBEtUkLERBIU_8JRdkXMcw"
)
b, err := jwsEncodeJSON(claims, testKey, "", "https://example.com", "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 jws.Protected != protected {
t.Errorf("protected:\n%s\nwant:\n%s", jws.Protected, protected)
}
if jws.Payload != payload {
t.Errorf("payload:\n%s\nwant:\n%s", jws.Payload, payload)
}
if jws.Signature != signature {
t.Errorf("signature:\n%s\nwant:\n%s", jws.Signature, signature)
}
}
func TestJWSEncodeJSONEC(t *testing.T) {
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, "", "https://example.com", "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 = 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)
}
}
}
func TestJWKThumbprintRSA(t *testing.T) {
// Key example from RFC 7638
const base64N = "0vx7agoebGcQSuuPiLJXZptN9nndrQmbXEps2aiAFbWhM78LhWx4cbbfAAt" +
"VT86zwu1RK7aPFFxuhDR1L6tSoc_BJECPebWKRXjBZCiFV4n3oknjhMstn6" +
"4tZ_2W-5JsGY4Hc5n9yBXArwl93lqt7_RN5w6Cf0h4QyQ5v-65YGjQR0_FD" +
"W2QvzqY368QQMicAtaSqzs8KJZgnYb9c7d0zgdAZHzu6qMQvRL5hajrn1n9" +
"1CbOpbISD08qNLyrdkt-bFTWhAI4vMQFh6WeZu0fM4lFd2NcRwr3XPksINH" +
"aQ-G_xBniIqbw0Ls1jF44-csFCur-kEgU8awapJzKnqDKgw"
const base64E = "AQAB"
const expected = "NzbLsXh8uDCcd-6MNwXF4W_7noWXFZAfHkxZsRGC9Xs"
b, err := base64.RawURLEncoding.DecodeString(base64N)
if err != nil {
t.Fatalf("Error parsing example key N: %v", err)
}
n := new(big.Int).SetBytes(b)
b, err = base64.RawURLEncoding.DecodeString(base64E)
if err != nil {
t.Fatalf("Error parsing example key E: %v", err)
}
e := new(big.Int).SetBytes(b)
pub := &rsa.PublicKey{N: n, E: int(e.Uint64())}
th, err := JWKThumbprint(pub)
if err != nil {
t.Error(err)
}
if th != expected {
t.Errorf("thumbprint = %q; want %q", th, expected)
}
}
func TestJWKThumbprintEC(t *testing.T) {
// Key example from RFC 7520
// expected was computed with
// echo -n '{"crv":"P-521","kty":"EC","x":"<base64X>","y":"<base64Y>"}' | \
// openssl dgst -binary -sha256 | \
// base64 | \
// tr -d '=' | tr '/+' '_-'
const (
base64X = "AHKZLLOsCOzz5cY97ewNUajB957y-C-U88c3v13nmGZx6sYl_oJXu9A5RkT" +
"KqjqvjyekWF-7ytDyRXYgCF5cj0Kt"
base64Y = "AdymlHvOiLxXkEhayXQnNCvDX4h9htZaCJN34kfmC6pV5OhQHiraVySsUda" +
"QkAgDPrwQrJmbnX9cwlGfP-HqHZR1"
expected = "dHri3SADZkrush5HU_50AoRhcKFryN-PI6jPBtPL55M"
)
b, err := base64.RawURLEncoding.DecodeString(base64X)
if err != nil {
t.Fatalf("Error parsing example key X: %v", err)
}
x := new(big.Int).SetBytes(b)
b, err = base64.RawURLEncoding.DecodeString(base64Y)
if err != nil {
t.Fatalf("Error parsing example key Y: %v", err)
}
y := new(big.Int).SetBytes(b)
pub := &ecdsa.PublicKey{Curve: elliptic.P521(), X: x, Y: y}
th, err := JWKThumbprint(pub)
if err != nil {
t.Error(err)
}
if th != expected {
t.Errorf("thumbprint = %q; want %q", th, expected)
}
}
func TestJWKThumbprintErrUnsupportedKey(t *testing.T) {
_, err := JWKThumbprint(struct{}{})
if err != ErrUnsupportedKey {
t.Errorf("err = %q; want %q", err, ErrUnsupportedKey)
}
}

404
third_party/crypto/acme/types.go vendored Normal file
View File

@ -0,0 +1,404 @@
// 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"
"time"
)
// ACME server response statuses used to describe Account, Authorization, and Challenge states.
const (
StatusUnknown = "unknown"
StatusPending = "pending"
StatusProcessing = "processing"
StatusValid = "valid"
StatusInvalid = "invalid"
StatusRevoked = "revoked"
StatusDeactivated = "deactivated"
)
// CRLReasonCode identifies the reason for a certificate revocation.
type CRLReasonCode int
// CRL reason codes as defined in RFC 5280.
const (
CRLReasonUnspecified CRLReasonCode = 0
CRLReasonKeyCompromise CRLReasonCode = 1
CRLReasonCACompromise CRLReasonCode = 2
CRLReasonAffiliationChanged CRLReasonCode = 3
CRLReasonSuperseded CRLReasonCode = 4
CRLReasonCessationOfOperation CRLReasonCode = 5
CRLReasonCertificateHold CRLReasonCode = 6
CRLReasonRemoveFromCRL CRLReasonCode = 8
CRLReasonPrivilegeWithdrawn CRLReasonCode = 9
CRLReasonAACompromise CRLReasonCode = 10
)
// 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 as defined in RFC 7807, Problem Details for HTTP APIs.
type Error struct {
// StatusCode is The HTTP status code generated by the origin server.
StatusCode int
// Type is a URI that identifies the problem type, typically in
// a "urn:ietf:params:acme:error:xxx" form.
Type string
// Detail is a human-readable explanation specific to this occurrence of the problem.
Detail string
// Subproblems is an optional list of additional error information, usually
// indicating problems with specific identifiers during authorization.
Subproblems []Subproblem
// Header is the original server error response headers.
// It may be nil.
Header http.Header
}
func (e *Error) Error() string {
return fmt.Sprintf("acme: %s: %s", e.Type, e.Detail)
}
// An Subproblem is additional error detail that is included in an Error,
// usually indicating a problem with a specific identifier during authorization.
type Subproblem struct {
// Type is a URI that identifies the subproblem type, typically in
// "urn:ietf:params:acme:error:xxx" form.
Type string
// Detail is a human-readable explanation specific to this occurrence of the
// subproblem.
Detail string
// Identifier optionally indicates the identifier that this subproblem is about.
Identifier *AuthzID
}
// OrderInvalidError is returned when an order is marked as invalid.
type OrderInvalidError struct {
// Order is the order that is invalid.
Order *Order
}
func (e OrderInvalidError) Error() string {
if e.Order == nil || e.Order.Error == nil {
return "acme: order is invalid"
}
return fmt.Sprintf("acme: invalid order (%s): %s", e.Order.Error.Type, e.Order.Error.Detail)
}
// OrderPendingError is returned when an order is still pending after an
// attempted finalization.
type OrderPendingError struct {
// Order is the order that is pending.
Order *Order
}
func (e OrderPendingError) Error() string {
return "acme: order is pending due to incomplete authorization"
}
// AuthorizationError is returned when an authorization is marked as invalid.
type AuthorizationError struct {
// Authorization is the authorization that is invalid.
Authorization *Authorization
}
func (e AuthorizationError) Error() string {
if e.Authorization == nil {
return "acme: authorization is invalid"
}
return fmt.Sprintf("acme: authorization for identifier %s is %s", e.Authorization.Identifier.Value, e.Authorization.Status)
}
// 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-09#section-6.5
func RateLimit(err error) (time.Time, bool) {
e, ok := err.(*Error)
if !ok || e.Type != "urn:ietf:params:acme:error:rateLimited" {
return time.Time{}, false
}
if e.Header == nil {
return time.Time{}, true
}
return retryAfter(e.Header.Get("Retry-After")), true
}
// Account is a user account. It is associated with a private key.
type Account struct {
// URL uniquely identifies the account.
URL string
// Status is the status of the account. Valid values are StatusValid,
// StatusDeactivated, and StatusRevoked.
Status string
// Contact is a list of URLs that the server can use to contact the client
// for issues related to this account.
Contact []string
// TermsAgreed indicates agreement with the terms of service. It is not
// modifiable after account creation.
TermsAgreed bool
// OrdersURL is the URL used to fetch a list of orders submitted by this
// account.
OrdersURL string
}
// Directory is ACME server discovery data.
type Directory struct {
// NewNonceURL is used to retrieve new nonces.
NewNonceURL string
// NewAccountURL is used to create new accounts.
NewAccountURL string
// NewOrderURL is used to create new orders.
NewOrderURL string
// NewAuthzURL is used to create new authorizations.
NewAuthzURL string
// RevokeCertURL is used to revoke a certificate.
RevokeCertURL string
// KeyChangeURL is used to change the account key.
KeyChangeURL string
// Terms is a URL identifying the current terms of service.
Terms string
// Website is an HTTP or HTTPS URL locating a website
// providing more information about the ACME server.
Website string
// CAA consists of lowercase hostname elements, which the ACME server
// recognises as referring to itself for the purposes of CAA record validation
// as defined in RFC6844.
CAA []string
// ExternalAccountRequired, if true, indicates that the CA requires that all
// new account requests include an ExternalAccountBinding field associating
// the new account with an external account.
ExternalAccountRequired bool
}
// NewOrder creates a new order with the domains provided, suitable for creating
// a TLS certificate order with CreateOrder.
func NewOrder(domains ...string) *Order {
o := &Order{Identifiers: make([]AuthzID, len(domains))}
for i, d := range domains {
o.Identifiers[i] = AuthzID{
Type: "dns",
Value: d,
}
}
return o
}
// An Order represents a request for a certificate and is used to track the
// progress through to issuance.
type Order struct {
// URL uniquely identifies the order.
URL string
// Status is the status of the order. It will be one of StatusPending,
// StatusProcessing, StatusValid, and StatusInvalid.
Status string
// Expires is the teimstamp after which the server will consider the order invalid.
Expires time.Time
// Identifiers is a list of identifiers that the order pertains to.
Identifiers []AuthzID
// NotBefore is an optional requested value of the notBefore field in the certificate.
NotBefore time.Time
// NotAfter is an optional requested value of the notAfter field in the certificate.
NotAfter time.Time
// Error is the error that occurred while processing the order, if any.
Error *Error
// Authorizations is a list of URLs for authorizations that the client needs
// to complete before the requested certificate can be issued. For
// valid/invalid orders, these are the authorizations that were completed.
Authorizations []string
// FinalizeURL is the URL that is used to finalize the Order.
FinalizeURL string
// CertificateURL is the URL for the certificate that has been issued in
// response to this order.
CertificateURL string
// RetryAfter is the timestamp, if any, to wait for before fetching this
// order again.
RetryAfter time.Time
}
// A Challenge is a CA challenge for an identifier.
type Challenge struct {
// Type is the challenge type, e.g. "http-01", "tls-sni-02", "dns-01".
Type string
// URL is the URL where a challenge response can be posted.
URL string
// Token is a random value that uniquely identifies the challenge.
Token string
// Validated is the time at which the server validated this challenge.
Validated time.Time
// Status identifies the status of this challenge. Valid values are
// StatusPending, StatusValid, and StatusInvalid.
Status string
// Error indicates the errors that occurred while the server was validating
// this challenge.
Errors []*Error
}
// Authorization encodes an authorization response.
type Authorization struct {
// URL uniquely identifies the authorization.
URL string
// Status is the status of the authorization. Valid values are
// StatusPending, StatusProcessing, StatusValid, StatusInvalid, and
// StatusRevoked.
Status string
// Identifier is the identifier that the account is authorized to represent.
Identifier AuthzID
// Expires is the timestamp after which the server will consider this authorization invalid.
Expires *time.Time
// Challenges is the list of challenges that the client can fulfill in order
// to prove posession of the identifier. For valid/invalid authorizations,
// this is the list of challenges that were used.
Challenges []*Challenge
}
// AuthzID is an identifier that an account is authorized to represent.
type AuthzID struct {
Type string // The type of identifier, e.g. "dns".
Value string // The identifier itself, e.g. "example.org".
}
type wireAuthzID struct {
Type string `json:"type"`
Value string `json:"value"`
}
// wireAuthz is ACME JSON representation of Authorization objects.
type wireAuthz struct {
Status string
Challenges []wireChallenge
Expires *time.Time
Identifier struct {
Type string
Value string
}
}
func (z *wireAuthz) authorization(url string) *Authorization {
a := &Authorization{
URL: url,
Status: z.Status,
Expires: z.Expires,
Identifier: AuthzID{Type: z.Identifier.Type, Value: z.Identifier.Value},
Challenges: make([]*Challenge, len(z.Challenges)),
}
for i, v := range z.Challenges {
a.Challenges[i] = v.challenge()
}
return a
}
// wireChallenge is ACME JSON challenge representation.
type wireChallenge struct {
URL string
Type string
Token string
Status string
Validated time.Time
Errors []*Error
}
func (c *wireChallenge) challenge() *Challenge {
v := &Challenge{
URL: c.URL,
Type: c.Type,
Token: c.Token,
Status: c.Status,
Validated: c.Validated,
Errors: c.Errors,
}
if v.Status == "" {
v.Status = StatusUnknown
}
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
Subproblems []Subproblem
}
func (e *wireError) error(h http.Header) *Error {
return &Error{
StatusCode: e.Status,
Type: e.Type,
Detail: e.Detail,
Subproblems: e.Subproblems,
Header: h,
}
}
type wireOrder struct {
Status string
Expires time.Time
Identifiers []AuthzID
NotBefore time.Time
NotAfter time.Time
Error *Error
Authorizations []string
Finalize string
Certificate string
}
func (o *wireOrder) order(url string, retryHeader string) *Order {
return &Order{
URL: url,
Status: o.Status,
Expires: o.Expires,
Identifiers: o.Identifiers,
NotBefore: o.NotBefore,
NotAfter: o.NotAfter,
Error: o.Error,
Authorizations: o.Authorizations,
FinalizeURL: o.Finalize,
CertificateURL: o.Certificate,
RetryAfter: retryAfter(retryHeader),
}
}

63
third_party/crypto/acme/types_test.go vendored Normal file
View 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{
Type: "urn:ietf:params:acme:error:nolimit",
Header: h120,
}
err2 := &Error{
Type: "urn:ietf:params:acme:error:rateLimited",
Header: h120,
}
err3 := &Error{
Type: "urn:ietf:params:acme:error:rateLimited",
Header: nil,
}
err4 := &Error{
Type: "urn:ietf:params:acme:error:rateLimited",
Header: hTime,
}
tt := []struct {
err error
res time.Time
ok bool
}{
{},
{err: errors.New("dummy")},
{err: err1},
{err: err2, res: now.Add(2 * time.Minute), ok: true},
{err: err3, ok: true},
{err: err4, res: now.Add(time.Hour), ok: 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.Equal(test.res) {
t.Errorf("%d: RateLimit(%+v) = %v; want %v", i, test.err, res, test.res)
}
}
}