acme/autocert: new high-level package for automatic cert management

Package autocert provides automatic access to certificates
from Let's Encrypt and any other ACME-based CA.

It is heavily based on the ideas from rsc.io/letsencrypt.

Change-Id: I62021452a918cd49093162f3d6c74e9d7f452fb8
Reviewed-on: https://go-review.googlesource.com/23970
Reviewed-by: Brad Fitzpatrick <bradfitz@golang.org>
diff --git a/acme/autocert/autocert.go b/acme/autocert/autocert.go
new file mode 100644
index 0000000..09fb065
--- /dev/null
+++ b/acme/autocert/autocert.go
@@ -0,0 +1,607 @@
+// 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/rand"
+	"crypto/rsa"
+	"crypto/tls"
+	"crypto/x509"
+	"crypto/x509/pkix"
+	"encoding/pem"
+	"errors"
+	"fmt"
+	"net/http"
+	"sort"
+	"strconv"
+	"strings"
+	"sync"
+	"time"
+
+	"golang.org/x/crypto/acme/internal/acme"
+	"golang.org/x/net/context"
+)
+
+// AcceptTOS always returns true to indicate the acceptance of a CA Terms of Service
+// during account registration.
+func AcceptTOS(tosURL string) bool { return true }
+
+// 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}
+//	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
+
+	// DNSNames restricts Manager to work with only the specified domain names.
+	// If the field is nil or empty, any domain name is allowed.
+	// The elements of DNSNames must be sorted in lexical order.
+	// Only exact matches are supported, no regexp or wildcard.
+	DNSNames []string
+
+	// 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 2048-bit RSA 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
+
+	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
+}
+
+// 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.
+//
+// A simple usage can be shown as follows:
+//
+//	s := &http.Server{
+//		Addr: ":https",
+//		TLSConfig: &tls.Config{
+//			GetCertificate: m.GetCertificate,
+//		},
+//	}
+//	s.ListenAndServeTLS("", "")
+//
+// If m.DNSNames is not empty and none of its elements match hello.ServerName exactly,
+// GetCertificate returns an error.
+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)
+	}
+
+	// check against allowed set of host names
+	if len(m.DNSNames) > 0 {
+		i := sort.SearchStrings(m.DNSNames, name)
+		if i >= len(m.DNSNames) || m.DNSNames[i] != name {
+			return nil, fmt.Errorf("acme/autocert: %q is not allowed", name)
+		}
+	}
+
+	// regular domain
+	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?
+	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()
+	s, ok := m.state[name]
+	if 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)
+	}
+	m.state[name] = &certState{
+		key:  signer,
+		cert: cert.Certificate,
+		leaf: cert.Leaf,
+	}
+	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")
+	}
+
+	// parse public part(s) and verify the leaf is not expired
+	// and corresponds to the private key
+	x509Cert, err := x509.ParseCertificates(pubDER)
+	if len(x509Cert) == 0 {
+		return nil, errors.New("acme/autocert: no public key found in cache")
+	}
+	leaf := x509Cert[0]
+	now := time.Now()
+	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 !domainMatch(leaf, domain) {
+		return nil, errors.New("acme/autocert: certificate does not match domain name")
+	}
+	switch pub := leaf.PublicKey.(type) {
+	case *rsa.PublicKey:
+		prv, ok := privKey.(*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 := privKey.(*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")
+	}
+
+	tlscert := &tls.Certificate{
+		Certificate: make([][]byte, len(x509Cert)),
+		PrivateKey:  privKey,
+		Leaf:        leaf,
+	}
+	for i, crt := range x509Cert {
+		tlscert.Certificate[i] = crt.Raw
+	}
+	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:
+		b, err := x509.MarshalECPrivateKey(key)
+		if err != nil {
+			return err
+		}
+		pb := &pem.Block{Type: "EC PRIVATE KEY", Bytes: b}
+		if err := pem.Encode(&buf, pb); 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())
+}
+
+// createCert starts 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) {
+	state, ok, 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 ok {
+		state.RLock()
+		defer state.RUnlock()
+		return state.tlscert()
+	}
+
+	// We are the first.
+	// Unblock the readers when domain ownership is verified
+	// and the we got the cert or the process failed.
+	defer state.Unlock()
+	// TODO: make m.verify retry or retry m.verify calls here
+	if err := m.verify(ctx, domain); err != nil {
+		return nil, err
+	}
+	client, err := m.acmeClient(ctx)
+	if err != nil {
+		return nil, err
+	}
+	csr, err := certRequest(state.key, domain)
+	if err != nil {
+		return nil, err
+	}
+	der, _, err := client.CreateCert(ctx, csr, 0, true)
+	if err != nil {
+		return nil, err
+	}
+	state.cert = der
+	return state.tlscert()
+}
+
+// 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
+	}
+	// 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
+	for {
+		a, err := client.GetAuthz(ctx, authz.URI)
+		if err == nil {
+			if a.Status == acme.StatusValid {
+				break
+			}
+			if a.Status == acme.StatusInvalid {
+				return fmt.Errorf("acme/autocert: validation for domain %q failed", domain)
+			}
+		}
+		// still pending
+		d := time.Second
+		if ae, ok := err.(*acme.Error); ok {
+			d = retryAfter(ae.Header.Get("retry-after"))
+		}
+		select {
+		case <-ctx.Done():
+			return ctx.Err()
+		case <-time.After(d):
+			// retry
+		}
+	}
+	return nil
+}
+
+// certState returns existing state or creates a new one locked for read/write.
+// The boolean return value indicates whether the state was found in m.state.
+func (m *Manager) certState(domain string) (*certState, bool, 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, true, nil
+	}
+	// new locked state
+	key, err := rsa.GenerateKey(rand.Reader, 2048)
+	if err != nil {
+		return nil, false, err
+	}
+	state := &certState{key: key}
+	state.Lock()
+	m.state[domain] = state
+	return state, false, nil
+}
+
+// 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)
+	}
+}
+
+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 = rsa.GenerateKey(rand.Reader, 2048)
+		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
+}
+
+// certState is ready when its mutex is unlocked for reading.
+type certState struct {
+	sync.RWMutex
+	key  crypto.Signer
+	cert [][]byte          // DER encoding
+	leaf *x509.Certificate // parsed cert[0]; may be 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")
+	}
+	// TODO: compare pub.N with key.N or pub.{X,Y} for ECDSA?
+	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.
+//
+// Copied from crypto/tls/tls.go.
+func parsePrivateKey(der []byte) (crypto.PrivateKey, 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, *ecdsa.PrivateKey:
+			return key, nil
+		default:
+			return nil, errors.New("acme/autocert: found 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")
+}
+
+// domainMatch matches cert against the specified domain name.
+// It doesn't support wildcard.
+func domainMatch(cert *x509.Certificate, name string) bool {
+	if cert.Subject.CommonName == name {
+		return true
+	}
+	sort.Strings(cert.DNSNames)
+	i := sort.SearchStrings(cert.DNSNames, name)
+	return i < len(cert.DNSNames) && cert.DNSNames[i] == name
+}
+
+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(time.Now())
+	}
+	return time.Second
+}
diff --git a/acme/autocert/autocert_test.go b/acme/autocert/autocert_test.go
new file mode 100644
index 0000000..6059feb
--- /dev/null
+++ b/acme/autocert/autocert_test.go
@@ -0,0 +1,278 @@
+// 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 (
+	"bytes"
+	"crypto/rand"
+	"crypto/rsa"
+	"crypto/tls"
+	"crypto/x509"
+	"crypto/x509/pkix"
+	"encoding/pem"
+	"fmt"
+	"html/template"
+	"math/big"
+	"net/http"
+	"net/http/httptest"
+	"strings"
+	"testing"
+	"time"
+
+	"golang.org/x/crypto/acme/internal/acme"
+	"golang.org/x/net/context"
+)
+
+var discoTmpl = template.Must(template.New("disco").Parse(`{
+	"new-reg": "{{.}}/new-reg",
+	"new-authz": "{{.}}/new-authz",
+	"new-cert": "{{.}}/new-cert"
+}`))
+
+var authzTmpl = template.Must(template.New("authz").Parse(`{
+	"status": "pending",
+	"challenges": [
+		{
+			"uri": "{{.}}/challenge/1",
+			"type": "tls-sni-01",
+			"token": "token-01"
+		},
+		{
+			"uri": "{{.}}/challenge/2",
+			"type": "tls-sni-02",
+			"token": "token-02"
+		}
+	]
+}`))
+
+func dummyCert(san ...string) ([]byte, error) {
+	key, err := rsa.GenerateKey(rand.Reader, 2048)
+	if err != nil {
+		return nil, err
+	}
+	t := &x509.Certificate{
+		SerialNumber:          big.NewInt(1),
+		NotBefore:             time.Now(),
+		NotAfter:              time.Now().Add(24 * time.Hour),
+		BasicConstraintsValid: true,
+		KeyUsage:              x509.KeyUsageKeyEncipherment,
+		DNSNames:              san,
+	}
+	return x509.CreateCertificate(rand.Reader, t, t, &key.PublicKey, key)
+}
+
+func TestGetCertificate(t *testing.T) {
+	const domain = "example.org"
+	man := &Manager{Prompt: AcceptTOS}
+
+	// echo token-02 | shasum -a 256
+	// then divide result in 2 parts separated by dot
+	tokenCertName := "4e8eb87631187e9ff2153b56b13a4dec.13a35d002e485d60ff37354b32f665d9.token.acme.invalid"
+	verifyTokenCert := func() {
+		hello := &tls.ClientHelloInfo{ServerName: tokenCertName}
+		_, err := man.GetCertificate(hello)
+		if err != nil {
+			t.Errorf("verifyTokenCert: GetCertificate(%q): %v", tokenCertName, err)
+			return
+		}
+	}
+
+	// ACME CA server stub
+	var ca *httptest.Server
+	ca = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+		w.Header().Set("replay-nonce", "nonce")
+		switch r.URL.Path {
+		// discovery
+		case "/":
+			if err := discoTmpl.Execute(w, ca.URL); err != nil {
+				t.Fatalf("discoTmpl: %v", err)
+			}
+		// client key registration
+		case "/new-reg":
+			w.Write([]byte("{}"))
+		// domain authorization
+		case "/new-authz":
+			w.Header().Set("location", ca.URL+"/authz/1")
+			w.WriteHeader(http.StatusCreated)
+			if err := authzTmpl.Execute(w, ca.URL); err != nil {
+				t.Fatalf("authzTmpl: %v", err)
+			}
+		// accept tls-sni-02 challenge
+		case "/challenge/2":
+			verifyTokenCert()
+			w.Write([]byte("{}"))
+		// authorization status
+		case "/authz/1":
+			w.Write([]byte(`{"status": "valid"}`))
+		// cert request
+		case "/new-cert":
+			der, err := dummyCert(domain)
+			if err != nil {
+				t.Fatalf("new-cert: dummyCert: %v", err)
+			}
+			chainUp := fmt.Sprintf("<%s/ca-cert>; rel=up", ca.URL)
+			w.Header().Set("link", chainUp)
+			w.WriteHeader(http.StatusCreated)
+			w.Write(der)
+		// CA chain cert
+		case "/ca-cert":
+			der, err := dummyCert("ca")
+			if err != nil {
+				t.Fatalf("ca-cert: dummyCert: %v", err)
+			}
+			w.Write(der)
+		default:
+			t.Errorf("unrecognized r.URL.Path: %s", r.URL.Path)
+		}
+	}))
+	defer ca.Close()
+	man.Client = &acme.Client{DirectoryURL: ca.URL}
+
+	// simulate tls.Config.GetCertificate
+	var (
+		tlscert *tls.Certificate
+		err     error
+		done    = make(chan struct{})
+	)
+	go func() {
+		hello := &tls.ClientHelloInfo{ServerName: domain}
+		tlscert, err = man.GetCertificate(hello)
+		close(done)
+	}()
+	select {
+	case <-time.After(10 * time.Second):
+		t.Fatal("man.GetCertificate took too long to return")
+	case <-done:
+	}
+	if err != nil {
+		t.Fatalf("man.GetCertificate: %v", err)
+	}
+
+	// verify the tlscert is the same we responded with from the CA stub
+	if len(tlscert.Certificate) == 0 {
+		t.Fatal("len(tlscert.Certificate) is 0")
+	}
+	cert, err := x509.ParseCertificate(tlscert.Certificate[0])
+	if err != nil {
+		t.Fatalf("x509.ParseCertificate: %v", err)
+	}
+	if len(cert.DNSNames) == 0 || cert.DNSNames[0] != domain {
+		t.Errorf("cert.DNSNames = %v; want %q", cert.DNSNames, domain)
+	}
+
+	// make sure token cert was removed
+	done = make(chan struct{})
+	go func() {
+		for {
+			hello := &tls.ClientHelloInfo{ServerName: tokenCertName}
+			if _, err := man.GetCertificate(hello); err != nil {
+				break
+			}
+			time.Sleep(100 * time.Millisecond)
+		}
+		close(done)
+	}()
+	select {
+	case <-time.After(5 * time.Second):
+		t.Error("token cert was not removed")
+	case <-done:
+	}
+}
+
+type memCache map[string][]byte
+
+func (m memCache) Get(ctx context.Context, key string) ([]byte, error) {
+	v, ok := m[key]
+	if !ok {
+		return nil, ErrCacheMiss
+	}
+	return v, nil
+}
+
+func (m memCache) Put(ctx context.Context, key string, data []byte) error {
+	m[key] = data
+	return nil
+}
+
+func (m memCache) Delete(ctx context.Context, key string) error {
+	delete(m, key)
+	return nil
+}
+
+func TestCache(t *testing.T) {
+	privKey, err := rsa.GenerateKey(rand.Reader, 2048)
+	if err != nil {
+		t.Fatal(err)
+	}
+	tmpl := &x509.Certificate{
+		SerialNumber: big.NewInt(1),
+		Subject:      pkix.Name{CommonName: "example.org"},
+		NotAfter:     time.Now().Add(time.Hour),
+	}
+	pub, err := x509.CreateCertificate(rand.Reader, tmpl, tmpl, &privKey.PublicKey, privKey)
+	if err != nil {
+		t.Fatal(err)
+	}
+	tlscert := &tls.Certificate{
+		Certificate: [][]byte{pub},
+		PrivateKey:  privKey,
+	}
+
+	cache := make(memCache)
+	man := Manager{Cache: cache}
+	if err := man.cachePut("example.org", tlscert); err != nil {
+		t.Fatalf("man.cachePut: %v", err)
+	}
+	res, err := man.cacheGet("example.org")
+	if err != nil {
+		t.Fatalf("man.cacheGet: %v", err)
+	}
+	if res == nil {
+		t.Fatal("res is nil")
+	}
+
+	priv := x509.MarshalPKCS1PrivateKey(privKey)
+	dummy, err := dummyCert("dummy")
+	if err != nil {
+		t.Fatalf("dummyCert: %v", err)
+	}
+	tt := []struct {
+		key      string
+		prv, pub []byte
+	}{
+		{"dummy", priv, dummy},
+		{"bad1", priv, []byte{1}},
+		{"bad2", []byte{1}, pub},
+	}
+	for i, test := range tt {
+		var buf bytes.Buffer
+		pb := &pem.Block{Type: "RSA PRIVATE KEY", Bytes: test.prv}
+		if err := pem.Encode(&buf, pb); err != nil {
+			t.Errorf("%d: pem.Encode: %v", i, err)
+		}
+		pb = &pem.Block{Type: "CERTIFICATE", Bytes: test.pub}
+		if err := pem.Encode(&buf, pb); err != nil {
+			t.Errorf("%d: pem.Encode: %v", i, err)
+		}
+
+		cache.Put(nil, test.key, buf.Bytes())
+		if _, err := man.cacheGet(test.key); err == nil {
+			t.Errorf("%d: err is nil", i)
+		}
+	}
+}
+
+func TestDNSNames(t *testing.T) {
+	man := Manager{
+		DNSNames: []string{"example.com"},
+		// prevent network round-trips, just in case
+		Client: &acme.Client{DirectoryURL: "dummy"},
+	}
+	hello := &tls.ClientHelloInfo{ServerName: "example.org"}
+	_, err := man.GetCertificate(hello)
+	if err == nil || !strings.Contains(err.Error(), "not allowed") {
+		t.Errorf("err = %v; want 'not allowed'", err)
+	}
+}
diff --git a/acme/autocert/cache.go b/acme/autocert/cache.go
new file mode 100644
index 0000000..1c67f6c
--- /dev/null
+++ b/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/acme/autocert/cache_test.go b/acme/autocert/cache_test.go
new file mode 100644
index 0000000..ad6d4a4
--- /dev/null
+++ b/acme/autocert/cache_test.go
@@ -0,0 +1,58 @@
+// 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 (
+	"io/ioutil"
+	"os"
+	"path/filepath"
+	"reflect"
+	"testing"
+
+	"golang.org/x/net/context"
+)
+
+// make sure DirCache satisfies Cache interface
+var _ Cache = DirCache("/")
+
+func TestDirCache(t *testing.T) {
+	dir, err := ioutil.TempDir("", "autocert")
+	if err != nil {
+		t.Fatal(err)
+	}
+	dir = filepath.Join(dir, "certs") // a nonexistent dir
+	cache := DirCache(dir)
+	ctx := context.Background()
+
+	// test cache miss
+	if _, err := cache.Get(ctx, "nonexistent"); err != ErrCacheMiss {
+		t.Errorf("get: %v; want ErrCacheMiss", err)
+	}
+
+	// test put/get
+	b1 := []byte{1}
+	if err := cache.Put(ctx, "dummy", b1); err != nil {
+		t.Fatalf("put: %v", err)
+	}
+	b2, err := cache.Get(ctx, "dummy")
+	if err != nil {
+		t.Fatalf("get: %v", err)
+	}
+	if !reflect.DeepEqual(b1, b2) {
+		t.Errorf("b1 = %v; want %v", b1, b2)
+	}
+	name := filepath.Join(dir, "dummy")
+	if _, err := os.Stat(name); err != nil {
+		t.Error(err)
+	}
+
+	// test delete
+	if err := cache.Delete(ctx, "dummy"); err != nil {
+		t.Fatalf("delete: %v", err)
+	}
+	if _, err := cache.Get(ctx, "dummy"); err != ErrCacheMiss {
+		t.Errorf("get: %v; want ErrCacheMiss", err)
+	}
+}