acme/autocert: support both RSA and ECDSA clients on the fly

GetCertificate has all the information it needs to know if a client
supports ECDSA in ClientHelloInfo. Deprecate and ignore ForceRSA, and
just obtain a RSA certificate on the fly when a client that doesn't
support ECDSA connects.

This changes the cache key format to have a "+rsa" suffix for RSA
certificates. The default (ForceRSA = false) cache key is unchanged,
so most DirCache instances will still be valid. Caches created with
ForceRSA set will be silently ignored and certificates reissued.

The cache keys for HTTP tokens and the account key are changed to be
guaranteed not to overlap with valid domain names as well.

Note that ECDSA support detection is more strict in following RFC 5246
than crypto/tls, which ignores signature_algorithms.

Fixes golang/go#22066

Change-Id: I70227747b563d6849cb693f83a950d57040b3f39
Reviewed-on: https://go-review.googlesource.com/114501
Reviewed-by: Adam Langley <agl@golang.org>
Reviewed-by: Brad Fitzpatrick <bradfitz@golang.org>
diff --git a/acme/autocert/autocert.go b/acme/autocert/autocert.go
index 96cc56f..023a06d 100644
--- a/acme/autocert/autocert.go
+++ b/acme/autocert/autocert.go
@@ -101,8 +101,7 @@
 	// 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.
+	// Using a persistent Cache, such as DirCache, is strongly recommended.
 	Cache Cache
 
 	// HostPolicy controls which domains the Manager will attempt
@@ -140,10 +139,10 @@
 	// 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.
+	// ForceRSA used to make the Manager generate RSA certificates. It is now ignored.
 	//
-	// If false, a default is used. Currently the default
-	// is EC-based keys using the P-256 curve.
+	// Deprecated: the Manager will request the correct type of certificate based
+	// on what each client supports.
 	ForceRSA bool
 
 	// ExtraExtensions are used when generating a new CSR (Certificate Request),
@@ -159,12 +158,11 @@
 	client   *acme.Client // initialized by acmeClient method
 
 	stateMu sync.Mutex
-	state   map[string]*certState // keyed by domain name
+	state   map[certKey]*certState
 
 	// renewal tracks the set of domains currently running renewal timers.
-	// It is keyed by domain name.
 	renewalMu sync.Mutex
-	renewal   map[string]*domainRenewal
+	renewal   map[certKey]*domainRenewal
 
 	// tokensMu guards the rest of the fields: tryHTTP01, certTokens and httpTokens.
 	tokensMu sync.RWMutex
@@ -183,6 +181,23 @@
 	certTokens map[string]*tls.Certificate
 }
 
+// certKey is the key by which certificates are tracked in state, renewal and cache.
+type certKey struct {
+	domain  string // without trailing dot
+	isRSA   bool   // RSA cert for legacy clients (as opposed to default ECDSA)
+	isToken bool   // tls-sni challenge token cert; key type is undefined regardless of isRSA
+}
+
+func (c certKey) String() string {
+	if c.isToken {
+		return c.domain + "+token"
+	}
+	if c.isRSA {
+		return c.domain + "+rsa"
+	}
+	return c.domain
+}
+
 // 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.
@@ -203,7 +218,7 @@
 	if !strings.Contains(strings.Trim(name, "."), ".") {
 		return nil, errors.New("acme/autocert: server name component count invalid")
 	}
