Browse Source

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>
Filippo Valsorda 7 years ago
parent
commit
8f8078c97f

+ 139 - 63
acme/autocert/autocert.go

@@ -101,8 +101,7 @@ type Manager struct {
 	// Cache optionally stores and retrieves previously-obtained certificates.
 	// Cache optionally stores and retrieves previously-obtained certificates.
 	// If nil, certs will only be cached for the lifetime of the Manager.
 	// 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
 	Cache Cache
 
 
 	// HostPolicy controls which domains the Manager will attempt
 	// HostPolicy controls which domains the Manager will attempt
@@ -140,10 +139,10 @@ type Manager struct {
 	// If the Client's account key is already registered, Email is not used.
 	// If the Client's account key is already registered, Email is not used.
 	Email string
 	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
 	ForceRSA bool
 
 
 	// ExtraExtensions are used when generating a new CSR (Certificate Request),
 	// ExtraExtensions are used when generating a new CSR (Certificate Request),
@@ -159,12 +158,11 @@ type Manager struct {
 	client   *acme.Client // initialized by acmeClient method
 	client   *acme.Client // initialized by acmeClient method
 
 
 	stateMu sync.Mutex
 	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.
 	// renewal tracks the set of domains currently running renewal timers.
-	// It is keyed by domain name.
 	renewalMu sync.Mutex
 	renewalMu sync.Mutex
-	renewal   map[string]*domainRenewal
+	renewal   map[certKey]*domainRenewal
 
 
 	// tokensMu guards the rest of the fields: tryHTTP01, certTokens and httpTokens.
 	// tokensMu guards the rest of the fields: tryHTTP01, certTokens and httpTokens.
 	tokensMu sync.RWMutex
 	tokensMu sync.RWMutex
@@ -183,6 +181,23 @@ type Manager struct {
 	certTokens map[string]*tls.Certificate
 	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.
 // GetCertificate implements the tls.Config.GetCertificate hook.
 // It provides a TLS certificate for hello.ServerName host, including answering
 // It provides a TLS certificate for hello.ServerName host, including answering
 // *.acme.invalid (TLS-SNI) challenges. All other fields of hello are ignored.
 // *.acme.invalid (TLS-SNI) challenges. All other fields of hello are ignored.
@@ -203,7 +218,7 @@ func (m *Manager) GetCertificate(hello *tls.ClientHelloInfo) (*tls.Certificate,
 	if !strings.Contains(strings.Trim(name, "."), ".") {
 	if !strings.Contains(strings.Trim(name, "."), ".") {
 		return nil, errors.New("acme/autocert: server name component count invalid")
 		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")
 		return nil, errors.New("acme/autocert: server name contains invalid character")
 	}
 	}
 
 
@@ -219,7 +234,7 @@ func (m *Manager) GetCertificate(hello *tls.ClientHelloInfo) (*tls.Certificate,
 		if cert := m.certTokens[name]; cert != nil {
 		if cert := m.certTokens[name]; cert != nil {
 			return 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
 			return cert, nil
 		}
 		}
 		// TODO: cache error results?
 		// TODO: cache error results?
@@ -227,8 +242,11 @@ func (m *Manager) GetCertificate(hello *tls.ClientHelloInfo) (*tls.Certificate,
 	}
 	}
 
 
 	// regular domain
 	// 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 {
 	if err == nil {
 		return cert, nil
 		return cert, nil
 	}
 	}
@@ -240,14 +258,59 @@ func (m *Manager) GetCertificate(hello *tls.ClientHelloInfo) (*tls.Certificate,
 	if err := m.hostPolicy()(ctx, name); err != nil {
 	if err := m.hostPolicy()(ctx, name); err != nil {
 		return nil, err
 		return nil, err
 	}
 	}
-	cert, err = m.createCert(ctx, name)
+	cert, err = m.createCert(ctx, ck)
 	if err != nil {
 	if err != nil {
 		return nil, err
 		return nil, err
 	}
 	}
-	m.cachePut(ctx, name, cert)
+	m.cachePut(ctx, ck, cert)
 	return cert, nil
 	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.
 // HTTPHandler configures the Manager to provision ACME "http-01" challenge responses.
 // It returns an http.Handler that responds to the challenges and must be
 // 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,
 // running on port 80. If it receives a request that is not an ACME challenge,
@@ -313,16 +376,16 @@ func stripPort(hostport string) string {
 // cert returns an existing certificate either from m.state or cache.
 // 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
 // If a certificate is found in cache but not in m.state, the latter will be filled
 // with the cached value.
 // 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()
 	m.stateMu.Lock()
-	if s, ok := m.state[name]; ok {
+	if s, ok := m.state[ck]; ok {
 		m.stateMu.Unlock()
 		m.stateMu.Unlock()
 		s.RLock()
 		s.RLock()
 		defer s.RUnlock()
 		defer s.RUnlock()
 		return s.tlscert()
 		return s.tlscert()
 	}
 	}
 	defer m.stateMu.Unlock()
 	defer m.stateMu.Unlock()
-	cert, err := m.cacheGet(ctx, name)
+	cert, err := m.cacheGet(ctx, ck)
 	if err != nil {
 	if err != nil {
 		return nil, err
 		return nil, err
 	}
 	}
@@ -331,25 +394,25 @@ func (m *Manager) cert(ctx context.Context, name string) (*tls.Certificate, erro
 		return nil, errors.New("acme/autocert: private key cannot sign")
 		return nil, errors.New("acme/autocert: private key cannot sign")
 	}
 	}
 	if m.state == nil {
 	if m.state == nil {
-		m.state = make(map[string]*certState)
+		m.state = make(map[certKey]*certState)
 	}
 	}
 	s := &certState{
 	s := &certState{
 		key:  signer,
 		key:  signer,
 		cert: cert.Certificate,
 		cert: cert.Certificate,
 		leaf: cert.Leaf,
 		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
 	return cert, nil
 }
 }
 
 
 // cacheGet always returns a valid certificate, or an error otherwise.
 // 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 {
 	if m.Cache == nil {
 		return nil, ErrCacheMiss
 		return nil, ErrCacheMiss
 	}
 	}
-	data, err := m.Cache.Get(ctx, domain)
+	data, err := m.Cache.Get(ctx, ck.String())
 	if err != nil {
 	if err != nil {
 		return nil, err
 		return nil, err
 	}
 	}
@@ -380,7 +443,7 @@ func (m *Manager) cacheGet(ctx context.Context, domain string) (*tls.Certificate
 	}
 	}
 
 
 	// verify and create TLS cert
 	// verify and create TLS cert
-	leaf, err := validCert(domain, pubDER, privKey)
+	leaf, err := validCert(ck, pubDER, privKey)
 	if err != nil {
 	if err != nil {
 		return nil, ErrCacheMiss
 		return nil, ErrCacheMiss
 	}
 	}
@@ -392,7 +455,7 @@ func (m *Manager) cacheGet(ctx context.Context, domain string) (*tls.Certificate
 	return tlscert, nil
 	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 {
 	if m.Cache == nil {
 		return nil
 		return nil
 	}
 	}
@@ -424,7 +487,7 @@ func (m *Manager) cachePut(ctx context.Context, domain string, tlscert *tls.Cert
 		}
 		}
 	}
 	}
 
 
-	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 {
 func encodeECDSAKey(w io.Writer, key *ecdsa.PrivateKey) error {
@@ -441,9 +504,9 @@ func encodeECDSAKey(w io.Writer, key *ecdsa.PrivateKey) error {
 //
 //
 // If the domain is already being verified, it waits for the existing verification to complete.
 // 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.
 // 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
 	// TODO: maybe rewrite this whole piece using sync.Once
-	state, err := m.certState(domain)
+	state, err := m.certState(ck)
 	if err != nil {
 	if err != nil {
 		return nil, err
 		return nil, err
 	}
 	}
@@ -461,44 +524,44 @@ func (m *Manager) createCert(ctx context.Context, domain string) (*tls.Certifica
 	defer state.Unlock()
 	defer state.Unlock()
 	state.locked = false
 	state.locked = false
 
 
-	der, leaf, err := m.authorizedCert(ctx, state.key, domain)
+	der, leaf, err := m.authorizedCert(ctx, state.key, ck)
 	if err != nil {
 	if err != nil {
 		// Remove the failed state after some time,
 		// Remove the failed state after some time,
 		// making the manager call createCert again on the following TLS hello.
 		// making the manager call createCert again on the following TLS hello.
 		time.AfterFunc(createCertRetryAfter, func() {
 		time.AfterFunc(createCertRetryAfter, func() {
-			defer testDidRemoveState(domain)
+			defer testDidRemoveState(ck)
 			m.stateMu.Lock()
 			m.stateMu.Lock()
 			defer m.stateMu.Unlock()
 			defer m.stateMu.Unlock()
 			// Verify the state hasn't changed and it's still invalid
 			// Verify the state hasn't changed and it's still invalid
 			// before deleting.
 			// before deleting.
-			s, ok := m.state[domain]
+			s, ok := m.state[ck]
 			if !ok {
 			if !ok {
 				return
 				return
 			}
 			}
-			if _, err := validCert(domain, s.cert, s.key); err == nil {
+			if _, err := validCert(ck, s.cert, s.key); err == nil {
 				return
 				return
 			}
 			}
-			delete(m.state, domain)
+			delete(m.state, ck)
 		})
 		})
 		return nil, err
 		return nil, err
 	}
 	}
 	state.cert = der
 	state.cert = der
 	state.leaf = leaf
 	state.leaf = leaf
-	go m.renew(domain, state.key, state.leaf.NotAfter)
+	go m.renew(ck, state.key, state.leaf.NotAfter)
 	return state.tlscert()
 	return state.tlscert()
 }
 }
 
 
 // certState returns a new or existing certState.
 // certState returns a new or existing certState.
 // If a new certState is returned, state.exist is false and the state is locked.
 // 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.
 // 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()
 	m.stateMu.Lock()
 	defer m.stateMu.Unlock()
 	defer m.stateMu.Unlock()
 	if m.state == nil {
 	if m.state == nil {
-		m.state = make(map[string]*certState)
+		m.state = make(map[certKey]*certState)
 	}
 	}
 	// existing state
 	// existing state
-	if state, ok := m.state[domain]; ok {
+	if state, ok := m.state[ck]; ok {
 		return state, nil
 		return state, nil
 	}
 	}
 
 
@@ -507,7 +570,7 @@ func (m *Manager) certState(domain string) (*certState, error) {
 		err error
 		err error
 		key crypto.Signer
 		key crypto.Signer
 	)
 	)
-	if m.ForceRSA {
+	if ck.isRSA {
 		key, err = rsa.GenerateKey(rand.Reader, 2048)
 		key, err = rsa.GenerateKey(rand.Reader, 2048)
 	} else {
 	} else {
 		key, err = ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
 		key, err = ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
@@ -521,22 +584,22 @@ func (m *Manager) certState(domain string) (*certState, error) {
 		locked: true,
 		locked: true,
 	}
 	}
 	state.Lock() // will be unlocked by m.certState caller
 	state.Lock() // will be unlocked by m.certState caller
-	m.state[domain] = state
+	m.state[ck] = state
 	return state, nil
 	return state, nil
 }
 }
 
 
 // authorizedCert starts the domain ownership verification process and requests a new cert upon success.
 // authorizedCert starts the domain ownership verification process and requests a new cert upon success.
 // The key argument is the certificate private key.
 // 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)
 	client, err := m.acmeClient(ctx)
 	if err != nil {
 	if err != nil {
 		return nil, nil, err
 		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
 		return nil, nil, err
 	}
 	}
-	csr, err := certRequest(key, domain, m.ExtraExtensions)
+	csr, err := certRequest(key, ck.domain, m.ExtraExtensions)
 	if err != nil {
 	if err != nil {
 		return nil, nil, err
 		return nil, nil, err
 	}
 	}
@@ -544,7 +607,7 @@ func (m *Manager) authorizedCert(ctx context.Context, key crypto.Signer, domain
 	if err != nil {
 	if err != nil {
 		return nil, nil, err
 		return nil, nil, err
 	}
 	}
-	leaf, err = validCert(domain, der, key)
+	leaf, err = validCert(ck, der, key)
 	if err != nil {
 	if err != nil {
 		return nil, nil, err
 		return nil, nil, err
 	}
 	}
@@ -674,8 +737,8 @@ func pickChallenge(typ string, chal []*acme.Challenge) *acme.Challenge {
 	return nil
 	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) {
 func (m *Manager) putCertToken(ctx context.Context, name string, cert *tls.Certificate) {
 	m.tokensMu.Lock()
 	m.tokensMu.Lock()
 	defer m.tokensMu.Unlock()
 	defer m.tokensMu.Unlock()
@@ -683,17 +746,18 @@ func (m *Manager) putCertToken(ctx context.Context, name string, cert *tls.Certi
 		m.certTokens = make(map[string]*tls.Certificate)
 		m.certTokens = make(map[string]*tls.Certificate)
 	}
 	}
 	m.certTokens[name] = cert
 	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.
 // from both m.certTokens map and m.Cache.
 func (m *Manager) deleteCertToken(name string) {
 func (m *Manager) deleteCertToken(name string) {
 	m.tokensMu.Lock()
 	m.tokensMu.Lock()
 	defer m.tokensMu.Unlock()
 	defer m.tokensMu.Unlock()
 	delete(m.certTokens, name)
 	delete(m.certTokens, name)
 	if m.Cache != nil {
 	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 @@ func (m *Manager) deleteHTTPToken(tokenPath string) {
 // httpTokenCacheKey returns a key at which an http-01 token value may be stored
 // httpTokenCacheKey returns a key at which an http-01 token value may be stored
 // in the Manager's optional Cache.
 // in the Manager's optional Cache.
 func httpTokenCacheKey(tokenPath string) string {
 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.
 // renew starts a cert renewal timer loop, one per domain.
@@ -755,18 +819,18 @@ func httpTokenCacheKey(tokenPath string) string {
 //
 //
 // The key argument is a certificate private key.
 // The key argument is a certificate private key.
 // The exp argument is the cert expiration time (NotAfter).
 // 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()
 	m.renewalMu.Lock()
 	defer m.renewalMu.Unlock()
 	defer m.renewalMu.Unlock()
-	if m.renewal[domain] != nil {
+	if m.renewal[ck] != nil {
 		// another goroutine is already on it
 		// another goroutine is already on it
 		return
 		return
 	}
 	}
 	if m.renewal == nil {
 	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)
 	dr.start(exp)
 }
 }
 
 
@@ -782,7 +846,10 @@ func (m *Manager) stopRenew() {
 }
 }
 
 
 func (m *Manager) accountKey(ctx context.Context) (crypto.Signer, error) {
 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) {
 	genKey := func() (*ecdsa.PrivateKey, error) {
 		return ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
 		return ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
@@ -793,6 +860,9 @@ func (m *Manager) accountKey(ctx context.Context) (crypto.Signer, error) {
 	}
 	}
 
 
 	data, err := m.Cache.Get(ctx, keyName)
 	data, err := m.Cache.Get(ctx, keyName)
+	if err == ErrCacheMiss {
+		data, err = m.Cache.Get(ctx, legacyKeyName)
+	}
 	if err == ErrCacheMiss {
 	if err == ErrCacheMiss {
 		key, err := genKey()
 		key, err := genKey()
 		if err != nil {
 		if err != nil {
@@ -925,12 +995,12 @@ func parsePrivateKey(der []byte) (crypto.Signer, error) {
 	return nil, errors.New("acme/autocert: failed to parse private key")
 	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.
 // 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)
 	// parse public part(s)
 	var n int
 	var n int
 	for _, b := range der {
 	for _, b := range der {
@@ -942,7 +1012,7 @@ func validCert(domain string, der [][]byte, key crypto.Signer) (leaf *x509.Certi
 		n += copy(pub[n:], b)
 		n += copy(pub[n:], b)
 	}
 	}
 	x509Cert, err := x509.ParseCertificates(pub)
 	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")
 		return nil, errors.New("acme/autocert: no public key found")
 	}
 	}
 	// verify the leaf is not expired and matches the domain name
 	// verify the leaf is not expired and matches the domain name
@@ -954,10 +1024,10 @@ func validCert(domain string, der [][]byte, key crypto.Signer) (leaf *x509.Certi
 	if now.After(leaf.NotAfter) {
 	if now.After(leaf.NotAfter) {
 		return nil, errors.New("acme/autocert: expired certificate")
 		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
 		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) {
 	switch pub := leaf.PublicKey.(type) {
 	case *rsa.PublicKey:
 	case *rsa.PublicKey:
 		prv, ok := key.(*rsa.PrivateKey)
 		prv, ok := key.(*rsa.PrivateKey)
@@ -967,6 +1037,9 @@ func validCert(domain string, der [][]byte, key crypto.Signer) (leaf *x509.Certi
 		if pub.N.Cmp(prv.N) != 0 {
 		if pub.N.Cmp(prv.N) != 0 {
 			return nil, errors.New("acme/autocert: private key does not match public key")
 			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:
 	case *ecdsa.PublicKey:
 		prv, ok := key.(*ecdsa.PrivateKey)
 		prv, ok := key.(*ecdsa.PrivateKey)
 		if !ok {
 		if !ok {
@@ -975,6 +1048,9 @@ func validCert(domain string, der [][]byte, key crypto.Signer) (leaf *x509.Certi
 		if pub.X.Cmp(prv.X) != 0 || pub.Y.Cmp(prv.Y) != 0 {
 		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")
 			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:
 	default:
 		return nil, errors.New("acme/autocert: unknown public key algorithm")
 		return nil, errors.New("acme/autocert: unknown public key algorithm")
 	}
 	}
@@ -998,5 +1074,5 @@ var (
 	timeNow = time.Now
 	timeNow = time.Now
 
 
 	// Called when a state is removed.
 	// Called when a state is removed.
-	testDidRemoveState = func(domain string) {}
+	testDidRemoveState = func(certKey) {}
 )
 )

+ 344 - 66
acme/autocert/autocert_test.go

@@ -5,6 +5,7 @@
 package autocert
 package autocert
 
 
 import (
 import (
+	"bytes"
 	"context"
 	"context"
 	"crypto"
 	"crypto"
 	"crypto/ecdsa"
 	"crypto/ecdsa"
@@ -32,6 +33,12 @@ import (
 	"golang.org/x/crypto/acme"
 	"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(`{
 var discoTmpl = template.Must(template.New("disco").Parse(`{
 	"new-reg": "{{.}}/new-reg",
 	"new-reg": "{{.}}/new-reg",
 	"new-authz": "{{.}}/new-authz",
 	"new-authz": "{{.}}/new-authz",
@@ -65,6 +72,7 @@ var authzTmpl = template.Must(template.New("authz").Parse(`{
 }`))
 }`))
 
 
 type memCache struct {
 type memCache struct {
+	t       *testing.T
 	mu      sync.Mutex
 	mu      sync.Mutex
 	keyData map[string][]byte
 	keyData map[string][]byte
 }
 }
@@ -80,7 +88,26 @@ func (m *memCache) Get(ctx context.Context, key string) ([]byte, error) {
 	return v, nil
 	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 {
 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()
 	m.mu.Lock()
 	defer m.mu.Unlock()
 	defer m.mu.Unlock()
 
 
@@ -96,12 +123,29 @@ func (m *memCache) Delete(ctx context.Context, key string) error {
 	return nil
 	return nil
 }
 }
 
 
-func newMemCache() *memCache {
+func newMemCache(t *testing.T) *memCache {
 	return &memCache{
 	return &memCache{
+		t:       t,
 		keyData: make(map[string][]byte),
 		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) {
 func dummyCert(pub interface{}, san ...string) ([]byte, error) {
 	return dateDummyCert(pub, time.Now(), time.Now().Add(90*24*time.Hour), san...)
 	return dateDummyCert(pub, time.Now(), time.Now().Add(90*24*time.Hour), san...)
 }
 }
@@ -138,43 +182,55 @@ func decodePayload(v interface{}, r io.Reader) error {
 	return json.Unmarshal(payload, v)
 	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) {
 func TestGetCertificate(t *testing.T) {
 	man := &Manager{Prompt: AcceptTOS}
 	man := &Manager{Prompt: AcceptTOS}
 	defer man.stopRenew()
 	defer man.stopRenew()
-	hello := &tls.ClientHelloInfo{ServerName: "example.org"}
+	hello := clientHelloInfo("example.org", true)
 	testGetCertificate(t, man, "example.org", hello)
 	testGetCertificate(t, man, "example.org", hello)
 }
 }
 
 
 func TestGetCertificate_trailingDot(t *testing.T) {
 func TestGetCertificate_trailingDot(t *testing.T) {
 	man := &Manager{Prompt: AcceptTOS}
 	man := &Manager{Prompt: AcceptTOS}
 	defer man.stopRenew()
 	defer man.stopRenew()
-	hello := &tls.ClientHelloInfo{ServerName: "example.org."}
+	hello := clientHelloInfo("example.org.", true)
 	testGetCertificate(t, man, "example.org", hello)
 	testGetCertificate(t, man, "example.org", hello)
 }
 }
 
 
 func TestGetCertificate_ForceRSA(t *testing.T) {
 func TestGetCertificate_ForceRSA(t *testing.T) {
 	man := &Manager{
 	man := &Manager{
 		Prompt:   AcceptTOS,
 		Prompt:   AcceptTOS,
-		Cache:    newMemCache(),
+		Cache:    newMemCache(t),
 		ForceRSA: true,
 		ForceRSA: true,
 	}
 	}
 	defer man.stopRenew()
 	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 {
 	if err != nil {
 		t.Fatalf("man.cacheGet: %v", err)
 		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) {
 func TestGetCertificate_nilPrompt(t *testing.T) {
 	man := &Manager{}
 	man := &Manager{}
 	defer man.stopRenew()
 	defer man.stopRenew()
-	url, finish := startACMEServerStub(t, man, "example.org")
+	url, finish := startACMEServerStub(t, getCertificateFromManager(man, true), "example.org")
 	defer finish()
 	defer finish()
 	key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
 	key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
 	if err != nil {
 	if err != nil {
@@ -184,7 +240,7 @@ func TestGetCertificate_nilPrompt(t *testing.T) {
 		Key:          key,
 		Key:          key,
 		DirectoryURL: url,
 		DirectoryURL: url,
 	}
 	}
-	hello := &tls.ClientHelloInfo{ServerName: "example.org"}
+	hello := clientHelloInfo("example.org", true)
 	if _, err := man.GetCertificate(hello); err == nil {
 	if _, err := man.GetCertificate(hello); err == nil {
 		t.Error("got certificate for example.org; wanted error")
 		t.Error("got certificate for example.org; wanted error")
 	}
 	}
@@ -198,7 +254,7 @@ func TestGetCertificate_expiredCache(t *testing.T) {
 	}
 	}
 	tmpl := &x509.Certificate{
 	tmpl := &x509.Certificate{
 		SerialNumber: big.NewInt(1),
 		SerialNumber: big.NewInt(1),
-		Subject:      pkix.Name{CommonName: "example.org"},
+		Subject:      pkix.Name{CommonName: exampleDomain},
 		NotAfter:     time.Now(),
 		NotAfter:     time.Now(),
 	}
 	}
 	pub, err := x509.CreateCertificate(rand.Reader, tmpl, tmpl, &pk.PublicKey, pk)
 	pub, err := x509.CreateCertificate(rand.Reader, tmpl, tmpl, &pk.PublicKey, pk)
@@ -210,16 +266,16 @@ func TestGetCertificate_expiredCache(t *testing.T) {
 		PrivateKey:  pk,
 		PrivateKey:  pk,
 	}
 	}
 
 
-	man := &Manager{Prompt: AcceptTOS, Cache: newMemCache()}
+	man := &Manager{Prompt: AcceptTOS, Cache: newMemCache(t)}
 	defer man.stopRenew()
 	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)
 		t.Fatalf("man.cachePut: %v", err)
 	}
 	}
 
 
 	// The expired cached cert should trigger a new cert issuance
 	// The expired cached cert should trigger a new cert issuance
 	// and return without an error.
 	// 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) {
 func TestGetCertificate_failedAttempt(t *testing.T) {
@@ -228,7 +284,6 @@ func TestGetCertificate_failedAttempt(t *testing.T) {
 	}))
 	}))
 	defer ts.Close()
 	defer ts.Close()
 
 
-	const example = "example.org"
 	d := createCertRetryAfter
 	d := createCertRetryAfter
 	f := testDidRemoveState
 	f := testDidRemoveState
 	defer func() {
 	defer func() {
@@ -237,9 +292,9 @@ func TestGetCertificate_failedAttempt(t *testing.T) {
 	}()
 	}()
 	createCertRetryAfter = 0
 	createCertRetryAfter = 0
 	done := make(chan struct{})
 	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)
 		close(done)
 	}
 	}
@@ -256,32 +311,174 @@ func TestGetCertificate_failedAttempt(t *testing.T) {
 		},
 		},
 	}
 	}
 	defer man.stopRenew()
 	defer man.stopRenew()
-	hello := &tls.ClientHelloInfo{ServerName: example}
+	hello := clientHelloInfo(exampleDomain, true)
 	if _, err := man.GetCertificate(hello); err == nil {
 	if _, err := man.GetCertificate(hello); err == nil {
 		t.Error("GetCertificate: err is nil")
 		t.Error("GetCertificate: err is nil")
 	}
 	}
 	select {
 	select {
 	case <-time.After(5 * time.Second):
 	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:
 	case <-done:
 		man.stateMu.Lock()
 		man.stateMu.Lock()
 		defer man.stateMu.Unlock()
 		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
 // startACMEServerStub runs an ACME server
 // The domain argument is the expected domain name of a certificate request.
 // 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
 	// echo token-02 | shasum -a 256
 	// then divide result in 2 parts separated by dot
 	// then divide result in 2 parts separated by dot
 	tokenCertName := "4e8eb87631187e9ff2153b56b13a4dec.13a35d002e485d60ff37354b32f665d9.token.acme.invalid"
 	tokenCertName := "4e8eb87631187e9ff2153b56b13a4dec.13a35d002e485d60ff37354b32f665d9.token.acme.invalid"
 	verifyTokenCert := func() {
 	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)
 			t.Errorf("verifyTokenCert: GetCertificate(%q): %v", tokenCertName, err)
 			return
 			return
 		}
 		}
@@ -363,8 +560,7 @@ func startACMEServerStub(t *testing.T, man *Manager, domain string) (url string,
 			tick := time.NewTicker(100 * time.Millisecond)
 			tick := time.NewTicker(100 * time.Millisecond)
 			defer tick.Stop()
 			defer tick.Stop()
 			for {
 			for {
-				hello := &tls.ClientHelloInfo{ServerName: tokenCertName}
-				if _, err := man.GetCertificate(hello); err != nil {
+				if err := getCertificate(tokenCertName); err != nil {
 					return
 					return
 				}
 				}
 				select {
 				select {
@@ -388,7 +584,7 @@ func startACMEServerStub(t *testing.T, man *Manager, domain string) (url string,
 // tests man.GetCertificate flow using the provided hello argument.
 // tests man.GetCertificate flow using the provided hello argument.
 // The domain argument is the expected domain name of a certificate request.
 // The domain argument is the expected domain name of a certificate request.
 func testGetCertificate(t *testing.T, man *Manager, domain string, hello *tls.ClientHelloInfo) {
 func 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()
 	defer finish()
 
 
 	// use EC key to run faster on 386
 	// use EC key to run faster on 386
@@ -446,7 +642,7 @@ func TestVerifyHTTP01(t *testing.T) {
 		if w.Code != http.StatusOK {
 		if w.Code != http.StatusOK {
 			t.Errorf("http token: w.Code = %d; want %d", 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)
 			t.Errorf("http token value = %q; want 'token-http-01.' prefix", v)
 		}
 		}
 	}
 	}
@@ -619,7 +815,7 @@ func TestRevokeFailedAuthz(t *testing.T) {
 	// The first 2 are tsl-sni-02 and tls-sni-01 challenges.
 	// 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.
 	// The third time an authorization is created but no viable challenge is found.
 	// See revokedAuthz above for more explanation.
 	// 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")
 		t.Errorf("m.createCert returned nil error")
 	}
 	}
 	select {
 	select {
@@ -677,7 +873,7 @@ func TestHTTPHandlerDefaultFallback(t *testing.T) {
 }
 }
 
 
 func TestAccountKeyCache(t *testing.T) {
 func TestAccountKeyCache(t *testing.T) {
-	m := Manager{Cache: newMemCache()}
+	m := Manager{Cache: newMemCache(t)}
 	ctx := context.Background()
 	ctx := context.Background()
 	k1, err := m.accountKey(ctx)
 	k1, err := m.accountKey(ctx)
 	if err != nil {
 	if err != nil {
@@ -693,36 +889,57 @@ func TestAccountKeyCache(t *testing.T) {
 }
 }
 
 
 func TestCache(t *testing.T) {
 func TestCache(t *testing.T) {
-	privKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
+	ecdsaKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
 	if err != nil {
 	if err != nil {
 		t.Fatal(err)
 		t.Fatal(err)
 	}
 	}
-	tmpl := &x509.Certificate{
-		SerialNumber: big.NewInt(1),
-		Subject:      pkix.Name{CommonName: "example.org"},
-		NotAfter:     time.Now().Add(time.Hour),
+	cert, err := dummyCert(ecdsaKey.Public(), exampleDomain)
+	if err != nil {
+		t.Fatal(err)
 	}
 	}
-	pub, err := x509.CreateCertificate(rand.Reader, tmpl, tmpl, &privKey.PublicKey, privKey)
+	ecdsaCert := &tls.Certificate{
+		Certificate: [][]byte{cert},
+		PrivateKey:  ecdsaKey,
+	}
+
+	rsaKey, err := rsa.GenerateKey(rand.Reader, 512)
 	if err != nil {
 	if err != nil {
 		t.Fatal(err)
 		t.Fatal(err)
 	}
 	}
-	tlscert := &tls.Certificate{
-		Certificate: [][]byte{pub},
-		PrivateKey:  privKey,
+	cert, err = dummyCert(rsaKey.Public(), exampleDomain)
+	if err != nil {
+		t.Fatal(err)
+	}
+	rsaCert := &tls.Certificate{
+		Certificate: [][]byte{cert},
+		PrivateKey:  rsaKey,
 	}
 	}
 
 
-	man := &Manager{Cache: newMemCache()}
+	man := &Manager{Cache: newMemCache(t)}
 	defer man.stopRenew()
 	defer man.stopRenew()
 	ctx := context.Background()
 	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)
 		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 || !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 {
 	if err != nil {
 		t.Fatalf("man.cacheGet: %v", err)
 		t.Fatalf("man.cacheGet: %v", err)
 	}
 	}
-	if res == nil {
-		t.Fatal("res is nil")
+	if res == nil || !bytes.Equal(res.Certificate[0], rsaCert.Certificate[0]) {
+		t.Errorf("man.cacheGet = %+v; want %+v", res, rsaCert)
 	}
 	}
 }
 }
 
 
@@ -786,26 +1003,28 @@ func TestValidCert(t *testing.T) {
 	}
 	}
 
 
 	tt := []struct {
 	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 {
 	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 {
 		if err != nil && test.ok {
 			t.Errorf("%d: err = %v", i, err)
 			t.Errorf("%d: err = %v", i, err)
 		}
 		}
@@ -854,7 +1073,7 @@ func TestManagerGetCertificateBogusSNI(t *testing.T) {
 		{"fo.o", "cache.Get of fo.o"},
 		{"fo.o", "cache.Get of fo.o"},
 	}
 	}
 	for _, tt := range tests {
 	for _, tt := range tests {
-		_, err := m.GetCertificate(&tls.ClientHelloInfo{ServerName: tt.name})
+		_, err := m.GetCertificate(clientHelloInfo(tt.name, true))
 		got := fmt.Sprint(err)
 		got := fmt.Sprint(err)
 		if got != tt.wantErr {
 		if got != tt.wantErr {
 			t.Errorf("GetCertificate(SNI = %q) = %q; want %q", tt.name, got, tt.wantErr)
 			t.Errorf("GetCertificate(SNI = %q) = %q; want %q", tt.name, got, tt.wantErr)
@@ -891,3 +1110,62 @@ func TestCertRequest(t *testing.T) {
 		t.Errorf("want %v in Extensions: %v", ext, r.Extensions)
 		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)
+		}
+	}
+}

+ 3 - 3
acme/autocert/cache.go

@@ -16,10 +16,10 @@ import (
 var ErrCacheMiss = errors.New("acme/autocert: certificate cache miss")
 var ErrCacheMiss = errors.New("acme/autocert: certificate cache miss")
 
 
 // Cache is used by Manager to store and retrieve previously obtained certificates
 // 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 {
 type Cache interface {
 	// Get returns a certificate data for the specified key.
 	// Get returns a certificate data for the specified key.
 	// If there's no such key, Get returns ErrCacheMiss.
 	// If there's no such key, Get returns ErrCacheMiss.

+ 7 - 7
acme/autocert/renewal.go

@@ -17,9 +17,9 @@ const renewJitter = time.Hour
 // domainRenewal tracks the state used by the periodic timers
 // domainRenewal tracks the state used by the periodic timers
 // renewing a single domain's cert.
 // renewing a single domain's cert.
 type domainRenewal struct {
 type domainRenewal struct {
-	m      *Manager
-	domain string
-	key    crypto.Signer
+	m   *Manager
+	ck  certKey
+	key crypto.Signer
 
 
 	timerMu sync.Mutex
 	timerMu sync.Mutex
 	timer   *time.Timer
 	timer   *time.Timer
@@ -77,7 +77,7 @@ func (dr *domainRenewal) updateState(state *certState) {
 	dr.m.stateMu.Lock()
 	dr.m.stateMu.Lock()
 	defer dr.m.stateMu.Unlock()
 	defer dr.m.stateMu.Unlock()
 	dr.key = state.key
 	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.
 // do is similar to Manager.createCert but it doesn't lock a Manager.state item.
@@ -91,7 +91,7 @@ func (dr *domainRenewal) updateState(state *certState) {
 func (dr *domainRenewal) do(ctx context.Context) (time.Duration, error) {
 func (dr *domainRenewal) do(ctx context.Context) (time.Duration, error) {
 	// a race is likely unavoidable in a distributed environment
 	// a race is likely unavoidable in a distributed environment
 	// but we try nonetheless
 	// 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)
 		next := dr.next(tlscert.Leaf.NotAfter)
 		if next > dr.m.renewBefore()+renewJitter {
 		if next > dr.m.renewBefore()+renewJitter {
 			signer, ok := tlscert.PrivateKey.(crypto.Signer)
 			signer, ok := tlscert.PrivateKey.(crypto.Signer)
@@ -107,7 +107,7 @@ func (dr *domainRenewal) do(ctx context.Context) (time.Duration, error) {
 		}
 		}
 	}
 	}
 
 
-	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 {
 	if err != nil {
 		return 0, err
 		return 0, err
 	}
 	}
@@ -120,7 +120,7 @@ func (dr *domainRenewal) do(ctx context.Context) (time.Duration, error) {
 	if err != nil {
 	if err != nil {
 		return 0, err
 		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
 		return 0, err
 	}
 	}
 	dr.updateState(state)
 	dr.updateState(state)

+ 24 - 28
acme/autocert/renewal_test.go

@@ -48,8 +48,6 @@ func TestRenewalNext(t *testing.T) {
 }
 }
 
 
 func TestRenewFromCache(t *testing.T) {
 func TestRenewFromCache(t *testing.T) {
-	const domain = "example.org"
-
 	// ACME CA server stub
 	// ACME CA server stub
 	var ca *httptest.Server
 	var ca *httptest.Server
 	ca = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
 	ca = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
@@ -84,7 +82,7 @@ func TestRenewFromCache(t *testing.T) {
 			if err != nil {
 			if err != nil {
 				t.Fatalf("new-cert: CSR: %v", err)
 				t.Fatalf("new-cert: CSR: %v", err)
 			}
 			}
-			der, err := dummyCert(csr.PublicKey, domain)
+			der, err := dummyCert(csr.PublicKey, exampleDomain)
 			if err != nil {
 			if err != nil {
 				t.Fatalf("new-cert: dummyCert: %v", err)
 				t.Fatalf("new-cert: dummyCert: %v", err)
 			}
 			}
@@ -112,7 +110,7 @@ func TestRenewFromCache(t *testing.T) {
 	}
 	}
 	man := &Manager{
 	man := &Manager{
 		Prompt:      AcceptTOS,
 		Prompt:      AcceptTOS,
-		Cache:       newMemCache(),
+		Cache:       newMemCache(t),
 		RenewBefore: 24 * time.Hour,
 		RenewBefore: 24 * time.Hour,
 		Client: &acme.Client{
 		Client: &acme.Client{
 			Key:          key,
 			Key:          key,
@@ -123,12 +121,12 @@ func TestRenewFromCache(t *testing.T) {
 
 
 	// cache an almost expired cert
 	// cache an almost expired cert
 	now := time.Now()
 	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 {
 	if err != nil {
 		t.Fatal(err)
 		t.Fatal(err)
 	}
 	}
 	tlscert := &tls.Certificate{PrivateKey: key, Certificate: [][]byte{cert}}
 	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)
 		t.Fatal(err)
 	}
 	}
 
 
@@ -152,7 +150,7 @@ func TestRenewFromCache(t *testing.T) {
 
 
 		// ensure the new cert is cached
 		// ensure the new cert is cached
 		after := time.Now().Add(future)
 		after := time.Now().Add(future)
-		tlscert, err := man.cacheGet(context.Background(), domain)
+		tlscert, err := man.cacheGet(context.Background(), exampleCertKey)
 		if err != nil {
 		if err != nil {
 			t.Fatalf("man.cacheGet: %v", err)
 			t.Fatalf("man.cacheGet: %v", err)
 		}
 		}
@@ -163,9 +161,9 @@ func TestRenewFromCache(t *testing.T) {
 		// verify the old cert is also replaced in memory
 		// verify the old cert is also replaced in memory
 		man.stateMu.Lock()
 		man.stateMu.Lock()
 		defer man.stateMu.Unlock()
 		defer man.stateMu.Unlock()
-		s := man.state[domain]
+		s := man.state[exampleCertKey]
 		if s == nil {
 		if s == nil {
-			t.Fatalf("m.state[%q] is nil", domain)
+			t.Fatalf("m.state[%q] is nil", exampleCertKey)
 		}
 		}
 		tlscert, err = s.tlscert()
 		tlscert, err = s.tlscert()
 		if err != nil {
 		if err != nil {
@@ -177,7 +175,7 @@ func TestRenewFromCache(t *testing.T) {
 	}
 	}
 
 
 	// trigger renew
 	// trigger renew
-	hello := &tls.ClientHelloInfo{ServerName: domain}
+	hello := clientHelloInfo(exampleDomain, true)
 	if _, err := man.GetCertificate(hello); err != nil {
 	if _, err := man.GetCertificate(hello); err != nil {
 		t.Fatal(err)
 		t.Fatal(err)
 	}
 	}
@@ -191,8 +189,6 @@ func TestRenewFromCache(t *testing.T) {
 }
 }
 
 
 func TestRenewFromCacheAlreadyRenewed(t *testing.T) {
 func TestRenewFromCacheAlreadyRenewed(t *testing.T) {
-	const domain = "example.org"
-
 	// use EC key to run faster on 386
 	// use EC key to run faster on 386
 	key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
 	key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
 	if err != nil {
 	if err != nil {
@@ -200,7 +196,7 @@ func TestRenewFromCacheAlreadyRenewed(t *testing.T) {
 	}
 	}
 	man := &Manager{
 	man := &Manager{
 		Prompt:      AcceptTOS,
 		Prompt:      AcceptTOS,
-		Cache:       newMemCache(),
+		Cache:       newMemCache(t),
 		RenewBefore: 24 * time.Hour,
 		RenewBefore: 24 * time.Hour,
 		Client: &acme.Client{
 		Client: &acme.Client{
 			Key:          key,
 			Key:          key,
@@ -215,38 +211,38 @@ func TestRenewFromCacheAlreadyRenewed(t *testing.T) {
 		t.Fatal(err)
 		t.Fatal(err)
 	}
 	}
 	now := time.Now()
 	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 {
 	if err != nil {
 		t.Fatal(err)
 		t.Fatal(err)
 	}
 	}
-	newLeaf, err := validCert(domain, [][]byte{newCert}, newKey)
+	newLeaf, err := validCert(exampleCertKey, [][]byte{newCert}, newKey)
 	if err != nil {
 	if err != nil {
 		t.Fatal(err)
 		t.Fatal(err)
 	}
 	}
 	newTLSCert := &tls.Certificate{PrivateKey: newKey, Certificate: [][]byte{newCert}, Leaf: newLeaf}
 	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)
 		t.Fatal(err)
 	}
 	}
 
 
 	// set internal state to an almost expired cert
 	// 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 {
 	if err != nil {
 		t.Fatal(err)
 		t.Fatal(err)
 	}
 	}
-	oldLeaf, err := validCert(domain, [][]byte{oldCert}, key)
+	oldLeaf, err := validCert(exampleCertKey, [][]byte{oldCert}, key)
 	if err != nil {
 	if err != nil {
 		t.Fatal(err)
 		t.Fatal(err)
 	}
 	}
 	man.stateMu.Lock()
 	man.stateMu.Lock()
 	if man.state == nil {
 	if man.state == nil {
-		man.state = make(map[string]*certState)
+		man.state = make(map[certKey]*certState)
 	}
 	}
 	s := &certState{
 	s := &certState{
 		key:  key,
 		key:  key,
 		cert: [][]byte{oldCert},
 		cert: [][]byte{oldCert},
 		leaf: oldLeaf,
 		leaf: oldLeaf,
 	}
 	}
-	man.state[domain] = s
+	man.state[exampleCertKey] = s
 	man.stateMu.Unlock()
 	man.stateMu.Unlock()
 
 
 	// veriy the renewal accepted the newer cached cert
 	// veriy the renewal accepted the newer cached cert
@@ -267,7 +263,7 @@ func TestRenewFromCacheAlreadyRenewed(t *testing.T) {
 		}
 		}
 
 
 		// ensure the cached cert was not modified
 		// ensure the cached cert was not modified
-		tlscert, err := man.cacheGet(context.Background(), domain)
+		tlscert, err := man.cacheGet(context.Background(), exampleCertKey)
 		if err != nil {
 		if err != nil {
 			t.Fatalf("man.cacheGet: %v", err)
 			t.Fatalf("man.cacheGet: %v", err)
 		}
 		}
@@ -278,9 +274,9 @@ func TestRenewFromCacheAlreadyRenewed(t *testing.T) {
 		// verify the old cert is also replaced in memory
 		// verify the old cert is also replaced in memory
 		man.stateMu.Lock()
 		man.stateMu.Lock()
 		defer man.stateMu.Unlock()
 		defer man.stateMu.Unlock()
-		s := man.state[domain]
+		s := man.state[exampleCertKey]
 		if s == nil {
 		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)
 		stateKey := s.key.Public().(*ecdsa.PublicKey)
 		if stateKey.X.Cmp(newKey.X) != 0 || stateKey.Y.Cmp(newKey.Y) != 0 {
 		if stateKey.X.Cmp(newKey.X) != 0 || stateKey.Y.Cmp(newKey.Y) != 0 {
@@ -295,9 +291,9 @@ func TestRenewFromCacheAlreadyRenewed(t *testing.T) {
 		}
 		}
 
 
 		// verify the private key is replaced in the renewal state
 		// verify the private key is replaced in the renewal state
-		r := man.renewal[domain]
+		r := man.renewal[exampleCertKey]
 		if r == nil {
 		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)
 		renewalKey := r.key.Public().(*ecdsa.PublicKey)
 		if renewalKey.X.Cmp(newKey.X) != 0 || renewalKey.Y.Cmp(newKey.Y) != 0 {
 		if renewalKey.X.Cmp(newKey.X) != 0 || renewalKey.Y.Cmp(newKey.Y) != 0 {
@@ -307,7 +303,7 @@ func TestRenewFromCacheAlreadyRenewed(t *testing.T) {
 	}
 	}
 
 
 	// assert the expiring cert is returned from state
 	// assert the expiring cert is returned from state
-	hello := &tls.ClientHelloInfo{ServerName: domain}
+	hello := clientHelloInfo(exampleDomain, true)
 	tlscert, err := man.GetCertificate(hello)
 	tlscert, err := man.GetCertificate(hello)
 	if err != nil {
 	if err != nil {
 		t.Fatal(err)
 		t.Fatal(err)
@@ -317,7 +313,7 @@ func TestRenewFromCacheAlreadyRenewed(t *testing.T) {
 	}
 	}
 
 
 	// trigger renew
 	// trigger renew
-	go man.renew(domain, s.key, s.leaf.NotAfter)
+	go man.renew(exampleCertKey, s.key, s.leaf.NotAfter)
 
 
 	// wait for renew loop
 	// wait for renew loop
 	select {
 	select {
@@ -325,7 +321,7 @@ func TestRenewFromCacheAlreadyRenewed(t *testing.T) {
 		t.Fatal("renew took too long to occur")
 		t.Fatal("renew took too long to occur")
 	case <-done:
 	case <-done:
 		// assert the new cert is returned from state after renew
 		// assert the new cert is returned from state after renew
-		hello := &tls.ClientHelloInfo{ServerName: domain}
+		hello := clientHelloInfo(exampleDomain, true)
 		tlscert, err := man.GetCertificate(hello)
 		tlscert, err := man.GetCertificate(hello)
 		if err != nil {
 		if err != nil {
 			t.Fatal(err)
 			t.Fatal(err)