diff options
| -rw-r--r-- | README.md | 2 | ||||
| -rw-r--r-- | cmd/cashierd/main.go | 18 | ||||
| -rw-r--r-- | server/config/config.go | 24 | ||||
| -rw-r--r-- | vendor/golang.org/x/crypto/acme/acme.go | 946 | ||||
| -rw-r--r-- | vendor/golang.org/x/crypto/acme/autocert/autocert.go | 793 | ||||
| -rw-r--r-- | vendor/golang.org/x/crypto/acme/autocert/cache.go | 130 | ||||
| -rw-r--r-- | vendor/golang.org/x/crypto/acme/autocert/renewal.go | 125 | ||||
| -rw-r--r-- | vendor/golang.org/x/crypto/acme/jws.go | 153 | ||||
| -rw-r--r-- | vendor/golang.org/x/crypto/acme/types.go | 209 | ||||
| -rw-r--r-- | vendor/vendor.json | 12 | 
10 files changed, 2397 insertions, 15 deletions
@@ -103,6 +103,8 @@ For any option that takes a file path as a parameter (e.g. SSH signing key, TLS  - `use_tls` : boolean. If this is set then `tls_key` and `tls_cert` are required.  - `tls_key` : string. Path to the TLS key. See the [note](#a-note-on-files) on files above.  - `tls_cert` : string. Path to the TLS cert. See the [note](#a-note-on-files) on files above. +- `letsencrypt_servername`: string. If set will request a certificate from LetsEncrypt. This should match the expected FQDN of the server. +- `letsencrypt_cachedir: string. Directory to cache the LetsEncrypt certificate.  - `address` : string. IP address to listen on. If unset the server listens on all addresses.  - `port` : int. Port to listen on.  - `user` : string. User to which the server drops privileges to. diff --git a/cmd/cashierd/main.go b/cmd/cashierd/main.go index 7df85e6..12d744d 100644 --- a/cmd/cashierd/main.go +++ b/cmd/cashierd/main.go @@ -18,6 +18,7 @@ import (  	"strings"  	"go4.org/wkfs" +	"golang.org/x/crypto/acme/autocert"  	"golang.org/x/oauth2"  	"github.com/gorilla/csrf" @@ -342,10 +343,19 @@ func main() {  	tlsConfig := &tls.Config{}  	if config.Server.UseTLS { -		tlsConfig.Certificates = make([]tls.Certificate, 1) -		tlsConfig.Certificates[0], err = loadCerts(config.Server.TLSCert, config.Server.TLSKey) -		if err != nil { -			log.Fatal(err) +		if config.Server.LetsEncryptServername != "" { +			m := autocert.Manager{ +				Prompt:     autocert.AcceptTOS, +				Cache:      autocert.DirCache(config.Server.LetsEncryptCache), +				HostPolicy: autocert.HostWhitelist(config.Server.LetsEncryptServername), +			} +			tlsConfig.GetCertificate = m.GetCertificate +		} else { +			tlsConfig.Certificates = make([]tls.Certificate, 1) +			tlsConfig.Certificates[0], err = loadCerts(config.Server.TLSCert, config.Server.TLSKey) +			if err != nil { +				log.Fatal(err) +			}  		}  		l = tls.NewListener(l, tlsConfig)  	} diff --git a/server/config/config.go b/server/config/config.go index 2d821f9..5f3f458 100644 --- a/server/config/config.go +++ b/server/config/config.go @@ -29,17 +29,19 @@ type Database map[string]string  // Server holds the configuration specific to the web server and sessions.  type Server struct { -	UseTLS       bool     `mapstructure:"use_tls"` -	TLSKey       string   `mapstructure:"tls_key"` -	TLSCert      string   `mapstructure:"tls_cert"` -	Addr         string   `mapstructure:"address"` -	Port         int      `mapstructure:"port"` -	User         string   `mapstructure:"user"` -	CookieSecret string   `mapstructure:"cookie_secret"` -	CSRFSecret   string   `mapstructure:"csrf_secret"` -	HTTPLogFile  string   `mapstructure:"http_logfile"` -	Database     Database `mapstructure:"database"` -	Datastore    string   `mapstructure:"datastore"` // Deprecated. TODO: remove. +	UseTLS                bool     `mapstructure:"use_tls"` +	TLSKey                string   `mapstructure:"tls_key"` +	TLSCert               string   `mapstructure:"tls_cert"` +	LetsEncryptServername string   `mapstructure:"letsencrypt_servername"` +	LetsEncryptCache      string   `mapstructure:"letsencrypt_cachedir"` +	Addr                  string   `mapstructure:"address"` +	Port                  int      `mapstructure:"port"` +	User                  string   `mapstructure:"user"` +	CookieSecret          string   `mapstructure:"cookie_secret"` +	CSRFSecret            string   `mapstructure:"csrf_secret"` +	HTTPLogFile           string   `mapstructure:"http_logfile"` +	Database              Database `mapstructure:"database"` +	Datastore             string   `mapstructure:"datastore"` // Deprecated. TODO: remove.  }  // Auth holds the configuration specific to the OAuth provider. diff --git a/vendor/golang.org/x/crypto/acme/acme.go b/vendor/golang.org/x/crypto/acme/acme.go new file mode 100644 index 0000000..8aafada --- /dev/null +++ b/vendor/golang.org/x/crypto/acme/acme.go @@ -0,0 +1,946 @@ +// 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-02 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" +	"crypto" +	"crypto/ecdsa" +	"crypto/elliptic" +	"crypto/rand" +	"crypto/sha256" +	"crypto/tls" +	"crypto/x509" +	"encoding/base64" +	"encoding/hex" +	"encoding/json" +	"encoding/pem" +	"errors" +	"fmt" +	"io" +	"io/ioutil" +	"math/big" +	"net/http" +	"strconv" +	"strings" +	"sync" +	"time" + +	"golang.org/x/net/context" +	"golang.org/x/net/context/ctxhttp" +) + +// LetsEncryptURL is the Directory endpoint of Let's Encrypt CA. +const LetsEncryptURL = "https://acme-v01.api.letsencrypt.org/directory" + +const ( +	maxChainLen = 5       // max depth and breadth of a certificate chain +	maxCertSize = 1 << 20 // max size of a certificate, in bytes +) + +// CertOption is an optional argument type for Client methods which manipulate +// certificate data. +type CertOption interface { +	privateCertOpt() +} + +// WithKey creates an option holding a private/public key pair. +// The private part signs a certificate, and the public part represents the signee. +func WithKey(key crypto.Signer) CertOption { +	return &certOptKey{key} +} + +type certOptKey struct { +	key crypto.Signer +} + +func (*certOptKey) privateCertOpt() {} + +// WithTemplate creates an option for specifying a certificate template. +// See x509.CreateCertificate for template usage details. +// +// In TLSSNIxChallengeCert methods, the template is also used as parent, +// resulting in a self-signed certificate. +// The DNSNames field of t is always overwritten for tls-sni challenge certs. +func WithTemplate(t *x509.Certificate) CertOption { +	return (*certOptTemplate)(t) +} + +type certOptTemplate x509.Certificate + +func (*certOptTemplate) privateCertOpt() {} + +// Client is an ACME client. +// The only required field is Key. An example of creating a client with a new key +// is as follows: +// +// 	key, err := rsa.GenerateKey(rand.Reader, 2048) +// 	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 + +	dirMu sync.Mutex // guards writes to dir +	dir   *Directory // cached result of Client's Discover method +} + +// 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.dirMu.Lock() +	defer c.dirMu.Unlock() +	if c.dir != nil { +		return *c.dir, nil +	} + +	dirURL := c.DirectoryURL +	if dirURL == "" { +		dirURL = LetsEncryptURL +	} +	res, err := ctxhttp.Get(ctx, c.HTTPClient, dirURL) +	if err != nil { +		return Directory{}, err +	} +	defer res.Body.Close() +	if res.StatusCode != http.StatusOK { +		return Directory{}, responseError(res) +	} + +	var v struct { +		Reg    string `json:"new-reg"` +		Authz  string `json:"new-authz"` +		Cert   string `json:"new-cert"` +		Revoke string `json:"revoke-cert"` +		Meta   struct { +			Terms   string   `json:"terms-of-service"` +			Website string   `json:"website"` +			CAA     []string `json:"caa-identities"` +		} +	} +	if json.NewDecoder(res.Body).Decode(&v); err != nil { +		return Directory{}, err +	} +	c.dir = &Directory{ +		RegURL:    v.Reg, +		AuthzURL:  v.Authz, +		CertURL:   v.Cert, +		RevokeURL: v.Revoke, +		Terms:     v.Meta.Terms, +		Website:   v.Meta.Website, +		CAA:       v.Meta.CAA, +	} +	return *c.dir, nil +} + +// CreateCert requests a new certificate using the Certificate Signing Request csr encoded in DER format. +// The exp argument indicates the desired certificate validity duration. CA may issue a certificate +// with a different duration. +// If the bundle argument is true, the returned value will also contain the CA (issuer) certificate chain. +// +// In the case where CA server does not provide the issued certificate in the response, +// CreateCert will poll certURL using c.FetchCert, which will result in additional round-trips. +// In such scenario the caller can cancel the polling with ctx. +// +// CreateCert returns an error if the CA's response or chain was unreasonably large. +// Callers are encouraged to parse the returned value to ensure the certificate is valid and has the expected features. +func (c *Client) CreateCert(ctx context.Context, csr []byte, exp time.Duration, bundle bool) (der [][]byte, certURL string, err error) { +	if _, err := c.Discover(ctx); err != nil { +		return nil, "", err +	} + +	req := struct { +		Resource  string `json:"resource"` +		CSR       string `json:"csr"` +		NotBefore string `json:"notBefore,omitempty"` +		NotAfter  string `json:"notAfter,omitempty"` +	}{ +		Resource: "new-cert", +		CSR:      base64.RawURLEncoding.EncodeToString(csr), +	} +	now := timeNow() +	req.NotBefore = now.Format(time.RFC3339) +	if exp > 0 { +		req.NotAfter = now.Add(exp).Format(time.RFC3339) +	} + +	res, err := postJWS(ctx, c.HTTPClient, c.Key, c.dir.CertURL, req) +	if err != nil { +		return nil, "", err +	} +	defer res.Body.Close() +	if res.StatusCode != http.StatusCreated { +		return nil, "", responseError(res) +	} + +	curl := res.Header.Get("location") // cert permanent URL +	if res.ContentLength == 0 { +		// no cert in the body; poll until we get it +		cert, err := c.FetchCert(ctx, curl, bundle) +		return cert, curl, err +	} +	// slurp issued cert and CA chain, if requested +	cert, err := responseCert(ctx, c.HTTPClient, res, bundle) +	return cert, curl, err +} + +// FetchCert retrieves already issued certificate from the given url, in DER format. +// It retries the request until the certificate is successfully retrieved, +// context is cancelled by the caller or an error response is received. +// +// The returned value will also contain the CA (issuer) certificate if the bundle argument is true. +// +// FetchCert returns an error if the CA's response or chain was unreasonably large. +// Callers are encouraged to parse the returned value to ensure the certificate is valid +// and has expected features. +func (c *Client) FetchCert(ctx context.Context, url string, bundle bool) ([][]byte, error) { +	for { +		res, err := ctxhttp.Get(ctx, c.HTTPClient, url) +		if err != nil { +			return nil, err +		} +		defer res.Body.Close() +		if res.StatusCode == http.StatusOK { +			return responseCert(ctx, c.HTTPClient, res, bundle) +		} +		if res.StatusCode > 299 { +			return nil, responseError(res) +		} +		d := retryAfter(res.Header.Get("retry-after"), 3*time.Second) +		select { +		case <-time.After(d): +			// retry +		case <-ctx.Done(): +			return nil, ctx.Err() +		} +	} +} + +// RevokeCert revokes a previously issued certificate cert, provided in DER format. +// +// The key argument, used to sign the request, must be authorized +// to revoke the certificate. It's up to the CA to decide which keys are authorized. +// For instance, the key pair of the certificate may be authorized. +// If the key is nil, c.Key is used instead. +func (c *Client) RevokeCert(ctx context.Context, key crypto.Signer, cert []byte, reason CRLReasonCode) error { +	if _, err := c.Discover(ctx); err != nil { +		return err +	} + +	body := &struct { +		Resource string `json:"resource"` +		Cert     string `json:"certificate"` +		Reason   int    `json:"reason"` +	}{ +		Resource: "revoke-cert", +		Cert:     base64.RawURLEncoding.EncodeToString(cert), +		Reason:   int(reason), +	} +	if key == nil { +		key = c.Key +	} +	res, err := postJWS(ctx, c.HTTPClient, key, c.dir.RevokeURL, body) +	if err != nil { +		return err +	} +	defer res.Body.Close() +	if res.StatusCode != http.StatusOK { +		return responseError(res) +	} +	return nil +} + +// AcceptTOS always returns true to indicate the acceptance of a CA's Terms of Service +// during account registration. See Register method of Client for more details. +func AcceptTOS(tosURL string) bool { return true } + +// Register creates a new account registration by following the "new-reg" flow. +// It returns registered account. The a argument is not modified. +// +// The registration may require the caller to agree to the CA's Terms of Service (TOS). +// If so, and the account has not indicated the acceptance of the terms (see Account for details), +// Register calls prompt with a TOS URL provided by the CA. Prompt should report +// whether the caller agrees to the terms. To always accept the terms, the caller can use AcceptTOS. +func (c *Client) Register(ctx context.Context, a *Account, prompt func(tosURL string) bool) (*Account, error) { +	if _, err := c.Discover(ctx); err != nil { +		return nil, err +	} + +	var err error +	if a, err = c.doReg(ctx, c.dir.RegURL, "new-reg", a); err != nil { +		return nil, err +	} +	var accept bool +	if a.CurrentTerms != "" && a.CurrentTerms != a.AgreedTerms { +		accept = prompt(a.CurrentTerms) +	} +	if accept { +		a.AgreedTerms = a.CurrentTerms +		a, err = c.UpdateReg(ctx, a) +	} +	return a, err +} + +// GetReg retrieves an existing registration. +// The url argument is an Account URI. +func (c *Client) GetReg(ctx context.Context, url string) (*Account, error) { +	a, err := c.doReg(ctx, url, "reg", nil) +	if err != nil { +		return nil, err +	} +	a.URI = url +	return a, nil +} + +// UpdateReg updates an existing registration. +// It returns an updated account copy. The provided account is not modified. +func (c *Client) UpdateReg(ctx context.Context, a *Account) (*Account, error) { +	uri := a.URI +	a, err := c.doReg(ctx, uri, "reg", a) +	if err != nil { +		return nil, err +	} +	a.URI = uri +	return a, nil +} + +// Authorize performs the initial step in an authorization flow. +// The caller will then need to choose from and perform a set of returned +// challenges using c.Accept in order to successfully complete authorization. +// +// If an authorization has been previously granted, the CA may return +// a valid authorization (Authorization.Status is StatusValid). If so, the caller +// need not fulfill any challenge and can proceed to requesting a certificate. +func (c *Client) Authorize(ctx context.Context, domain string) (*Authorization, error) { +	if _, err := c.Discover(ctx); err != nil { +		return nil, err +	} + +	type authzID struct { +		Type  string `json:"type"` +		Value string `json:"value"` +	} +	req := struct { +		Resource   string  `json:"resource"` +		Identifier authzID `json:"identifier"` +	}{ +		Resource:   "new-authz", +		Identifier: authzID{Type: "dns", Value: domain}, +	} +	res, err := postJWS(ctx, c.HTTPClient, c.Key, c.dir.AuthzURL, req) +	if err != nil { +		return nil, err +	} +	defer res.Body.Close() +	if res.StatusCode != http.StatusCreated { +		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) +	} +	if v.Status != StatusPending && v.Status != StatusValid { +		return nil, fmt.Errorf("acme: unexpected status: %s", v.Status) +	} +	return v.authorization(res.Header.Get("Location")), nil +} + +// 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 := ctxhttp.Get(ctx, c.HTTPClient, url) +	if err != nil { +		return nil, err +	} +	defer res.Body.Close() +	if res.StatusCode != http.StatusOK && res.StatusCode != http.StatusAccepted { +		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 +} + +// RevokeAuthorization relinquishes an existing authorization identified +// by the given URL. +// The url argument is an Authorization.URI value. +// +// If successful, the caller will be required to obtain a new authorization +// using the Authorize method before being able to request a new certificate +// for the domain associated with the authorization. +// +// It does not revoke existing certificates. +func (c *Client) RevokeAuthorization(ctx context.Context, url string) error { +	req := struct { +		Resource string `json:"resource"` +		Status   string `json:"status"` +		Delete   bool   `json:"delete"` +	}{ +		Resource: "authz", +		Status:   "deactivated", +		Delete:   true, +	} +	res, err := postJWS(ctx, c.HTTPClient, c.Key, url, req) +	if err != nil { +		return err +	} +	defer res.Body.Close() +	if res.StatusCode != http.StatusOK { +		return responseError(res) +	} +	return nil +} + +// WaitAuthorization polls an authorization at the given URL +// until it is in one of the final states, StatusValid or StatusInvalid, +// or the context is done. +// +// 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. +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 +		} +	} + +	for { +		res, err := ctxhttp.Get(ctx, c.HTTPClient, url) +		if err != nil { +			return nil, err +		} +		retry := res.Header.Get("retry-after") +		if res.StatusCode != http.StatusOK && res.StatusCode != http.StatusAccepted { +			res.Body.Close() +			if err := sleep(retry, 1); err != nil { +				return nil, err +			} +			continue +		} +		var raw wireAuthz +		err = json.NewDecoder(res.Body).Decode(&raw) +		res.Body.Close() +		if err != nil { +			if err := sleep(retry, 0); err != nil { +				return nil, err +			} +			continue +		} +		if raw.Status == StatusValid { +			return raw.authorization(url), nil +		} +		if raw.Status == StatusInvalid { +			return nil, ErrAuthorizationFailed +		} +		if err := sleep(retry, 0); err != nil { +			return nil, err +		} +	} +} + +// GetChallenge retrieves the current status of an challenge. +// +// 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) +	if err != nil { +		return nil, err +	} +	defer res.Body.Close() +	if res.StatusCode != http.StatusOK && res.StatusCode != http.StatusAccepted { +		return nil, responseError(res) +	} +	v := wireChallenge{URI: url} +	if err := json.NewDecoder(res.Body).Decode(&v); err != nil { +		return nil, fmt.Errorf("acme: invalid response: %v", err) +	} +	return v.challenge(), nil +} + +// Accept informs the server that the client accepts one of its challenges +// previously obtained with c.Authorize. +// +// The server will then perform the validation asynchronously. +func (c *Client) Accept(ctx context.Context, chal *Challenge) (*Challenge, error) { +	auth, err := keyAuth(c.Key.Public(), chal.Token) +	if err != nil { +		return nil, err +	} + +	req := struct { +		Resource string `json:"resource"` +		Type     string `json:"type"` +		Auth     string `json:"keyAuthorization"` +	}{ +		Resource: "challenge", +		Type:     chal.Type, +		Auth:     auth, +	} +	res, err := postJWS(ctx, c.HTTPClient, c.Key, chal.URI, req) +	if err != nil { +		return nil, err +	} +	defer res.Body.Close() +	// Note: the protocol specifies 200 as the expected response code, but +	// letsencrypt seems to be returning 202. +	if res.StatusCode != http.StatusOK && res.StatusCode != http.StatusAccepted { +		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 +} + +// TLSSNI01ChallengeCert creates a certificate for TLS-SNI-01 challenge response. +// Servers can present the certificate to validate the challenge and prove control +// over a domain name. +// +// The implementation is incomplete in that the returned value is a single certificate, +// computed only for Z0 of the key authorization. ACME CAs are expected to update +// their implementations to use the newer version, TLS-SNI-02. +// For more details on TLS-SNI-01 see https://tools.ietf.org/html/draft-ietf-acme-acme-01#section-7.3. +// +// The token argument is a Challenge.Token value. +// If a WithKey option is provided, its private part signs the returned cert, +// and the public part is used to specify the signee. +// If no WithKey option is provided, a new ECDSA key is generated using P-256 curve. +// +// The returned certificate is valid for the next 24 hours and must be presented only when +// the server name of the client hello matches exactly the returned name value. +func (c *Client) TLSSNI01ChallengeCert(token string, opt ...CertOption) (cert tls.Certificate, name string, err error) { +	ka, err := keyAuth(c.Key.Public(), token) +	if err != nil { +		return tls.Certificate{}, "", err +	} +	b := sha256.Sum256([]byte(ka)) +	h := hex.EncodeToString(b[:]) +	name = fmt.Sprintf("%s.%s.acme.invalid", h[:32], h[32:]) +	cert, err = tlsChallengeCert([]string{name}, opt) +	if err != nil { +		return tls.Certificate{}, "", err +	} +	return cert, name, nil +} + +// TLSSNI02ChallengeCert creates a certificate for TLS-SNI-02 challenge response. +// Servers can present the certificate to validate the challenge and prove control +// over a domain name. For more details on TLS-SNI-02 see +// https://tools.ietf.org/html/draft-ietf-acme-acme-03#section-7.3. +// +// The token argument is a Challenge.Token value. +// If a WithKey option is provided, its private part signs the returned cert, +// and the public part is used to specify the signee. +// If no WithKey option is provided, a new ECDSA key is generated using P-256 curve. +// +// The returned certificate is valid for the next 24 hours and must be presented only when +// the server name in the client hello matches exactly the returned name value. +func (c *Client) TLSSNI02ChallengeCert(token string, opt ...CertOption) (cert tls.Certificate, name string, err error) { +	b := sha256.Sum256([]byte(token)) +	h := hex.EncodeToString(b[:]) +	sanA := fmt.Sprintf("%s.%s.token.acme.invalid", h[:32], h[32:]) + +	ka, err := keyAuth(c.Key.Public(), token) +	if err != nil { +		return tls.Certificate{}, "", err +	} +	b = sha256.Sum256([]byte(ka)) +	h = hex.EncodeToString(b[:]) +	sanB := fmt.Sprintf("%s.%s.ka.acme.invalid", h[:32], h[32:]) + +	cert, err = tlsChallengeCert([]string{sanA, sanB}, opt) +	if err != nil { +		return tls.Certificate{}, "", err +	} +	return cert, sanA, nil +} + +// doReg sends all types of registration requests. +// The type of request is identified by typ argument, which is a "resource" +// in the ACME spec terms. +// +// A non-nil acct argument indicates whether the intention is to mutate data +// of the Account. Only Contact and Agreement of its fields are used +// in such cases. +func (c *Client) doReg(ctx context.Context, url string, typ string, acct *Account) (*Account, error) { +	req := struct { +		Resource  string   `json:"resource"` +		Contact   []string `json:"contact,omitempty"` +		Agreement string   `json:"agreement,omitempty"` +	}{ +		Resource: typ, +	} +	if acct != nil { +		req.Contact = acct.Contact +		req.Agreement = acct.AgreedTerms +	} +	res, err := postJWS(ctx, c.HTTPClient, c.Key, url, req) +	if err != nil { +		return nil, err +	} +	defer res.Body.Close() +	if res.StatusCode < 200 || res.StatusCode > 299 { +		return nil, responseError(res) +	} + +	var v struct { +		Contact        []string +		Agreement      string +		Authorizations string +		Certificates   string +	} +	if err := json.NewDecoder(res.Body).Decode(&v); err != nil { +		return nil, fmt.Errorf("acme: invalid response: %v", err) +	} +	var tos string +	if v := linkHeader(res.Header, "terms-of-service"); len(v) > 0 { +		tos = v[0] +	} +	var authz string +	if v := linkHeader(res.Header, "next"); len(v) > 0 { +		authz = v[0] +	} +	return &Account{ +		URI:            res.Header.Get("Location"), +		Contact:        v.Contact, +		AgreedTerms:    v.Agreement, +		CurrentTerms:   tos, +		Authz:          authz, +		Authorizations: v.Authorizations, +		Certificates:   v.Certificates, +	}, nil +} + +func responseCert(ctx context.Context, client *http.Client, res *http.Response, bundle bool) ([][]byte, error) { +	b, err := ioutil.ReadAll(io.LimitReader(res.Body, maxCertSize+1)) +	if err != nil { +		return nil, fmt.Errorf("acme: response stream: %v", err) +	} +	if len(b) > maxCertSize { +		return nil, errors.New("acme: certificate is too big") +	} +	cert := [][]byte{b} +	if !bundle { +		return cert, nil +	} + +	// Append CA chain cert(s). +	// At least one is required according to the spec: +	// https://tools.ietf.org/html/draft-ietf-acme-acme-03#section-6.3.1 +	up := linkHeader(res.Header, "up") +	if len(up) == 0 { +		return nil, errors.New("acme: rel=up link not found") +	} +	if len(up) > maxChainLen { +		return nil, errors.New("acme: rel=up link is too large") +	} +	for _, url := range up { +		cc, err := chainCert(ctx, client, url, 0) +		if err != nil { +			return nil, err +		} +		cert = append(cert, cc...) +	} +	return cert, 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 := struct { +		Status int +		Type   string +		Detail string +	}{ +		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 &Error{ +		StatusCode:  e.Status, +		ProblemType: e.Type, +		Detail:      e.Detail, +		Header:      resp.Header, +	} +} + +// chainCert fetches CA certificate chain recursively by following "up" links. +// Each recursive call increments the depth by 1, resulting in an error +// if the recursion level reaches maxChainLen. +// +// First chainCert call starts with depth of 0. +func chainCert(ctx context.Context, client *http.Client, 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) +	if err != nil { +		return nil, err +	} +	defer res.Body.Close() +	if res.StatusCode != http.StatusOK { +		return nil, responseError(res) +	} +	b, err := ioutil.ReadAll(io.LimitReader(res.Body, maxCertSize+1)) +	if err != nil { +		return nil, err +	} +	if len(b) > maxCertSize { +		return nil, errors.New("acme: certificate is too big") +	} +	chain := [][]byte{b} + +	uplink := linkHeader(res.Header, "up") +	if len(uplink) > maxChainLen { +		return nil, errors.New("acme: certificate chain is too large") +	} +	for _, up := range uplink { +		cc, err := chainCert(ctx, client, up, depth+1) +		if err != nil { +			return nil, err +		} +		chain = append(chain, cc...) +	} + +	return chain, nil +} + +// postJWS signs the body with the given key and POSTs it to the provided url. +// The body argument must be JSON-serializable. +func postJWS(ctx context.Context, client *http.Client, key crypto.Signer, url string, body interface{}) (*http.Response, error) { +	nonce, err := fetchNonce(ctx, client, url) +	if err != nil { +		return nil, err +	} +	b, err := jwsEncodeJSON(body, key, nonce) +	if err != nil { +		return nil, err +	} +	return ctxhttp.Post(ctx, client, url, "application/jose+json", bytes.NewReader(b)) +} + +func fetchNonce(ctx context.Context, client *http.Client, url string) (string, error) { +	resp, err := ctxhttp.Head(ctx, client, url) +	if err != nil { +		return "", nil +	} +	defer resp.Body.Close() +	enc := resp.Header.Get("replay-nonce") +	if enc == "" { +		return "", errors.New("acme: nonce not found") +	} +	return enc, nil +} + +// linkHeader returns URI-Reference values of all Link headers +// with relation-type rel. +// See https://tools.ietf.org/html/rfc5988#section-5 for details. +func linkHeader(h http.Header, rel string) []string { +	var links []string +	for _, v := range h["Link"] { +		parts := strings.Split(v, ";") +		for _, p := range parts { +			p = strings.TrimSpace(p) +			if !strings.HasPrefix(p, "rel=") { +				continue +			} +			if v := strings.Trim(p[4:], `"`); v == rel { +				links = append(links, strings.Trim(parts[0], "<>")) +			} +		} +	} +	return links +} + +// 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. +func retryAfter(v string, d time.Duration) time.Duration { +	if i, err := strconv.Atoi(v); err == nil { +		return time.Duration(i) * time.Second +	} +	t, err := http.ParseTime(v) +	if err != nil { +		return d +	} +	return t.Sub(timeNow()) +} + +// 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 +} + +// tlsChallengeCert creates a temporary certificate for TLS-SNI challenges +// with the given SANs and auto-generated public/private key pair. +// To create a cert with a custom key pair, specify WithKey option. +func tlsChallengeCert(san []string, opt []CertOption) (tls.Certificate, error) { +	var ( +		key  crypto.Signer +		tmpl *x509.Certificate +	) +	for _, o := range opt { +		switch o := o.(type) { +		case *certOptKey: +			if key != nil { +				return tls.Certificate{}, errors.New("acme: duplicate key option") +			} +			key = o.key +		case *certOptTemplate: +			var t = *(*x509.Certificate)(o) // shallow copy is ok +			tmpl = &t +		default: +			// package's fault, if we let this happen: +			panic(fmt.Sprintf("unsupported option type %T", o)) +		} +	} +	if key == nil { +		var err error +		if key, err = ecdsa.GenerateKey(elliptic.P256(), rand.Reader); err != nil { +			return tls.Certificate{}, err +		} +	} +	if tmpl == nil { +		tmpl = &x509.Certificate{ +			SerialNumber:          big.NewInt(1), +			NotBefore:             time.Now(), +			NotAfter:              time.Now().Add(24 * time.Hour), +			BasicConstraintsValid: true, +			KeyUsage:              x509.KeyUsageKeyEncipherment, +		} +	} +	tmpl.DNSNames = san + +	der, err := x509.CreateCertificate(rand.Reader, tmpl, tmpl, key.Public(), key) +	if err != nil { +		return tls.Certificate{}, err +	} +	return tls.Certificate{ +		Certificate: [][]byte{der}, +		PrivateKey:  key, +	}, nil +} + +// encodePEM returns b encoded as PEM with block of type typ. +func encodePEM(typ string, b []byte) []byte { +	pb := &pem.Block{Type: typ, Bytes: b} +	return pem.EncodeToMemory(pb) +} + +// timeNow is useful for testing for fixed current time. +var timeNow = time.Now diff --git a/vendor/golang.org/x/crypto/acme/autocert/autocert.go b/vendor/golang.org/x/crypto/acme/autocert/autocert.go new file mode 100644 index 0000000..4b15816 --- /dev/null +++ b/vendor/golang.org/x/crypto/acme/autocert/autocert.go @@ -0,0 +1,793 @@ +// 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 autocert 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 autocert + +import ( +	"bytes" +	"crypto" +	"crypto/ecdsa" +	"crypto/elliptic" +	"crypto/rand" +	"crypto/rsa" +	"crypto/tls" +	"crypto/x509" +	"crypto/x509/pkix" +	"encoding/pem" +	"errors" +	"fmt" +	"io" +	mathrand "math/rand" +	"net/http" +	"strconv" +	"strings" +	"sync" +	"time" + +	"golang.org/x/crypto/acme" +	"golang.org/x/net/context" +) + +// pseudoRand is safe for concurrent use. +var pseudoRand *lockedMathRand + +func init() { +	src := mathrand.NewSource(timeNow().UnixNano()) +	pseudoRand = &lockedMathRand{rnd: mathrand.New(src)} +} + +// AcceptTOS always returns true to indicate the acceptance of a CA 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. +// It returns a non-nil error if the host should be rejected. +// The returned error is accessible via tls.Conn.Handshake and its callers. +// See Manager's HostPolicy field and GetCertificate method docs for more details. +type HostPolicy func(ctx context.Context, host string) error + +// HostWhitelist returns a policy where only the specified host names are allowed. +// Only exact matches are currently supported. Subdomains, regexp or wildcard +// will not match. +func HostWhitelist(hosts ...string) HostPolicy { +	whitelist := make(map[string]bool, len(hosts)) +	for _, h := range hosts { +		whitelist[h] = true +	} +	return func(_ context.Context, host string) error { +		if !whitelist[host] { +			return errors.New("acme/autocert: host not configured") +		} +		return nil +	} +} + +// defaultHostPolicy is used when Manager.HostPolicy is not set. +func defaultHostPolicy(context.Context, string) error { +	return nil +} + +// Manager is a stateful certificate manager built on top of acme.Client. +// 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 { +	// Prompt specifies a callback function to conditionally accept a CA's Terms of Service (TOS). +	// The registration may require the caller to agree to the CA's TOS. +	// If so, Manager calls Prompt with a TOS URL provided by the CA. Prompt should report +	// whether the caller agrees to the terms. +	// +	// To always accept the terms, the callers can use AcceptTOS. +	Prompt func(tosURL string) bool + +	// Cache optionally stores and retrieves previously-obtained certificates. +	// If nil, certs will only be cached for the lifetime of the Manager. +	// +	// Manager passes the Cache certificates data encoded in PEM, with private/public +	// parts combined in a single Cache.Put call, private key first. +	Cache Cache + +	// HostPolicy controls which domains the Manager will attempt +	// to retrieve new certificates for. It does not affect cached certs. +	// +	// If non-nil, HostPolicy is called before requesting a new cert. +	// If nil, all hosts are currently allowed. This is not recommended, +	// as it opens a potential attack where clients connect to a server +	// by IP address and pretend to be asking for an incorrect host name. +	// Manager will attempt to obtain a certificate for that host, incorrectly, +	// eventually reaching the CA's rate limit for certificate requests +	// and making it impossible to obtain actual certificates. +	// +	// See GetCertificate for more details. +	HostPolicy HostPolicy + +	// RenewBefore optionally specifies how early certificates should +	// be renewed before they expire. +	// +	// If zero, they're renewed 1 week before expiration. +	RenewBefore time.Duration + +	// Client is used to perform low-level operations, such as account registration +	// and requesting new certificates. +	// If Client is nil, a zero-value acme.Client is used with acme.LetsEncryptURL +	// directory endpoint and a newly-generated ECDSA P-256 key. +	// +	// Mutating the field after the first call of GetCertificate method will have no effect. +	Client *acme.Client + +	// Email optionally specifies a contact email address. +	// This is used by CAs, such as Let's Encrypt, to notify about problems +	// with issued certificates. +	// +	// If the Client's account key is already registered, Email is not used. +	Email string + +	// ForceRSA makes the Manager generate certificates with 2048-bit RSA keys. +	// +	// If false, a default is used. Currently the default +	// is EC-based keys using the P-256 curve. +	ForceRSA bool + +	clientMu sync.Mutex +	client   *acme.Client // initialized by acmeClient method + +	stateMu sync.Mutex +	state   map[string]*certState // keyed by domain name + +	// tokenCert is keyed by token domain name, which matches server name +	// of ClientHello. Keys always have ".acme.invalid" suffix. +	tokenCertMu sync.RWMutex +	tokenCert   map[string]*tls.Certificate + +	// renewal tracks the set of domains currently running renewal timers. +	// It is keyed by domain name. +	renewalMu sync.Mutex +	renewal   map[string]*domainRenewal +} + +// GetCertificate implements the tls.Config.GetCertificate hook. +// It provides a TLS certificate for hello.ServerName host, including answering +// *.acme.invalid (TLS-SNI) challenges. All other fields of hello are ignored. +// +// If m.HostPolicy is non-nil, GetCertificate calls the policy before requesting +// a new cert. A non-nil error returned from m.HostPolicy halts TLS negotiation. +// 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) { +	name := hello.ServerName +	if name == "" { +		return nil, errors.New("acme/autocert: missing server name") +	} + +	// check whether this is a token cert requested for TLS-SNI challenge +	if strings.HasSuffix(name, ".acme.invalid") { +		m.tokenCertMu.RLock() +		defer m.tokenCertMu.RUnlock() +		if cert := m.tokenCert[name]; cert != nil { +			return cert, nil +		} +		if cert, err := m.cacheGet(name); err == nil { +			return cert, nil +		} +		// TODO: cache error results? +		return nil, fmt.Errorf("acme/autocert: no token cert for %q", name) +	} + +	// regular domain +	name = strings.TrimSuffix(name, ".") // golang.org/issue/18114 +	cert, err := m.cert(name) +	if err == nil { +		return cert, nil +	} +	if err != ErrCacheMiss { +		return nil, err +	} + +	// first-time +	ctx := context.Background() // TODO: use a deadline? +	if err := m.hostPolicy()(ctx, name); err != nil { +		return nil, err +	} +	cert, err = m.createCert(ctx, name) +	if err != nil { +		return nil, err +	} +	m.cachePut(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) { +	m.stateMu.Lock() +	if s, ok := m.state[name]; ok { +		m.stateMu.Unlock() +		s.RLock() +		defer s.RUnlock() +		return s.tlscert() +	} +	defer m.stateMu.Unlock() +	cert, err := m.cacheGet(name) +	if err != nil { +		return nil, err +	} +	signer, ok := cert.PrivateKey.(crypto.Signer) +	if !ok { +		return nil, errors.New("acme/autocert: private key cannot sign") +	} +	if m.state == nil { +		m.state = make(map[string]*certState) +	} +	s := &certState{ +		key:  signer, +		cert: cert.Certificate, +		leaf: cert.Leaf, +	} +	m.state[name] = s +	go m.renew(name, s.key, s.leaf.NotAfter) +	return cert, nil +} + +// cacheGet always returns a valid certificate, or an error otherwise. +func (m *Manager) cacheGet(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 +	} + +	// 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") +	} +	privKey, err := parsePrivateKey(priv.Bytes) +	if err != nil { +		return nil, err +	} + +	// public +	var pubDER [][]byte +	for len(pub) > 0 { +		var b *pem.Block +		b, pub = pem.Decode(pub) +		if b == nil { +			break +		} +		pubDER = append(pubDER, b.Bytes) +	} +	if len(pub) > 0 { +		return nil, errors.New("acme/autocert: invalid public key") +	} + +	// verify and create TLS cert +	leaf, err := validCert(domain, pubDER, privKey) +	if err != nil { +		return nil, err +	} +	tlscert := &tls.Certificate{ +		Certificate: pubDER, +		PrivateKey:  privKey, +		Leaf:        leaf, +	} +	return tlscert, nil +} + +func (m *Manager) cachePut(domain string, tlscert *tls.Certificate) error { +	if m.Cache == nil { +		return nil +	} + +	// contains PEM-encoded data +	var buf bytes.Buffer + +	// private +	switch key := tlscert.PrivateKey.(type) { +	case *ecdsa.PrivateKey: +		if err := encodeECDSAKey(&buf, key); err != nil { +			return err +		} +	case *rsa.PrivateKey: +		b := x509.MarshalPKCS1PrivateKey(key) +		pb := &pem.Block{Type: "RSA PRIVATE KEY", Bytes: b} +		if err := pem.Encode(&buf, pb); err != nil { +			return err +		} +	default: +		return errors.New("acme/autocert: unknown private key type") +	} + +	// public +	for _, b := range tlscert.Certificate { +		pb := &pem.Block{Type: "CERTIFICATE", Bytes: b} +		if err := pem.Encode(&buf, pb); err != nil { +			return err +		} +	} + +	// TODO: might want to define a cache timeout on m +	ctx := context.Background() +	return m.Cache.Put(ctx, domain, buf.Bytes()) +} + +func encodeECDSAKey(w io.Writer, key *ecdsa.PrivateKey) error { +	b, err := x509.MarshalECPrivateKey(key) +	if err != nil { +		return err +	} +	pb := &pem.Block{Type: "EC PRIVATE KEY", Bytes: b} +	return pem.Encode(w, pb) +} + +// createCert starts the domain ownership verification and returns a certificate +// for that domain upon success. +// +// If the domain is already being verified, it waits for the existing verification to complete. +// Either way, createCert blocks for the duration of the whole process. +func (m *Manager) createCert(ctx context.Context, domain string) (*tls.Certificate, error) { +	// TODO: maybe rewrite this whole piece using sync.Once +	state, err := m.certState(domain) +	if err != nil { +		return nil, err +	} +	// state may exist if another goroutine is already working on it +	// in which case just wait for it to finish +	if !state.locked { +		state.RLock() +		defer state.RUnlock() +		return state.tlscert() +	} + +	// We are the first; state is locked. +	// Unblock the readers when domain ownership is verified +	// and the we got the cert or the process failed. +	defer state.Unlock() +	state.locked = false + +	der, leaf, err := m.authorizedCert(ctx, state.key, domain) +	if err != nil { +		return nil, err +	} +	state.cert = der +	state.leaf = leaf +	go m.renew(domain, state.key, state.leaf.NotAfter) +	return state.tlscert() +} + +// certState returns a new or existing certState. +// If a new certState is returned, state.exist is false and the state is locked. +// The returned error is non-nil only in the case where a new state could not be created. +func (m *Manager) certState(domain string) (*certState, error) { +	m.stateMu.Lock() +	defer m.stateMu.Unlock() +	if m.state == nil { +		m.state = make(map[string]*certState) +	} +	// existing state +	if state, ok := m.state[domain]; ok { +		return state, nil +	} + +	// new locked state +	var ( +		err error +		key crypto.Signer +	) +	if m.ForceRSA { +		key, err = rsa.GenerateKey(rand.Reader, 2048) +	} else { +		key, err = ecdsa.GenerateKey(elliptic.P256(), rand.Reader) +	} +	if err != nil { +		return nil, err +	} + +	state := &certState{ +		key:    key, +		locked: true, +	} +	state.Lock() // will be unlocked by m.certState caller +	m.state[domain] = state +	return state, nil +} + +// 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 +	} +	client, err := m.acmeClient(ctx) +	if err != nil { +		return nil, nil, err +	} +	csr, err := certRequest(key, domain) +	if err != nil { +		return nil, nil, err +	} +	der, _, err = client.CreateCert(ctx, csr, 0, true) +	if err != nil { +		return nil, nil, err +	} +	leaf, err = validCert(domain, der, key) +	if err != nil { +		return nil, nil, err +	} +	return der, leaf, nil +} + +// verify starts a new identifier (domain) authorization flow. +// It prepares a challenge response and then blocks until the authorization +// is marked as "completed" by the CA (either succeeded or failed). +// +// verify returns nil iff the verification was successful. +func (m *Manager) verify(ctx context.Context, domain string) error { +	client, err := m.acmeClient(ctx) +	if err != nil { +		return err +	} + +	// start domain authorization and get the challenge +	authz, err := client.Authorize(ctx, domain) +	if err != nil { +		return err +	} +	// maybe don't need to at all +	if authz.Status == acme.StatusValid { +		return nil +	} + +	// pick a challenge: prefer tls-sni-02 over tls-sni-01 +	// TODO: consider authz.Combinations +	var chal *acme.Challenge +	for _, c := range authz.Challenges { +		if c.Type == "tls-sni-02" { +			chal = c +			break +		} +		if c.Type == "tls-sni-01" { +			chal = c +		} +	} +	if chal == nil { +		return errors.New("acme/autocert: no supported challenge type found") +	} + +	// create a token cert for the challenge response +	var ( +		cert tls.Certificate +		name string +	) +	switch chal.Type { +	case "tls-sni-01": +		cert, name, err = client.TLSSNI01ChallengeCert(chal.Token) +	case "tls-sni-02": +		cert, name, err = client.TLSSNI02ChallengeCert(chal.Token) +	default: +		err = fmt.Errorf("acme/autocert: unknown challenge type %q", chal.Type) +	} +	if err != nil { +		return err +	} +	m.putTokenCert(name, &cert) +	defer func() { +		// verification has ended at this point +		// don't need token cert anymore +		go m.deleteTokenCert(name) +	}() + +	// ready to fulfill the challenge +	if _, err := client.Accept(ctx, chal); err != nil { +		return err +	} +	// wait for the CA to validate +	_, err = client.WaitAuthorization(ctx, authz.URI) +	return err +} + +// 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) { +	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) +} + +// deleteTokenCert removes the token certificate for the specified domain name +// from both m.tokenCert map and m.Cache. +func (m *Manager) deleteTokenCert(name string) { +	m.tokenCertMu.Lock() +	defer m.tokenCertMu.Unlock() +	delete(m.tokenCert, name) +	if m.Cache != nil { +		m.Cache.Delete(context.Background(), name) +	} +} + +// renew starts a cert renewal timer loop, one per domain. +// +// The loop is scheduled in two cases: +// - a cert was fetched from cache for the first time (wasn't in m.state) +// - a new cert was created by m.createCert +// +// The key argument is a certificate private key. +// The exp argument is the cert expiration time (NotAfter). +func (m *Manager) renew(domain string, key crypto.Signer, exp time.Time) { +	m.renewalMu.Lock() +	defer m.renewalMu.Unlock() +	if m.renewal[domain] != nil { +		// another goroutine is already on it +		return +	} +	if m.renewal == nil { +		m.renewal = make(map[string]*domainRenewal) +	} +	dr := &domainRenewal{m: m, domain: domain, key: key} +	m.renewal[domain] = dr +	dr.start(exp) +} + +// stopRenew stops all currently running cert renewal timers. +// The timers are not restarted during the lifetime of the Manager. +func (m *Manager) stopRenew() { +	m.renewalMu.Lock() +	defer m.renewalMu.Unlock() +	for name, dr := range m.renewal { +		delete(m.renewal, name) +		dr.stop() +	} +} + +func (m *Manager) accountKey(ctx context.Context) (crypto.Signer, error) { +	const keyName = "acme_account.key" + +	genKey := func() (*ecdsa.PrivateKey, error) { +		return ecdsa.GenerateKey(elliptic.P256(), rand.Reader) +	} + +	if m.Cache == nil { +		return genKey() +	} + +	data, err := m.Cache.Get(ctx, keyName) +	if err == ErrCacheMiss { +		key, err := genKey() +		if err != nil { +			return nil, err +		} +		var buf bytes.Buffer +		if err := encodeECDSAKey(&buf, key); err != nil { +			return nil, err +		} +		if err := m.Cache.Put(ctx, keyName, buf.Bytes()); err != nil { +			return nil, err +		} +		return key, nil +	} +	if err != nil { +		return nil, err +	} + +	priv, _ := pem.Decode(data) +	if priv == nil || !strings.Contains(priv.Type, "PRIVATE") { +		return nil, errors.New("acme/autocert: invalid account key found in cache") +	} +	return parsePrivateKey(priv.Bytes) +} + +func (m *Manager) acmeClient(ctx context.Context) (*acme.Client, error) { +	m.clientMu.Lock() +	defer m.clientMu.Unlock() +	if m.client != nil { +		return m.client, nil +	} + +	client := m.Client +	if client == nil { +		client = &acme.Client{DirectoryURL: acme.LetsEncryptURL} +	} +	if client.Key == nil { +		var err error +		client.Key, err = m.accountKey(ctx) +		if err != nil { +			return nil, err +		} +	} +	var contact []string +	if m.Email != "" { +		contact = []string{"mailto:" + m.Email} +	} +	a := &acme.Account{Contact: contact} +	_, err := client.Register(ctx, a, m.Prompt) +	if ae, ok := err.(*acme.Error); err == nil || ok && ae.StatusCode == http.StatusConflict { +		// conflict indicates the key is already registered +		m.client = client +		err = nil +	} +	return m.client, err +} + +func (m *Manager) hostPolicy() HostPolicy { +	if m.HostPolicy != nil { +		return m.HostPolicy +	} +	return defaultHostPolicy +} + +func (m *Manager) renewBefore() time.Duration { +	if m.RenewBefore > maxRandRenew { +		return m.RenewBefore +	} +	return 7 * 24 * time.Hour // 1 week +} + +// certState is ready when its mutex is unlocked for reading. +type certState struct { +	sync.RWMutex +	locked bool              // locked for read/write +	key    crypto.Signer     // private key for cert +	cert   [][]byte          // DER encoding +	leaf   *x509.Certificate // parsed cert[0]; always non-nil if cert != nil +} + +// tlscert creates a tls.Certificate from s.key and s.cert. +// Callers should wrap it in s.RLock() and s.RUnlock(). +func (s *certState) tlscert() (*tls.Certificate, error) { +	if s.key == nil { +		return nil, errors.New("acme/autocert: missing signer") +	} +	if len(s.cert) == 0 { +		return nil, errors.New("acme/autocert: missing certificate") +	} +	return &tls.Certificate{ +		PrivateKey:  s.key, +		Certificate: s.cert, +		Leaf:        s.leaf, +	}, nil +} + +// certRequest creates a certificate request for the given common name cn +// and optional SANs. +func certRequest(key crypto.Signer, cn string, san ...string) ([]byte, error) { +	req := &x509.CertificateRequest{ +		Subject:  pkix.Name{CommonName: cn}, +		DNSNames: san, +	} +	return x509.CreateCertificateRequest(rand.Reader, req, key) +} + +// Attempt to parse the given private key DER block. OpenSSL 0.9.8 generates +// PKCS#1 private keys by default, while OpenSSL 1.0.0 generates PKCS#8 keys. +// OpenSSL ecparam generates SEC1 EC private keys for ECDSA. We try all three. +// +// Inspired by parsePrivateKey in crypto/tls/tls.go. +func parsePrivateKey(der []byte) (crypto.Signer, error) { +	if key, err := x509.ParsePKCS1PrivateKey(der); err == nil { +		return key, nil +	} +	if key, err := x509.ParsePKCS8PrivateKey(der); err == nil { +		switch key := key.(type) { +		case *rsa.PrivateKey: +			return key, nil +		case *ecdsa.PrivateKey: +			return key, nil +		default: +			return nil, errors.New("acme/autocert: unknown private key type in PKCS#8 wrapping") +		} +	} +	if key, err := x509.ParseECPrivateKey(der); err == nil { +		return key, nil +	} + +	return nil, errors.New("acme/autocert: failed to parse private key") +} + +// validCert parses a cert chain provided as der argument and verifies the leaf, der[0], +// corresponds to the private key, as well as the domain match and expiration dates. +// It doesn't do any revocation checking. +// +// The returned value is the verified leaf cert. +func validCert(domain string, der [][]byte, key crypto.Signer) (leaf *x509.Certificate, err error) { +	// parse public part(s) +	var n int +	for _, b := range der { +		n += len(b) +	} +	pub := make([]byte, n) +	n = 0 +	for _, b := range der { +		n += copy(pub[n:], b) +	} +	x509Cert, err := x509.ParseCertificates(pub) +	if len(x509Cert) == 0 { +		return nil, errors.New("acme/autocert: no public key found") +	} +	// verify the leaf is not expired and matches the domain name +	leaf = x509Cert[0] +	now := timeNow() +	if now.Before(leaf.NotBefore) { +		return nil, errors.New("acme/autocert: certificate is not valid yet") +	} +	if now.After(leaf.NotAfter) { +		return nil, errors.New("acme/autocert: expired certificate") +	} +	if err := leaf.VerifyHostname(domain); err != nil { +		return nil, err +	} +	// ensure the leaf corresponds to the private key +	switch pub := leaf.PublicKey.(type) { +	case *rsa.PublicKey: +		prv, ok := key.(*rsa.PrivateKey) +		if !ok { +			return nil, errors.New("acme/autocert: private key type does not match public key type") +		} +		if pub.N.Cmp(prv.N) != 0 { +			return nil, errors.New("acme/autocert: private key does not match public key") +		} +	case *ecdsa.PublicKey: +		prv, ok := key.(*ecdsa.PrivateKey) +		if !ok { +			return nil, errors.New("acme/autocert: private key type does not match public key type") +		} +		if pub.X.Cmp(prv.X) != 0 || pub.Y.Cmp(prv.Y) != 0 { +			return nil, errors.New("acme/autocert: private key does not match public key") +		} +	default: +		return nil, errors.New("acme/autocert: unknown public key algorithm") +	} +	return leaf, nil +} + +func retryAfter(v string) time.Duration { +	if i, err := strconv.Atoi(v); err == nil { +		return time.Duration(i) * time.Second +	} +	if t, err := http.ParseTime(v); err == nil { +		return t.Sub(timeNow()) +	} +	return time.Second +} + +type lockedMathRand struct { +	sync.Mutex +	rnd *mathrand.Rand +} + +func (r *lockedMathRand) int63n(max int64) int64 { +	r.Lock() +	n := r.rnd.Int63n(max) +	r.Unlock() +	return n +} + +// for easier testing +var timeNow = time.Now diff --git a/vendor/golang.org/x/crypto/acme/autocert/cache.go b/vendor/golang.org/x/crypto/acme/autocert/cache.go new file mode 100644 index 0000000..1c67f6c --- /dev/null +++ b/vendor/golang.org/x/crypto/acme/autocert/cache.go @@ -0,0 +1,130 @@ +// 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 autocert + +import ( +	"errors" +	"io/ioutil" +	"os" +	"path/filepath" + +	"golang.org/x/net/context" +) + +// ErrCacheMiss is returned when a certificate is not found in cache. +var ErrCacheMiss = errors.New("acme/autocert: certificate cache miss") + +// Cache is used by Manager to store and retrieve previously obtained certificates +// as opaque data. +// +// The key argument of the methods refers to a domain name but need not be an FQDN. +// Cache implementations should not rely on the key naming pattern. +type Cache interface { +	// Get returns a certificate data for the specified key. +	// If there's no such key, Get returns ErrCacheMiss. +	Get(ctx context.Context, key string) ([]byte, error) + +	// Put stores the data in the cache under the specified key. +	// Inderlying implementations may use any data storage format, +	// as long as the reverse operation, Get, results in the original data. +	Put(ctx context.Context, key string, data []byte) error + +	// Delete removes a certificate data from the cache under the specified key. +	// If there's no such key in the cache, Delete returns nil. +	Delete(ctx context.Context, key string) error +} + +// DirCache implements Cache using a directory on the local filesystem. +// If the directory does not exist, it will be created with 0700 permissions. +type DirCache string + +// Get reads a certificate data from the specified file name. +func (d DirCache) Get(ctx context.Context, name string) ([]byte, error) { +	name = filepath.Join(string(d), name) +	var ( +		data []byte +		err  error +		done = make(chan struct{}) +	) +	go func() { +		data, err = ioutil.ReadFile(name) +		close(done) +	}() +	select { +	case <-ctx.Done(): +		return nil, ctx.Err() +	case <-done: +	} +	if os.IsNotExist(err) { +		return nil, ErrCacheMiss +	} +	return data, err +} + +// Put writes the certificate data to the specified file name. +// The file will be created with 0600 permissions. +func (d DirCache) Put(ctx context.Context, name string, data []byte) error { +	if err := os.MkdirAll(string(d), 0700); err != nil { +		return err +	} + +	done := make(chan struct{}) +	var err error +	go func() { +		defer close(done) +		var tmp string +		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 +		} +		name = filepath.Join(string(d), name) +		err = os.Rename(tmp, name) +	}() +	select { +	case <-ctx.Done(): +		return ctx.Err() +	case <-done: +	} +	return err +} + +// Delete removes the specified file name. +func (d DirCache) Delete(ctx context.Context, name string) error { +	name = filepath.Join(string(d), name) +	var ( +		err  error +		done = make(chan struct{}) +	) +	go func() { +		err = os.Remove(name) +		close(done) +	}() +	select { +	case <-ctx.Done(): +		return ctx.Err() +	case <-done: +	} +	if err != nil && !os.IsNotExist(err) { +		return err +	} +	return nil +} + +// writeTempFile writes b to a temporary file, closes the file and returns its path. +func (d DirCache) writeTempFile(prefix string, b []byte) (string, error) { +	// TempFile uses 0600 permissions +	f, err := ioutil.TempFile(string(d), prefix) +	if err != nil { +		return "", err +	} +	if _, err := f.Write(b); err != nil { +		f.Close() +		return "", err +	} +	return f.Name(), f.Close() +} diff --git a/vendor/golang.org/x/crypto/acme/autocert/renewal.go b/vendor/golang.org/x/crypto/acme/autocert/renewal.go new file mode 100644 index 0000000..1a5018c --- /dev/null +++ b/vendor/golang.org/x/crypto/acme/autocert/renewal.go @@ -0,0 +1,125 @@ +// 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 autocert + +import ( +	"crypto" +	"sync" +	"time" + +	"golang.org/x/net/context" +) + +// maxRandRenew is a maximum deviation from Manager.RenewBefore. +const maxRandRenew = time.Hour + +// domainRenewal tracks the state used by the periodic timers +// renewing a single domain's cert. +type domainRenewal struct { +	m      *Manager +	domain string +	key    crypto.Signer + +	timerMu sync.Mutex +	timer   *time.Timer +} + +// start starts a cert renewal timer at the time +// defined by the certificate expiration time exp. +// +// If the timer is already started, calling start is a noop. +func (dr *domainRenewal) start(exp time.Time) { +	dr.timerMu.Lock() +	defer dr.timerMu.Unlock() +	if dr.timer != nil { +		return +	} +	dr.timer = time.AfterFunc(dr.next(exp), dr.renew) +} + +// stop stops the cert renewal timer. +// If the timer is already stopped, calling stop is a noop. +func (dr *domainRenewal) stop() { +	dr.timerMu.Lock() +	defer dr.timerMu.Unlock() +	if dr.timer == nil { +		return +	} +	dr.timer.Stop() +	dr.timer = nil +} + +// renew is called periodically by a timer. +// The first renew call is kicked off by dr.start. +func (dr *domainRenewal) renew() { +	dr.timerMu.Lock() +	defer dr.timerMu.Unlock() +	if dr.timer == nil { +		return +	} + +	ctx, cancel := context.WithTimeout(context.Background(), 10*time.Minute) +	defer cancel() +	// TODO: rotate dr.key at some point? +	next, err := dr.do(ctx) +	if err != nil { +		next = maxRandRenew / 2 +		next += time.Duration(pseudoRand.int63n(int64(next))) +	} +	dr.timer = time.AfterFunc(next, dr.renew) +	testDidRenewLoop(next, err) +} + +// do is similar to Manager.createCert but it doesn't lock a Manager.state item. +// Instead, it requests a new certificate independently and, upon success, +// replaces dr.m.state item with a new one and updates cache for the given domain. +// +// It may return immediately if the expiration date of the currently cached cert +// is far enough in the future. +// +// The returned value is a time interval after which the renewal should occur again. +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 { +		next := dr.next(tlscert.Leaf.NotAfter) +		if next > dr.m.renewBefore()+maxRandRenew { +			return next, nil +		} +	} + +	der, leaf, err := dr.m.authorizedCert(ctx, dr.key, dr.domain) +	if err != nil { +		return 0, err +	} +	state := &certState{ +		key:  dr.key, +		cert: der, +		leaf: leaf, +	} +	tlscert, err := state.tlscert() +	if err != nil { +		return 0, err +	} +	dr.m.cachePut(dr.domain, tlscert) +	dr.m.stateMu.Lock() +	defer dr.m.stateMu.Unlock() +	// m.state is guaranteed to be non-nil at this point +	dr.m.state[dr.domain] = state +	return dr.next(leaf.NotAfter), nil +} + +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)) +	d -= time.Duration(n) +	if d < 0 { +		return 0 +	} +	return d +} + +var testDidRenewLoop = func(next time.Duration, err error) {} diff --git a/vendor/golang.org/x/crypto/acme/jws.go b/vendor/golang.org/x/crypto/acme/jws.go new file mode 100644 index 0000000..49ba313 --- /dev/null +++ b/vendor/golang.org/x/crypto/acme/jws.go @@ -0,0 +1,153 @@ +// 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, nonce string) ([]byte, error) { +	jwk, err := jwkEncode(key.Public()) +	if err != nil { +		return nil, err +	} +	alg, sha := jwsHasher(key) +	if alg == "" || !sha.Available() { +		return nil, ErrUnsupportedKey +	} +	phead := fmt.Sprintf(`{"alg":%q,"jwk":%s,"nonce":%q}`, alg, jwk, nonce) +	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-512": +			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 +} diff --git a/vendor/golang.org/x/crypto/acme/types.go b/vendor/golang.org/x/crypto/acme/types.go new file mode 100644 index 0000000..0513b2e --- /dev/null +++ b/vendor/golang.org/x/crypto/acme/types.go @@ -0,0 +1,209 @@ +package acme + +import ( +	"errors" +	"fmt" +	"net/http" +) + +// ACME server response statuses used to describe Authorization and Challenge states. +const ( +	StatusUnknown    = "unknown" +	StatusPending    = "pending" +	StatusProcessing = "processing" +	StatusValid      = "valid" +	StatusInvalid    = "invalid" +	StatusRevoked    = "revoked" +) + +// 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 +) + +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") +) + +// Error is an ACME error, defined in Problem Details for HTTP APIs doc +// http://tools.ietf.org/html/draft-ietf-appsawg-http-problem. +type Error struct { +	// StatusCode is The HTTP status code generated by the origin server. +	StatusCode int +	// ProblemType is a URI reference that identifies the problem type, +	// typically in a "urn:acme:error:xxx" form. +	ProblemType string +	// Detail is a human-readable explanation specific to this occurrence of the problem. +	Detail string +	// Header is the original server error response headers. +	Header http.Header +} + +func (e *Error) Error() string { +	return fmt.Sprintf("%d %s: %s", e.StatusCode, e.ProblemType, e.Detail) +} + +// 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 +	// account data from the CA. +	URI string + +	// Contact is a slice of contact info used during registration. +	Contact []string + +	// The terms user has agreed to. +	// A value not matching CurrentTerms indicates that the user hasn't agreed +	// to the actual Terms of Service of the CA. +	AgreedTerms string + +	// Actual terms of a CA. +	CurrentTerms string + +	// Authz is the authorization URL used to initiate a new authz flow. +	Authz string + +	// Authorizations is a URI from which a list of authorizations +	// granted to this account can be fetched via a GET request. +	Authorizations string + +	// Certificates is a URI from which a list of certificates +	// issued for this account can be fetched via a GET request. +	Certificates string +} + +// Directory is ACME server discovery data. +type Directory struct { +	// RegURL is an account endpoint URL, allowing for creating new +	// and modifying existing accounts. +	RegURL string + +	// AuthzURL is used to initiate Identifier Authorization flow. +	AuthzURL string + +	// CertURL is a new certificate issuance endpoint URL. +	CertURL string + +	// RevokeURL is used to initiate a certificate revocation flow. +	RevokeURL string + +	// Term is a URI 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 +} + +// Challenge encodes a returned CA challenge. +type Challenge struct { +	// Type is the challenge type, e.g. "http-01", "tls-sni-02", "dns-01". +	Type string + +	// URI is where a challenge response can be posted to. +	URI string + +	// Token is a random value that uniquely identifies the challenge. +	Token string + +	// Status identifies the status of this challenge. +	Status string +} + +// Authorization encodes an authorization response. +type Authorization struct { +	// URI uniquely identifies a authorization. +	URI string + +	// Status identifies the status of an authorization. +	Status string + +	// Identifier is what the account is authorized to represent. +	Identifier AuthzID + +	// Challenges that the client needs to fulfill in order to prove possession +	// of the identifier (for pending authorizations). +	// For final authorizations, the challenges that were used. +	Challenges []*Challenge + +	// A collection of sets of challenges, each of which would be sufficient +	// to prove possession of the identifier. +	// Clients must complete a set of challenges that covers at least one set. +	// Challenges are identified by their indices in the challenges array. +	// If this field is empty, the client needs to complete all challenges. +	Combinations [][]int +} + +// 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". +} + +// wireAuthz is ACME JSON representation of Authorization objects. +type wireAuthz struct { +	Status       string +	Challenges   []wireChallenge +	Combinations [][]int +	Identifier   struct { +		Type  string +		Value string +	} +} + +func (z *wireAuthz) authorization(uri string) *Authorization { +	a := &Authorization{ +		URI:          uri, +		Status:       z.Status, +		Identifier:   AuthzID{Type: z.Identifier.Type, Value: z.Identifier.Value}, +		Combinations: z.Combinations, // shallow copy +		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 { +	URI    string `json:"uri"` +	Type   string +	Token  string +	Status string +} + +func (c *wireChallenge) challenge() *Challenge { +	v := &Challenge{ +		URI:    c.URI, +		Type:   c.Type, +		Token:  c.Token, +		Status: c.Status, +	} +	if v.Status == "" { +		v.Status = StatusPending +	} +	return v +} diff --git a/vendor/vendor.json b/vendor/vendor.json index 526d10b..1412468 100644 --- a/vendor/vendor.json +++ b/vendor/vendor.json @@ -535,6 +535,18 @@  			"revisionTime": "2016-09-23T17:06:11Z"  		},  		{ +			"checksumSHA1": "TK1Yr8BbwionaaAvM+77lwAAx/8=", +			"path": "golang.org/x/crypto/acme", +			"revision": "f6b343c37ca80bfa8ea539da67a0b621f84fab1d", +			"revisionTime": "2016-12-21T04:54:10Z" +		}, +		{ +			"checksumSHA1": "fvFbUkZIL96D/kWOJwJRMESGjQQ=", +			"path": "golang.org/x/crypto/acme/autocert", +			"revision": "f6b343c37ca80bfa8ea539da67a0b621f84fab1d", +			"revisionTime": "2016-12-21T04:54:10Z" +		}, +		{  			"checksumSHA1": "dwOedwBJ1EIK9+S3t108Bx054Y8=",  			"path": "golang.org/x/crypto/curve25519",  			"revision": "b2fa06b6af4b7c9bfeb8569ab7b17f04550717bf",  | 