-	if strings.ContainsAny(name, `/\`) {
+	if strings.ContainsAny(name, `+/\`) {
 		return nil, errors.New("acme/autocert: server name contains invalid character")
 	}
 
@@ -219,7 +234,7 @@
 		if cert := m.certTokens[name]; cert != nil {
 			return cert, nil
 		}
-		if cert, err := m.cacheGet(ctx, name); err == nil {
+		if cert, err := m.cacheGet(ctx, certKey{domain: name, isToken: true}); err == nil {
 			return cert, nil
 		}
 		// TODO: cache error results?
@@ -227,8 +242,11 @@
 	}
 
 	// regular domain
-	name = strings.TrimSuffix(name, ".") // golang.org/issue/18114
-	cert, err := m.cert(ctx, name)
+	ck := certKey{
+		domain: strings.TrimSuffix(name, "."), // golang.org/issue/18114
+		isRSA:  !supportsECDSA(hello),
+	}
+	cert, err := m.cert(ctx, ck)
 	if err == nil {
 		return cert, nil
 	}
@@ -240,14 +258,59 @@
 	if err := m.hostPolicy()(ctx, name); err != nil {
 		return nil, err
 	}
-	cert, err = m.createCert(ctx, name)
+	cert, err = m.createCert(ctx, ck)
 	if err != nil {
 		return nil, err
 	}
-	m.cachePut(ctx, name, cert)
+	m.cachePut(ctx, ck, cert)
 	return cert, nil
 }
 
+func supportsECDSA(hello *tls.ClientHelloInfo) bool {
+	// The "signature_algorithms" extension, if present, limits the key exchange
+	// algorithms allowed by the cipher suites. See RFC 5246, section 7.4.1.4.1.
+	if hello.SignatureSchemes != nil {
+		ecdsaOK := false
+	schemeLoop:
+		for _, scheme := range hello.SignatureSchemes {
+			switch scheme {
+			case tls.ECDSAWithSHA1, tls.ECDSAWithP256AndSHA256,
+				tls.ECDSAWithP384AndSHA384, tls.ECDSAWithP521AndSHA512:
+				ecdsaOK = true
+				break schemeLoop
+			}
+		}
+		if !ecdsaOK {
+			return false
+		}
+	}
+	if hello.SupportedCurves != nil {
+		ecdsaOK := false
+		for _, curve := range hello.SupportedCurves {
+			if curve == tls.CurveP256 {
+				ecdsaOK = true
+				break
+			}
+		}
+		if !ecdsaOK {
+			return false
+		}
+	}
+	for _, suite := range hello.CipherSuites {
+		switch suite {
+		case tls.TLS_ECDHE_ECDSA_WITH_RC4_128_SHA,
+			tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA,
+			tls.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA,
+			tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256,
+			tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
+			tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
+			tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305:
+			return true
+		}
+	}
+	return false
+}
+
 // HTTPHandler configures the Manager to provision ACME "http-01" challenge responses.
 // It returns an http.Handler that responds to the challenges and must be
 // running on port 80. If it receives a request that is not an ACME challenge,
@@ -313,16 +376,16 @@
 // 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(ctx context.Context, name string) (*tls.Certificate, error) {
+func (m *Manager) cert(ctx context.Context, ck certKey) (*tls.Certificate, error) {
 	m.stateMu.Lock()
-	if s, ok := m.state[name]; ok {
+	if s, ok := m.state[ck]; ok {
 		m.stateMu.Unlock()
 		s.RLock()
 		defer s.RUnlock()
 		return s.tlscert()
 	}
 	defer m.stateMu.Unlock()
-	cert, err := m.cacheGet(ctx, name)
+	cert, err := m.cacheGet(ctx, ck)
 	if err != nil {
 		return nil, err
 	}
@@ -331,25 +394,25 @@
 		return nil, errors.New("acme/autocert: private key cannot sign")
 	}
 	if m.state == nil {
-		m.state = make(map[string]*certState)
+		m.state = make(map[certKey]*certState)
 	}
 	s := &certState{
 		key:  signer,
 		cert: cert.Certificate,
 		leaf: cert.Leaf,
 	}
-	m.state[name] = s
-	go m.renew(name, s.key, s.leaf.NotAfter)
+	m.state[ck] = s
+	go m.renew(ck, s.key, s.leaf.NotAfter)
 	return cert, nil
 }
 
 // cacheGet always returns a valid certificate, or an error otherwise.
-// If a cached certficate exists but is not valid, ErrCacheMiss is returned.
-func (m *Manager) cacheGet(ctx context.Context, domain string) (*tls.Certificate, error) {
+// If a cached certificate exists but is not valid, ErrCacheMiss is returned.
+func (m *Manager) cacheGet(ctx context.Context, ck certKey) (*tls.Certificate, error) {
 	if m.Cache == nil {
 		return nil, ErrCacheMiss
 	}
-	data, err := m.Cache.Get(ctx, domain)
+	data, err := m.Cache.Get(ctx, ck.String())
 	if err != nil {
 		return nil, err
 	}
@@ -380,7 +443,7 @@
 	}
 
 	// verify and create TLS cert
-	leaf, err := validCert(domain, pubDER, privKey)
+	leaf, err := validCert(ck, pubDER, privKey)
 	if err != nil {
 		return nil, ErrCacheMiss
 	}
@@ -392,7 +455,7 @@
 	return tlscert, nil
 }
 
-func (m *Manager) cachePut(ctx context.Context, domain string, tlscert *tls.Certificate) error {
+func (m *Manager) cachePut(ctx context.Context, ck certKey, tlscert *tls.Certificate) error {
 	if m.Cache == nil {
 		return nil
 	}
@@ -424,7 +487,7 @@
 		}
 	}
 
-	return m.Cache.Put(ctx, domain, buf.Bytes())
+	return m.Cache.Put(ctx, ck.String(), buf.Bytes())
 }
 
 func encodeECDSAKey(w io.Writer, key *ecdsa.PrivateKey) error {
@@ -441,9 +504,9 @@
 //
 // 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) {
+func (m *Manager) createCert(ctx context.Context, ck certKey) (*tls.Certificate, error) {
 	// TODO: maybe rewrite this whole piece using sync.Once
-	state, err := m.certState(domain)
+	state, err := m.certState(ck)
 	if err != nil {
 		return nil, err
 	}
@@ -461,44 +524,44 @@
 	defer state.Unlock()
 	state.locked = false
 
-	der, leaf, err := m.authorizedCert(ctx, state.key, domain)
+	der, leaf, err := m.authorizedCert(ctx, state.key, ck)
 	if err != nil {
 		// Remove the failed state after some time,
 		// making the manager call createCert again on the following TLS hello.
 		time.AfterFunc(createCertRetryAfter, func() {
-			defer testDidRemoveState(domain)
+			defer testDidRemoveState(ck)
 			m.stateMu.Lock()
 			defer m.stateMu.Unlock()
 			// Verify the state hasn't changed and it's still invalid
 			// before deleting.
-			s, ok := m.state[domain]
+			s, ok := m.state[ck]
 			if !ok {
 				return
 			}
-			if _, err := validCert(domain, s.cert, s.key); err == nil {
+			if _, err := validCert(ck, s.cert, s.key); err == nil {
 				return
 			}
-			delete(m.state, domain)
+			delete(m.state, ck)
 		})
 		return nil, err
 	}
 	state.cert = der
 	state.leaf = leaf
-	go m.renew(domain, state.key, state.leaf.NotAfter)
+	go m.renew(ck, 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) {
+func (m *Manager) certState(ck certKey) (*certState, error) {
 	m.stateMu.Lock()
 	defer m.stateMu.Unlock()
 	if m.state == nil {
-		m.state = make(map[string]*certState)
+		m.state = make(map[certKey]*certState)
 	}
 	// existing state
-	if state, ok := m.state[domain]; ok {
+	if state, ok := m.state[ck]; ok {
 		return state, nil
 	}
 
@@ -507,7 +570,7 @@
 		err error
 		key crypto.Signer
 	)
-	if m.ForceRSA {
+	if ck.isRSA {
 		key, err = rsa.GenerateKey(rand.Reader, 2048)
 	} else {
 		key, err = ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
@@ -521,22 +584,22 @@
 		locked: true,
 	}
 	state.Lock() // will be unlocked by m.certState caller
-	m.state[domain] = state
+	m.state[ck] = state
 	return state, nil
 }
 
 // authorizedCert starts the 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) {
+func (m *Manager) authorizedCert(ctx context.Context, key crypto.Signer, ck certKey) (der [][]byte, leaf *x509.Certificate, err error) {
 	client, err := m.acmeClient(ctx)
 	if err != nil {
 		return nil, nil, err
 	}
 
-	if err := m.verify(ctx, client, domain); err != nil {
+	if err := m.verify(ctx, client, ck.domain); err != nil {
 		return nil, nil, err
 	}
-	csr, err := certRequest(key, domain, m.ExtraExtensions)
+	csr, err := certRequest(key, ck.domain, m.ExtraExtensions)
 	if err != nil {
 		return nil, nil, err
 	}
@@ -544,7 +607,7 @@
 	if err != nil {
 		return nil, nil, err
 	}
-	leaf, err = validCert(domain, der, key)
+	leaf, err = validCert(ck, der, key)
 	if err != nil {
 		return nil, nil, err
 	}
@@ -674,8 +737,8 @@
 	return nil
 }
 
-// putCertToken stores the cert under the named key in both m.certTokens map
-// and m.Cache.
+// putCertToken stores the token certificate with the specified name
+// in both m.certTokens map and m.Cache.
 func (m *Manager) putCertToken(ctx context.Context, name string, cert *tls.Certificate) {
 	m.tokensMu.Lock()
 	defer m.tokensMu.Unlock()
@@ -683,17 +746,18 @@
 		m.certTokens = make(map[string]*tls.Certificate)
 	}
 	m.certTokens[name] = cert
-	m.cachePut(ctx, name, cert)
+	m.cachePut(ctx, certKey{domain: name, isToken: true}, cert)
 }
 
-// deleteCertToken removes the token certificate for the specified domain name
+// deleteCertToken removes the token certificate with the specified name
 // from both m.certTokens map and m.Cache.
 func (m *Manager) deleteCertToken(name string) {
 	m.tokensMu.Lock()
 	defer m.tokensMu.Unlock()
 	delete(m.certTokens, name)
 	if m.Cache != nil {
-		m.Cache.Delete(context.Background(), name)
+		ck := certKey{domain: name, isToken: true}
+		m.Cache.Delete(context.Background(), ck.String())
 	}
 }
 
@@ -744,7 +808,7 @@
 // httpTokenCacheKey returns a key at which an http-01 token value may be stored
 // in the Manager's optional Cache.
 func httpTokenCacheKey(tokenPath string) string {
-	return "http-01-" + path.Base(tokenPath)
+	return path.Base(tokenPath) + "+http-01"
 }
 
 // renew starts a cert renewal timer loop, one per domain.
@@ -755,18 +819,18 @@
 //
 // 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) {
+func (m *Manager) renew(ck certKey, key crypto.Signer, exp time.Time) {
 	m.renewalMu.Lock()
 	defer m.renewalMu.Unlock()
-	if m.renewal[domain] != nil {
+	if m.renewal[ck] != nil {
 		// another goroutine is already on it
 		return
 	}
 	if m.renewal == nil {
-		m.renewal = make(map[string]*domainRenewal)
+		m.renewal = make(map[certKey]*domainRenewal)
 	}
-	dr := &domainRenewal{m: m, domain: domain, key: key}
-	m.renewal[domain] = dr
+	dr := &domainRenewal{m: m, ck: ck, key: key}
+	m.renewal[ck] = dr
 	dr.start(exp)
 }
 
@@ -782,7 +846,10 @@
 }
 
 func (m *Manager) accountKey(ctx context.Context) (crypto.Signer, error) {
-	const keyName = "acme_account.key"
+	const keyName = "acme_account+key"
+
+	// Previous versions of autocert stored the value under a different key.
+	const legacyKeyName = "acme_account.key"
 
 	genKey := func() (*ecdsa.PrivateKey, error) {
 		return ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
@@ -794,6 +861,9 @@
 
 	data, err := m.Cache.Get(ctx, keyName)
 	if err == ErrCacheMiss {
+		data, err = m.Cache.Get(ctx, legacyKeyName)
+	}
+	if err == ErrCacheMiss {
 		key, err := genKey()
 		if err != nil {
 			return nil, err
@@ -925,12 +995,12 @@
 	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.
+// validCert parses a cert chain provided as der argument and verifies the leaf and der[0]
+// correspond to the private key, the domain and key type match, and expiration dates
+// are valid. 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) {
+func validCert(ck certKey, der [][]byte, key crypto.Signer) (leaf *x509.Certificate, err error) {
 	// parse public part(s)
 	var n int
 	for _, b := range der {
@@ -942,7 +1012,7 @@
 		n += copy(pub[n:], b)
 	}
 	x509Cert, err := x509.ParseCertificates(pub)
-	if len(x509Cert) == 0 {
+	if err != nil || len(x509Cert) == 0 {
 		return nil, errors.New("acme/autocert: no public key found")
 	}
 	// verify the leaf is not expired and matches the domain name
@@ -954,10 +1024,10 @@
 	if now.After(leaf.NotAfter) {
 		return nil, errors.New("acme/autocert: expired certificate")
 	}
-	if err := leaf.VerifyHostname(domain); err != nil {
+	if err := leaf.VerifyHostname(ck.domain); err != nil {
 		return nil, err
 	}
-	// ensure the leaf corresponds to the private key
+	// ensure the leaf corresponds to the private key and matches the certKey type
 	switch pub := leaf.PublicKey.(type) {
 	case *rsa.PublicKey:
 		prv, ok := key.(*rsa.PrivateKey)
@@ -967,6 +1037,9 @@
 		if pub.N.Cmp(prv.N) != 0 {
 			return nil, errors.New("acme/autocert: private key does not match public key")
 		}
+		if !ck.isRSA && !ck.isToken {
+			return nil, errors.New("acme/autocert: key type does not match expected value")
+		}
 	case *ecdsa.PublicKey:
 		prv, ok := key.(*ecdsa.PrivateKey)
 		if !ok {
@@ -975,6 +1048,9 @@
 		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")
 		}
+		if ck.isRSA && !ck.isToken {
+			return nil, errors.New("acme/autocert: key type does not match expected value")
+		}
 	default:
 		return nil, errors.New("acme/autocert: unknown public key algorithm")
 	}
@@ -998,5 +1074,5 @@
 	timeNow = time.Now
 
 	// Called when a state is removed.
-	testDidRemoveState = func(domain string) {}
+	testDidRemoveState = func(certKey) {}
 )
diff --git a/acme/autocert/autocert_test.go b/acme/autocert/autocert_test.go
index 0572ff9..3773aba 100644
--- a/acme/autocert/autocert_test.go
+++ b/acme/autocert/autocert_test.go
@@ -5,6 +5,7 @@
 package autocert
 
 import (
+	"bytes"
 	"context"
 	"crypto"
 	"crypto/ecdsa"
@@ -32,6 +33,12 @@
 	"golang.org/x/crypto/acme"
 )
 
+var (
+	exampleDomain     = "example.org"
+	exampleCertKey    = certKey{domain: exampleDomain}
+	exampleCertKeyRSA = certKey{domain: exampleDomain, isRSA: true}
+)
+
 var discoTmpl = template.Must(template.New("disco").Parse(`{
 	"new-reg": "{{.}}/new-reg",
 	"new-authz": "{{.}}/new-authz",
@@ -65,6 +72,7 @@
 }`))
 
 type memCache struct {
+	t       *testing.T
 	mu      sync.Mutex
 	keyData map[string][]byte
 }
@@ -80,7 +88,26 @@
 	return v, nil
 }
 
+// filenameSafe returns whether all characters in s are printable ASCII
+// and safe to use in a filename on most filesystems.
+func filenameSafe(s string) bool {
+	for _, c := range s {
+		if c < 0x20 || c > 0x7E {
+			return false
+		}
+		switch c {
+		case '\\', '/', ':', '*', '?', '"', '<', '>', '|':
+			return false
+		}
+	}
+	return true
+}
+
 func (m *memCache) Put(ctx context.Context, key string, data []byte) error {
+	if !filenameSafe(key) {
+		m.t.Errorf("invalid characters in cache key %q", key)
+	}
+
 	m.mu.Lock()
 	defer m.mu.Unlock()
 
@@ -96,12 +123,29 @@
 	return nil
 }
 
-func newMemCache() *memCache {
+func newMemCache(t *testing.T) *memCache {
 	return &memCache{
+		t:       t,
 		keyData: make(map[string][]byte),
 	}
 }
 
+func (m *memCache) numCerts() int {
+	m.mu.Lock()
+	defer m.mu.Unlock()
+
+	res := 0
+	for key := range m.keyData {
+		if strings.HasSuffix(key, "+token") ||
+			strings.HasSuffix(key, "+key") ||
+			strings.HasSuffix(key, "+http-01") {
+			continue
+		}
+		res++
+	}
+	return res
+}
+
 func dummyCert(pub interface{}, san ...string) ([]byte, error) {
 	return dateDummyCert(pub, time.Now(), time.Now().Add(90*24*time.Hour), san...)
 }
@@ -138,43 +182,55 @@
 	return json.Unmarshal(payload, v)
 }
 
+func clientHelloInfo(sni string, ecdsaSupport bool) *tls.ClientHelloInfo {
+	hello := &tls.ClientHelloInfo{
+		ServerName:   sni,
+		CipherSuites: []uint16{tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305},
+	}
+	if ecdsaSupport {
+		hello.CipherSuites = append(hello.CipherSuites, tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305)
+	}
+	return hello
+}
+
 func TestGetCertificate(t *testing.T) {
 	man := &Manager{Prompt: AcceptTOS}
 	defer man.stopRenew()
-	hello := &tls.ClientHelloInfo{ServerName: "example.org"}
+	hello := clientHelloInfo("example.org", true)
 	testGetCertificate(t, man, "example.org", hello)
 }
 
 func TestGetCertificate_trailingDot(t *testing.T) {
 	man := &Manager{Prompt: AcceptTOS}
 	defer man.stopRenew()
-	hello := &tls.ClientHelloInfo{ServerName: "example.org."}
+	hello := clientHelloInfo("example.org.", true)
 	testGetCertificate(t, man, "example.org", hello)
 }
 
 func TestGetCertificate_ForceRSA(t *testing.T) {
 	man := &Manager{
 		Prompt:   AcceptTOS,
-		Cache:    newMemCache(),
+		Cache:    newMemCache(t),
 		ForceRSA: true,
 	}
 	defer man.stopRenew()
-	hello := &tls.ClientHelloInfo{ServerName: "example.org"}
-	testGetCertificate(t, man, "example.org", hello)
+	hello := clientHelloInfo(exampleDomain, true)
+	testGetCertificate(t, man, exampleDomain, hello)
 
-	cert, err := man.cacheGet(context.Background(), "example.org")
+	// ForceRSA was deprecated and is now ignored.
+	cert, err := man.cacheGet(context.Background(), exampleCertKey)
 	if err != nil {
 		t.Fatalf("man.cacheGet: %v", err)
 	}
-	if _, ok := cert.PrivateKey.(*rsa.PrivateKey); !ok {
-		t.Errorf("cert.PrivateKey is %T; want *rsa.PrivateKey", cert.PrivateKey)
+	if _, ok := cert.PrivateKey.(*ecdsa.PrivateKey); !ok {
+		t.Errorf("cert.PrivateKey is %T; want *ecdsa.PrivateKey", cert.PrivateKey)
 	}
 }
 
 func TestGetCertificate_nilPrompt(t *testing.T) {
 	man := &Manager{}
 	defer man.stopRenew()
-	url, finish := startACMEServerStub(t, man, "example.org")
+	url, finish := startACMEServerStub(t, getCertificateFromManager(man, true), "example.org")
 	defer finish()
 	key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
 	if err != nil {
@@ -184,7 +240,7 @@
 		Key:          key,
 		DirectoryURL: url,
 	}
-	hello := &tls.ClientHelloInfo{ServerName: "example.org"}
+	hello := clientHelloInfo("example.org", true)
 	if _, err := man.GetCertificate(hello); err == nil {
 		t.Error("got certificate for example.org; wanted error")
 	}
@@ -198,7 +254,7 @@
 	}
 	tmpl := &x509.Certificate{
 		SerialNumber: big.NewInt(1),
-		Subject:      pkix.Name{CommonName: "example.org"},
+		Subject:      pkix.Name{CommonName: exampleDomain},
 		NotAfter:     time.Now(),
 	}
 	pub, err := x509.CreateCertificate(rand.Reader, tmpl, tmpl, &pk.PublicKey, pk)
@@ -210,16 +266,16 @@
 		PrivateKey:  pk,
 	}
 
-	man := &Manager{Prompt: AcceptTOS, Cache: newMemCache()}
+	man := &Manager{Prompt: AcceptTOS, Cache: newMemCache(t)}
 	defer man.stopRenew()
-	if err := man.cachePut(context.Background(), "example.org", tlscert); err != nil {
+	if err := man.cachePut(context.Background(), exampleCertKey, tlscert); err != nil {
 		t.Fatalf("man.cachePut: %v", err)
 	}
 
 	// The expired cached cert should trigger a new cert issuance
 	// and return without an error.
-	hello := &tls.ClientHelloInfo{ServerName: "example.org"}
-	testGetCertificate(t, man, "example.org", hello)
+	hello := clientHelloInfo(exampleDomain, true)
+	testGetCertificate(t, man, exampleDomain, hello)
 }
 
 func TestGetCertificate_failedAttempt(t *testing.T) {
@@ -228,7 +284,6 @@
 	}))
 	defer ts.Close()
 
-	const example = "example.org"
 	d := createCertRetryAfter
 	f := testDidRemoveState
 	defer func() {
@@ -237,9 +292,9 @@
 	}()
 	createCertRetryAfter = 0
 	done := make(chan struct{})
-	testDidRemoveState = func(domain string) {
-		if domain != example {
-			t.Errorf("testDidRemoveState: domain = %q; want %q", domain, example)
+	testDidRemoveState = func(ck certKey) {
+		if ck != exampleCertKey {
+			t.Errorf("testDidRemoveState: domain = %v; want %v", ck, exampleCertKey)
 		}
 		close(done)
 	}
@@ -256,32 +311,174 @@
 		},
 	}
 	defer man.stopRenew()
-	hello := &tls.ClientHelloInfo{ServerName: example}
+	hello := clientHelloInfo(exampleDomain, true)
 	if _, err := man.GetCertificate(hello); err == nil {
 		t.Error("GetCertificate: err is nil")
 	}
 	select {
 	case <-time.After(5 * time.Second):
-		t.Errorf("took too long to remove the %q state", example)
+		t.Errorf("took too long to remove the %q state", exampleCertKey)
 	case <-done:
 		man.stateMu.Lock()
 		defer man.stateMu.Unlock()
-		if v, exist := man.state[example]; exist {
-			t.Errorf("state exists for %q: %+v", example, v)
+		if v, exist := man.state[exampleCertKey]; exist {
+			t.Errorf("state exists for %v: %+v", exampleCertKey, v)
 		}
 	}
 }
 
+// testGetCertificate_tokenCache tests the fallback of token certificate fetches
+// to cache when Manager.certTokens misses. ecdsaSupport refers to the CA when
+// verifying the certificate token.
+func testGetCertificate_tokenCache(t *testing.T, ecdsaSupport bool) {
+	man1 := &Manager{
+		Cache:  newMemCache(t),
+		Prompt: AcceptTOS,
+	}
+	defer man1.stopRenew()
+	man2 := &Manager{
+		Cache:  man1.Cache,
+		Prompt: AcceptTOS,
+	}
+	defer man2.stopRenew()
+
+	// Send the verification request to a different Manager from the one that
+	// initiated the authorization, when they share caches.
+	url, finish := startACMEServerStub(t, getCertificateFromManager(man2, ecdsaSupport), "example.org")
+	defer finish()
+	key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
+	if err != nil {
+		t.Fatal(err)
+	}
+	man1.Client = &acme.Client{
+		Key:          key,
+		DirectoryURL: url,
+	}
+	hello := clientHelloInfo("example.org", true)
+	if _, err := man1.GetCertificate(hello); err != nil {
+		t.Error(err)
+	}
+	if _, err := man2.GetCertificate(hello); err != nil {
+		t.Error(err)
+	}
+}
+
+func TestGetCertificate_tokenCache(t *testing.T) {
+	t.Run("ecdsaSupport=true", func(t *testing.T) {
+		testGetCertificate_tokenCache(t, true)
+	})
+	t.Run("ecdsaSupport=false", func(t *testing.T) {
+		testGetCertificate_tokenCache(t, false)
+	})
+}
+
+func TestGetCertificate_ecdsaVsRSA(t *testing.T) {
+	cache := newMemCache(t)
+	man := &Manager{Prompt: AcceptTOS, Cache: cache}
+	defer man.stopRenew()
+	url, finish := startACMEServerStub(t, getCertificateFromManager(man, true), "example.org")
+	defer finish()
+	key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
+	if err != nil {
+		t.Fatal(err)
+	}
+	man.Client = &acme.Client{
+		Key:          key,
+		DirectoryURL: url,
+	}
+
+	cert, err := man.GetCertificate(clientHelloInfo("example.org", true))
+	if err != nil {
+		t.Error(err)
+	}
+	if _, ok := cert.Leaf.PublicKey.(*ecdsa.PublicKey); !ok {
+		t.Error("an ECDSA client was served a non-ECDSA certificate")
+	}
+
+	cert, err = man.GetCertificate(clientHelloInfo("example.org", false))
+	if err != nil {
+		t.Error(err)
+	}
+	if _, ok := cert.Leaf.PublicKey.(*rsa.PublicKey); !ok {
+		t.Error("a RSA client was served a non-RSA certificate")
+	}
+
+	if _, err := man.GetCertificate(clientHelloInfo("example.org", true)); err != nil {
+		t.Error(err)
+	}
+	if _, err := man.GetCertificate(clientHelloInfo("example.org", false)); err != nil {
+		t.Error(err)
+	}
+	if numCerts := cache.numCerts(); numCerts != 2 {
+		t.Errorf("found %d certificates in cache; want %d", numCerts, 2)
+	}
+}
+
+func TestGetCertificate_wrongCacheKeyType(t *testing.T) {
+	cache := newMemCache(t)
+	man := &Manager{Prompt: AcceptTOS, Cache: cache}
+	defer man.stopRenew()
+	url, finish := startACMEServerStub(t, getCertificateFromManager(man, true), exampleDomain)
+	defer finish()
+	key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
+	if err != nil {
+		t.Fatal(err)
+	}
+	man.Client = &acme.Client{
+		Key:          key,
+		DirectoryURL: url,
+	}
+
+	// Make an RSA cert and cache it without suffix.
+	pk, err := rsa.GenerateKey(rand.Reader, 512)
+	if err != nil {
+		t.Fatal(err)
+	}
+	tmpl := &x509.Certificate{
+		SerialNumber: big.NewInt(1),
+		Subject:      pkix.Name{CommonName: exampleDomain},
+		NotAfter:     time.Now().Add(90 * 24 * time.Hour),
+	}
+	pub, err := x509.CreateCertificate(rand.Reader, tmpl, tmpl, &pk.PublicKey, pk)
+	if err != nil {
+		t.Fatal(err)
+	}
+	rsaCert := &tls.Certificate{
+		Certificate: [][]byte{pub},
+		PrivateKey:  pk,
+	}
+	if err := man.cachePut(context.Background(), exampleCertKey, rsaCert); err != nil {
+		t.Fatalf("man.cachePut: %v", err)
+	}
+
+	// The RSA cached cert should be silently ignored and replaced.
+	cert, err := man.GetCertificate(clientHelloInfo(exampleDomain, true))
+	if err != nil {
+		t.Error(err)
+	}
+	if _, ok := cert.Leaf.PublicKey.(*ecdsa.PublicKey); !ok {
+		t.Error("an ECDSA client was served a non-ECDSA certificate")
+	}
+	if numCerts := cache.numCerts(); numCerts != 1 {
+		t.Errorf("found %d certificates in cache; want %d", numCerts, 1)
+	}
+}
+
+func getCertificateFromManager(man *Manager, ecdsaSupport bool) func(string) error {
+	return func(sni string) error {
+		_, err := man.GetCertificate(clientHelloInfo(sni, ecdsaSupport))
+		return err
+	}
+}
+
 // startACMEServerStub runs an ACME server
 // The domain argument is the expected domain name of a certificate request.
-func startACMEServerStub(t *testing.T, man *Manager, domain string) (url string, finish func()) {
+func startACMEServerStub(t *testing.T, getCertificate func(string) error, domain string) (url string, finish func()) {
 	// echo token-02 | shasum -a 256
 	// then divide result in 2 parts separated by dot
 	tokenCertName := "4e8eb87631187e9ff2153b56b13a4dec.13a35d002e485d60ff37354b32f665d9.token.acme.invalid"
 	verifyTokenCert := func() {
-		hello := &tls.ClientHelloInfo{ServerName: tokenCertName}
-		_, err := man.GetCertificate(hello)
-		if err != nil {
+		if err := getCertificate(tokenCertName); err != nil {
 			t.Errorf("verifyTokenCert: GetCertificate(%q): %v", tokenCertName, err)
 			return
 		}
@@ -363,8 +560,7 @@
 			tick := time.NewTicker(100 * time.Millisecond)
 			defer tick.Stop()
 			for {
-				hello := &tls.ClientHelloInfo{ServerName: tokenCertName}
-				if _, err := man.GetCertificate(hello); err != nil {
+				if err := getCertificate(tokenCertName); err != nil {
 					return
 				}
 				select {
@@ -388,7 +584,7 @@
 // tests man.GetCertificate flow using the provided hello argument.
 // The domain argument is the expected domain name of a certificate request.
 func testGetCertificate(t *testing.T, man *Manager, domain string, hello *tls.ClientHelloInfo) {
-	url, finish := startACMEServerStub(t, man, domain)
+	url, finish := startACMEServerStub(t, getCertificateFromManager(man, true), domain)
 	defer finish()
 
 	// use EC key to run faster on 386
@@ -446,7 +642,7 @@
 		if w.Code != http.StatusOK {
 			t.Errorf("http token: w.Code = %d; want %d", w.Code, http.StatusOK)
 		}
-		if v := string(w.Body.Bytes()); !strings.HasPrefix(v, "token-http-01.") {
+		if v := w.Body.String(); !strings.HasPrefix(v, "token-http-01.") {
 			t.Errorf("http token value = %q; want 'token-http-01.' prefix", v)
 		}
 	}
@@ -619,7 +815,7 @@
 	// The first 2 are tsl-sni-02 and tls-sni-01 challenges.
 	// The third time an authorization is created but no viable challenge is found.
 	// See revokedAuthz above for more explanation.
-	if _, err := m.createCert(context.Background(), "example.org"); err == nil {
+	if _, err := m.createCert(context.Background(), exampleCertKey); err == nil {
 		t.Errorf("m.createCert returned nil error")
 	}
 	select {
@@ -677,7 +873,7 @@
 }
 
 func TestAccountKeyCache(t *testing.T) {
-	m := Manager{Cache: newMemCache()}
+	m := Manager{Cache: newMemCache(t)}
 	ctx := context.Background()
 	k1, err := m.accountKey(ctx)
 	if err != nil {
@@ -693,36 +889,57 @@
 }
 
 func TestCache(t *testing.T) {
-	privKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
+	ecdsaKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
 	if err != nil {
 		t.Fatal(err)
 	}
-	tmpl := &x509.Certificate{
-		SerialNumber: big.NewInt(1),
-		Subject:      pkix.Name{CommonName: "example.org"},
-		NotAfter:     time.Now().Add(time.Hour),
-	}
-	pub, err := x509.CreateCertificate(rand.Reader, tmpl, tmpl, &privKey.PublicKey, privKey)
+	cert, err := dummyCert(ecdsaKey.Public(), exampleDomain)
 	if err != nil {
 		t.Fatal(err)
 	}
-	tlscert := &tls.Certificate{
-		Certificate: [][]byte{pub},
-		PrivateKey:  privKey,
+	ecdsaCert := &tls.Certificate{
+		Certificate: [][]byte{cert},
+		PrivateKey:  ecdsaKey,
 	}
 
-	man := &Manager{Cache: newMemCache()}
+	rsaKey, err := rsa.GenerateKey(rand.Reader, 512)
+	if err != nil {
+		t.Fatal(err)
+	}
+	cert, err = dummyCert(rsaKey.Public(), exampleDomain)
+	if err != nil {
+		t.Fatal(err)
+	}
+	rsaCert := &tls.Certificate{
+		Certificate: [][]byte{cert},
+		PrivateKey:  rsaKey,
+	}
+
+	man := &Manager{Cache: newMemCache(t)}
 	defer man.stopRenew()
 	ctx := context.Background()
-	if err := man.cachePut(ctx, "example.org", tlscert); err != nil {
+
+	if err := man.cachePut(ctx, exampleCertKey, ecdsaCert); err != nil {
 		t.Fatalf("man.cachePut: %v", err)
 	}
-	res, err := man.cacheGet(ctx, "example.org")
+	if err := man.cachePut(ctx, exampleCertKeyRSA, rsaCert); err != nil {
+		t.Fatalf("man.cachePut: %v", err)
+	}
+
+	res, err := man.cacheGet(ctx, exampleCertKey)
 	if err != nil {
 		t.Fatalf("man.cacheGet: %v", err)
 	}
-	if res == nil {
-		t.Fatal("res is nil")
+	if res == nil || !bytes.Equal(res.Certificate[0], ecdsaCert.Certificate[0]) {
+		t.Errorf("man.cacheGet = %+v; want %+v", res, ecdsaCert)
+	}
+
+	res, err = man.cacheGet(ctx, exampleCertKeyRSA)
+	if err != nil {
+		t.Fatalf("man.cacheGet: %v", err)
+	}
+	if res == nil || !bytes.Equal(res.Certificate[0], rsaCert.Certificate[0]) {
+		t.Errorf("man.cacheGet = %+v; want %+v", res, rsaCert)
 	}
 }
 
@@ -786,26 +1003,28 @@
 	}
 
 	tt := []struct {
-		domain string
-		key    crypto.Signer
-		cert   [][]byte
-		ok     bool
+		ck   certKey
+		key  crypto.Signer
+		cert [][]byte
+		ok   bool
 	}{
-		{"example.org", key1, [][]byte{cert1}, true},
-		{"example.org", key3, [][]byte{cert3}, true},
-		{"example.org", key1, [][]byte{cert1, cert2, cert3}, true},
-		{"example.org", key1, [][]byte{cert1, {1}}, false},
-		{"example.org", key1, [][]byte{{1}}, false},
-		{"example.org", key1, [][]byte{cert2}, false},
-		{"example.org", key2, [][]byte{cert1}, false},
-		{"example.org", key1, [][]byte{cert3}, false},
-		{"example.org", key3, [][]byte{cert1}, false},
-		{"example.net", key1, [][]byte{cert1}, false},
-		{"example.org", key1, [][]byte{early}, false},
-		{"example.org", key1, [][]byte{expired}, false},
+		{certKey{domain: "example.org"}, key1, [][]byte{cert1}, true},
+		{certKey{domain: "example.org", isRSA: true}, key3, [][]byte{cert3}, true},
+		{certKey{domain: "example.org"}, key1, [][]byte{cert1, cert2, cert3}, true},
+		{certKey{domain: "example.org"}, key1, [][]byte{cert1, {1}}, false},
+		{certKey{domain: "example.org"}, key1, [][]byte{{1}}, false},
+		{certKey{domain: "example.org"}, key1, [][]byte{cert2}, false},
+		{certKey{domain: "example.org"}, key2, [][]byte{cert1}, false},
+		{certKey{domain: "example.org"}, key1, [][]byte{cert3}, false},
+		{certKey{domain: "example.org"}, key3, [][]byte{cert1}, false},
+		{certKey{domain: "example.net"}, key1, [][]byte{cert1}, false},
+		{certKey{domain: "example.org"}, key1, [][]byte{early}, false},
+		{certKey{domain: "example.org"}, key1, [][]byte{expired}, false},
+		{certKey{domain: "example.org", isRSA: true}, key1, [][]byte{cert1}, false},
+		{certKey{domain: "example.org"}, key3, [][]byte{cert3}, false},
 	}
 	for i, test := range tt {
-		leaf, err := validCert(test.domain, test.cert, test.key)
+		leaf, err := validCert(test.ck, test.cert, test.key)
 		if err != nil && test.ok {
 			t.Errorf("%d: err = %v", i, err)
 		}
@@ -854,7 +1073,7 @@
 		{"fo.o", "cache.Get of fo.o"},
 	}
 	for _, tt := range tests {
-		_, err := m.GetCertificate(&tls.ClientHelloInfo{ServerName: tt.name})
+		_, err := m.GetCertificate(clientHelloInfo(tt.name, true))
 		got := fmt.Sprint(err)
 		if got != tt.wantErr {
 			t.Errorf("GetCertificate(SNI = %q) = %q; want %q", tt.name, got, tt.wantErr)
@@ -891,3 +1110,62 @@
 		t.Errorf("want %v in Extensions: %v", ext, r.Extensions)
 	}
 }
+
+func TestSupportsECDSA(t *testing.T) {
+	tests := []struct {
+		CipherSuites     []uint16
+		SignatureSchemes []tls.SignatureScheme
+		SupportedCurves  []tls.CurveID
+		ecdsaOk          bool
+	}{
+		{[]uint16{
+			tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
+		}, nil, nil, false},
+		{[]uint16{
+			tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
+		}, nil, nil, true},
+
+		// SignatureSchemes limits, not extends, CipherSuites
+		{[]uint16{
+			tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
+		}, []tls.SignatureScheme{
+			tls.PKCS1WithSHA256, tls.ECDSAWithP256AndSHA256,
+		}, nil, false},
+		{[]uint16{
+			tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
+		}, []tls.SignatureScheme{
+			tls.PKCS1WithSHA256,
+		}, nil, false},
+		{[]uint16{
+			tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
+		}, []tls.SignatureScheme{
+			tls.PKCS1WithSHA256, tls.ECDSAWithP256AndSHA256,
+		}, nil, true},
+
+		{[]uint16{
+			tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
+		}, []tls.SignatureScheme{
+			tls.PKCS1WithSHA256, tls.ECDSAWithP256AndSHA256,
+		}, []tls.CurveID{
+			tls.CurveP521,
+		}, false},
+		{[]uint16{
+			tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
+		}, []tls.SignatureScheme{
+			tls.PKCS1WithSHA256, tls.ECDSAWithP256AndSHA256,
+		}, []tls.CurveID{
+			tls.CurveP256,
+			tls.CurveP521,
+		}, true},
+	}
+	for i, tt := range tests {
+		result := supportsECDSA(&tls.ClientHelloInfo{
+			CipherSuites:     tt.CipherSuites,
+			SignatureSchemes: tt.SignatureSchemes,
+			SupportedCurves:  tt.SupportedCurves,
+		})
+		if result != tt.ecdsaOk {
+			t.Errorf("%d: supportsECDSA = %v; want %v", i, result, tt.ecdsaOk)
+		}
+	}
+}
diff --git a/acme/autocert/cache.go b/acme/autocert/cache.go
index 61a5fd2..aa9aa84 100644
--- a/acme/autocert/cache.go
+++ b/acme/autocert/cache.go
@@ -16,10 +16,10 @@
 var ErrCacheMiss = errors.New("acme/autocert: certificate cache miss")
 
 // Cache is used by Manager to store and retrieve previously obtained certificates
-// as opaque data.
+// and other account data as opaque blobs.
 //
-// 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.
+// Cache implementations should not rely on the key naming pattern. Keys can
+// include any printable ASCII characters, except the following: \/:*?"<>|
 type Cache interface {
 	// Get returns a certificate data for the specified key.
 	// If there's no such key, Get returns ErrCacheMiss.
diff --git a/acme/autocert/renewal.go b/acme/autocert/renewal.go
index 3fa4d61..ef3e44e 100644
--- a/acme/autocert/renewal.go
+++ b/acme/autocert/renewal.go
@@ -17,9 +17,9 @@
 // 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
+	m   *Manager
+	ck  certKey
+	key crypto.Signer
 
 	timerMu sync.Mutex
 	timer   *time.Timer
@@ -77,7 +77,7 @@
 	dr.m.stateMu.Lock()
 	defer dr.m.stateMu.Unlock()
 	dr.key = state.key
-	dr.m.state[dr.domain] = state
+	dr.m.state[dr.ck] = state
 }
 
 // do is similar to Manager.createCert but it doesn't lock a Manager.state item.
@@ -91,7 +91,7 @@
 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(ctx, dr.domain); err == nil {
+	if tlscert, err := dr.m.cacheGet(ctx, dr.ck); err == nil {
 		next := dr.next(tlscert.Leaf.NotAfter)
 		if next > dr.m.renewBefore()+renewJitter {
 			signer, ok := tlscert.PrivateKey.(crypto.Signer)
@@ -107,7 +107,7 @@
 		}
 	}
 
-	der, leaf, err := dr.m.authorizedCert(ctx, dr.key, dr.domain)
+	der, leaf, err := dr.m.authorizedCert(ctx, dr.key, dr.ck)
 	if err != nil {
 		return 0, err
 	}
@@ -120,7 +120,7 @@
 	if err != nil {
 		return 0, err
 	}
-	if err := dr.m.cachePut(ctx, dr.domain, tlscert); err != nil {
+	if err := dr.m.cachePut(ctx, dr.ck, tlscert); err != nil {
 		return 0, err
 	}
 	dr.updateState(state)
diff --git a/acme/autocert/renewal_test.go b/acme/autocert/renewal_test.go
index 6e88672..9dc5982 100644
--- a/acme/autocert/renewal_test.go
+++ b/acme/autocert/renewal_test.go
@@ -48,8 +48,6 @@
 }
 
 func TestRenewFromCache(t *testing.T) {
-	const domain = "example.org"
-
 	// ACME CA server stub
 	var ca *httptest.Server
 	ca = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
@@ -84,7 +82,7 @@
 			if err != nil {
 				t.Fatalf("new-cert: CSR: %v", err)
 			}
-			der, err := dummyCert(csr.PublicKey, domain)
+			der, err := dummyCert(csr.PublicKey, exampleDomain)
 			if err != nil {
 				t.Fatalf("new-cert: dummyCert: %v", err)
 			}
@@ -112,7 +110,7 @@
 	}
 	man := &Manager{
 		Prompt:      AcceptTOS,
-		Cache:       newMemCache(),
+		Cache:       newMemCache(t),
 		RenewBefore: 24 * time.Hour,
 		Client: &acme.Client{
 			Key:          key,
@@ -123,12 +121,12 @@
 
 	// cache an almost expired cert
 	now := time.Now()
-	cert, err := dateDummyCert(key.Public(), now.Add(-2*time.Hour), now.Add(time.Minute), domain)
+	cert, err := dateDummyCert(key.Public(), now.Add(-2*time.Hour), now.Add(time.Minute), exampleDomain)
 	if err != nil {
 		t.Fatal(err)
 	}
 	tlscert := &tls.Certificate{PrivateKey: key, Certificate: [][]byte{cert}}
-	if err := man.cachePut(context.Background(), domain, tlscert); err != nil {
+	if err := man.cachePut(context.Background(), exampleCertKey, tlscert); err != nil {
 		t.Fatal(err)
 	}
 
@@ -152,7 +150,7 @@
 
 		// ensure the new cert is cached
 		after := time.Now().Add(future)
-		tlscert, err := man.cacheGet(context.Background(), domain)
+		tlscert, err := man.cacheGet(context.Background(), exampleCertKey)
 		if err != nil {
 			t.Fatalf("man.cacheGet: %v", err)
 		}
@@ -163,9 +161,9 @@
 		// verify the old cert is also replaced in memory
 		man.stateMu.Lock()
 		defer man.stateMu.Unlock()
-		s := man.state[domain]
+		s := man.state[exampleCertKey]
 		if s == nil {
-			t.Fatalf("m.state[%q] is nil", domain)
+			t.Fatalf("m.state[%q] is nil", exampleCertKey)
 		}
 		tlscert, err = s.tlscert()
 		if err != nil {
@@ -177,7 +175,7 @@
 	}
 
 	// trigger renew
-	hello := &tls.ClientHelloInfo{ServerName: domain}
+	hello := clientHelloInfo(exampleDomain, true)
 	if _, err := man.GetCertificate(hello); err != nil {
 		t.Fatal(err)
 	}
@@ -191,8 +189,6 @@
 }
 
 func TestRenewFromCacheAlreadyRenewed(t *testing.T) {
-	const domain = "example.org"
-
 	// use EC key to run faster on 386
 	key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
 	if err != nil {
@@ -200,7 +196,7 @@
 	}
 	man := &Manager{
 		Prompt:      AcceptTOS,
-		Cache:       newMemCache(),
+		Cache:       newMemCache(t),
 		RenewBefore: 24 * time.Hour,
 		Client: &acme.Client{
 			Key:          key,
@@ -215,38 +211,38 @@
 		t.Fatal(err)
 	}
 	now := time.Now()
-	newCert, err := dateDummyCert(newKey.Public(), now.Add(-2*time.Hour), now.Add(time.Hour*24*90), domain)
+	newCert, err := dateDummyCert(newKey.Public(), now.Add(-2*time.Hour), now.Add(time.Hour*24*90), exampleDomain)
 	if err != nil {
 		t.Fatal(err)
 	}
-	newLeaf, err := validCert(domain, [][]byte{newCert}, newKey)
+	newLeaf, err := validCert(exampleCertKey, [][]byte{newCert}, newKey)
 	if err != nil {
 		t.Fatal(err)
 	}
 	newTLSCert := &tls.Certificate{PrivateKey: newKey, Certificate: [][]byte{newCert}, Leaf: newLeaf}
-	if err := man.cachePut(context.Background(), domain, newTLSCert); err != nil {
+	if err := man.cachePut(context.Background(), exampleCertKey, newTLSCert); err != nil {
 		t.Fatal(err)
 	}
 
 	// set internal state to an almost expired cert
-	oldCert, err := dateDummyCert(key.Public(), now.Add(-2*time.Hour), now.Add(time.Minute), domain)
+	oldCert, err := dateDummyCert(key.Public(), now.Add(-2*time.Hour), now.Add(time.Minute), exampleDomain)
 	if err != nil {
 		t.Fatal(err)
 	}
-	oldLeaf, err := validCert(domain, [][]byte{oldCert}, key)
+	oldLeaf, err := validCert(exampleCertKey, [][]byte{oldCert}, key)
 	if err != nil {
 		t.Fatal(err)
 	}
 	man.stateMu.Lock()
 	if man.state == nil {
-		man.state = make(map[string]*certState)
+		man.state = make(map[certKey]*certState)
 	}
 	s := &certState{
 		key:  key,
 		cert: [][]byte{oldCert},
 		leaf: oldLeaf,
 	}
-	man.state[domain] = s
+	man.state[exampleCertKey] = s
 	man.stateMu.Unlock()
 
 	// veriy the renewal accepted the newer cached cert
@@ -267,7 +263,7 @@
 		}
 
 		// ensure the cached cert was not modified
-		tlscert, err := man.cacheGet(context.Background(), domain)
+		tlscert, err := man.cacheGet(context.Background(), exampleCertKey)
 		if err != nil {
 			t.Fatalf("man.cacheGet: %v", err)
 		}
@@ -278,9 +274,9 @@
 		// verify the old cert is also replaced in memory
 		man.stateMu.Lock()
 		defer man.stateMu.Unlock()
-		s := man.state[domain]
+		s := man.state[exampleCertKey]
 		if s == nil {
-			t.Fatalf("m.state[%q] is nil", domain)
+			t.Fatalf("m.state[%q] is nil", exampleCertKey)
 		}
 		stateKey := s.key.Public().(*ecdsa.PublicKey)
 		if stateKey.X.Cmp(newKey.X) != 0 || stateKey.Y.Cmp(newKey.Y) != 0 {
@@ -295,9 +291,9 @@
 		}
 
 		// verify the private key is replaced in the renewal state
-		r := man.renewal[domain]
+		r := man.renewal[exampleCertKey]
 		if r == nil {
-			t.Fatalf("m.renewal[%q] is nil", domain)
+			t.Fatalf("m.renewal[%q] is nil", exampleCertKey)
 		}
 		renewalKey := r.key.Public().(*ecdsa.PublicKey)
 		if renewalKey.X.Cmp(newKey.X) != 0 || renewalKey.Y.Cmp(newKey.Y) != 0 {
@@ -307,7 +303,7 @@
 	}
 
 	// assert the expiring cert is returned from state
-	hello := &tls.ClientHelloInfo{ServerName: domain}
+	hello := clientHelloInfo(exampleDomain, true)
 	tlscert, err := man.GetCertificate(hello)
 	if err != nil {
 		t.Fatal(err)
@@ -317,7 +313,7 @@
 	}
 
 	// trigger renew
-	go man.renew(domain, s.key, s.leaf.NotAfter)
+	go man.renew(exampleCertKey, s.key, s.leaf.NotAfter)
 
 	// wait for renew loop
 	select {
@@ -325,7 +321,7 @@
 		t.Fatal("renew took too long to occur")
 	case <-done:
 		// assert the new cert is returned from state after renew
-		hello := &tls.ClientHelloInfo{ServerName: domain}
+		hello := clientHelloInfo(exampleDomain, true)
 		tlscert, err := man.GetCertificate(hello)
 		if err != nil {
 			t.Fatal(err)