acme/autocert: replace all ACME server stubs with acmetest

Change-Id: Ie5520f33674471b4a018feb9d0efaf6696ea38a2
Reviewed-on: https://go-review.googlesource.com/c/crypto/+/381715
Run-TryBot: Filippo Valsorda <filippo@golang.org>
Reviewed-by: Roland Shoemaker <roland@golang.org>
Trust: Filippo Valsorda <filippo@golang.org>
TryBot-Result: Gopher Robot <gobot@golang.org>
diff --git a/acme/autocert/autocert_test.go b/acme/autocert/autocert_test.go
index e4663e9..4ae408f 100644
--- a/acme/autocert/autocert_test.go
+++ b/acme/autocert/autocert_test.go
@@ -16,11 +16,7 @@
 	"crypto/x509"
 	"crypto/x509/pkix"
 	"encoding/asn1"
-	"encoding/base64"
-	"encoding/json"
 	"fmt"
-	"html/template"
-	"io"
 	"io/ioutil"
 	"math/big"
 	"net/http"
@@ -41,33 +37,6 @@
 	exampleCertKeyRSA = certKey{domain: exampleDomain, isRSA: true}
 )
 
-var discoTmpl = template.Must(template.New("disco").Parse(`{
-	"new-reg": "{{.}}/new-reg",
-	"new-authz": "{{.}}/new-authz",
-	"new-cert": "{{.}}/new-cert"
-}`))
-
-var authzTmpl = template.Must(template.New("authz").Parse(`{
-	"status": "pending",
-	"challenges": [
-		{
-			"uri": "{{.}}/challenge/tls-alpn-01",
-			"type": "tls-alpn-01",
-			"token": "token-alpn"
-		},
-		{
-			"uri": "{{.}}/challenge/dns-01",
-			"type": "dns-01",
-			"token": "token-dns-01"
-		},
-		{
-			"uri": "{{.}}/challenge/http-01",
-			"type": "http-01",
-			"token": "token-http-01"
-		}
-	]
-}`))
-
 type memCache struct {
 	t       *testing.T
 	mu      sync.Mutex
@@ -175,18 +144,6 @@
 	return serial
 }
 
-func decodePayload(v interface{}, r io.Reader) error {
-	var req struct{ Payload string }
-	if err := json.NewDecoder(r).Decode(&req); err != nil {
-		return err
-	}
-	payload, err := base64.RawURLEncoding.DecodeString(req.Payload)
-	if err != nil {
-		return err
-	}
-	return json.Unmarshal(payload, v)
-}
-
 type algorithmSupport int
 
 const (
@@ -205,235 +162,311 @@
 	return hello
 }
 
-// tokenCertFn returns a function suitable for startACMEServerStub.
-// The returned function simulates a TLS hello request from a CA
-// during validation of a tls-alpn-01 challenge.
-func tokenCertFn(man *Manager, alg algorithmSupport) getCertificateFunc {
-	return func(sni string) (*tls.Certificate, error) {
-		hello := clientHelloInfo(sni, alg)
-		hello.SupportedProtos = []string{acme.ALPNProto}
-		return man.GetCertificate(hello)
+func testManager(t *testing.T) *Manager {
+	man := &Manager{
+		Prompt: AcceptTOS,
+		Cache:  newMemCache(t),
 	}
+	t.Cleanup(man.stopRenew)
+	return man
 }
 
 func TestGetCertificate(t *testing.T) {
-	man := &Manager{Prompt: AcceptTOS}
-	defer man.stopRenew()
-	hello := clientHelloInfo("example.org", algECDSA)
-	testGetCertificate(t, man, "example.org", hello)
-}
+	tests := []struct {
+		name        string
+		hello       *tls.ClientHelloInfo
+		domain      string
+		expectError string
+		prepare     func(t *testing.T, man *Manager, s *acmetest.CAServer)
+		verify      func(t *testing.T, man *Manager, leaf *x509.Certificate)
+		disableALPN bool
+		disableHTTP bool
+	}{
+		{
+			name:        "ALPN",
+			hello:       clientHelloInfo("example.org", algECDSA),
+			domain:      "example.org",
+			disableHTTP: true,
+		},
+		{
+			name:        "HTTP",
+			hello:       clientHelloInfo("example.org", algECDSA),
+			domain:      "example.org",
+			disableALPN: true,
+		},
+		{
+			name:   "nilPrompt",
+			hello:  clientHelloInfo("example.org", algECDSA),
+			domain: "example.org",
+			prepare: func(t *testing.T, man *Manager, s *acmetest.CAServer) {
+				man.Prompt = nil
+			},
+			expectError: "Manager.Prompt not set",
+		},
+		{
+			name:   "trailingDot",
+			hello:  clientHelloInfo("example.org.", algECDSA),
+			domain: "example.org",
+		},
+		{
+			name:   "unicodeIDN",
+			hello:  clientHelloInfo("éé.com", algECDSA),
+			domain: "xn--9caa.com",
+		},
+		{
+			name:   "unicodeIDN/mixedCase",
+			hello:  clientHelloInfo("éÉ.com", algECDSA),
+			domain: "xn--9caa.com",
+		},
+		{
+			name:   "upperCase",
+			hello:  clientHelloInfo("EXAMPLE.ORG", algECDSA),
+			domain: "example.org",
+		},
+		{
+			name:   "goodCache",
+			hello:  clientHelloInfo("example.org", algECDSA),
+			domain: "example.org",
+			prepare: func(t *testing.T, man *Manager, s *acmetest.CAServer) {
+				// Make a valid cert and cache it.
+				c := s.Start().LeafCert(exampleDomain, "ECDSA",
+					// Use a time before the Let's Encrypt revocation cutoff to also test
+					// that non-Let's Encrypt certificates are not renewed.
+					time.Date(2022, time.January, 1, 0, 0, 0, 0, time.UTC),
+					time.Date(2122, time.January, 1, 0, 0, 0, 0, time.UTC),
+				)
+				if err := man.cachePut(context.Background(), exampleCertKey, c); err != nil {
+					t.Fatalf("man.cachePut: %v", err)
+				}
+			},
+			// Break the server to check that the cache is used.
+			disableALPN: true, disableHTTP: true,
+		},
+		{
+			name:   "expiredCache",
+			hello:  clientHelloInfo("example.org", algECDSA),
+			domain: "example.org",
+			prepare: func(t *testing.T, man *Manager, s *acmetest.CAServer) {
+				// Make an expired cert and cache it.
+				c := s.Start().LeafCert(exampleDomain, "ECDSA", time.Now().Add(-10*time.Minute), time.Now().Add(-5*time.Minute))
+				if err := man.cachePut(context.Background(), exampleCertKey, c); err != nil {
+					t.Fatalf("man.cachePut: %v", err)
+				}
+			},
+		},
+		{
+			name:   "forceRSA",
+			hello:  clientHelloInfo("example.org", algECDSA),
+			domain: "example.org",
+			prepare: func(t *testing.T, man *Manager, s *acmetest.CAServer) {
+				man.ForceRSA = true
+			},
+			verify: func(t *testing.T, man *Manager, leaf *x509.Certificate) {
+				if _, ok := leaf.PublicKey.(*ecdsa.PublicKey); !ok {
+					t.Errorf("leaf.PublicKey is %T; want *ecdsa.PublicKey", leaf.PublicKey)
+				}
+			},
+		},
+		{
+			name:   "goodLetsEncrypt",
+			hello:  clientHelloInfo("example.org", algECDSA),
+			domain: "example.org",
+			prepare: func(t *testing.T, man *Manager, s *acmetest.CAServer) {
+				// Make a valid certificate issued after the TLS-ALPN-01
+				// revocation window and cache it.
+				s.IssuerName(pkix.Name{Country: []string{"US"},
+					Organization: []string{"Let's Encrypt"}, CommonName: "R3"})
+				c := s.Start().LeafCert(exampleDomain, "ECDSA",
+					time.Date(2022, time.January, 26, 12, 0, 0, 0, time.UTC),
+					time.Date(2122, time.January, 1, 0, 0, 0, 0, time.UTC),
+				)
+				if err := man.cachePut(context.Background(), exampleCertKey, c); err != nil {
+					t.Fatalf("man.cachePut: %v", err)
+				}
+			},
+			// Break the server to check that the cache is used.
+			disableALPN: true, disableHTTP: true,
+		},
+		{
+			name:   "revokedLetsEncrypt",
+			hello:  clientHelloInfo("example.org", algECDSA),
+			domain: "example.org",
+			prepare: func(t *testing.T, man *Manager, s *acmetest.CAServer) {
+				// Make a certificate issued during the TLS-ALPN-01
+				// revocation window and cache it.
+				s.IssuerName(pkix.Name{Country: []string{"US"},
+					Organization: []string{"Let's Encrypt"}, CommonName: "R3"})
+				c := s.Start().LeafCert(exampleDomain, "ECDSA",
+					time.Date(2022, time.January, 1, 0, 0, 0, 0, time.UTC),
+					time.Date(2122, time.January, 1, 0, 0, 0, 0, time.UTC),
+				)
+				if err := man.cachePut(context.Background(), exampleCertKey, c); err != nil {
+					t.Fatalf("man.cachePut: %v", err)
+				}
+			},
+			verify: func(t *testing.T, man *Manager, leaf *x509.Certificate) {
+				if leaf.NotBefore.Before(time.Now().Add(-10 * time.Minute)) {
+					t.Error("certificate was not reissued")
+				}
+			},
+		},
+		{
+			// TestGetCertificate/tokenCache tests the fallback of token
+			// certificate fetches to cache when Manager.certTokens misses.
+			name:   "tokenCacheALPN",
+			hello:  clientHelloInfo("example.org", algECDSA),
+			domain: "example.org",
+			prepare: func(t *testing.T, man *Manager, s *acmetest.CAServer) {
+				// Make a separate manager with a shared cache, simulating
+				// separate nodes that serve requests for the same domain.
+				man2 := testManager(t)
+				man2.Cache = man.Cache
+				// Redirect the verification request to man2, although the
+				// client request will hit man, testing that they can complete a
+				// verification by communicating through the cache.
+				s.ResolveGetCertificate("example.org", man2.GetCertificate)
+			},
+			// Drop the default verification paths.
+			disableALPN: true,
+		},
+		{
+			name:   "tokenCacheHTTP",
+			hello:  clientHelloInfo("example.org", algECDSA),
+			domain: "example.org",
+			prepare: func(t *testing.T, man *Manager, s *acmetest.CAServer) {
+				man2 := testManager(t)
+				man2.Cache = man.Cache
+				s.ResolveHandler("example.org", man2.HTTPHandler(nil))
+			},
+			disableHTTP: true,
+		},
+		{
+			name:   "ecdsa",
+			hello:  clientHelloInfo("example.org", algECDSA),
+			domain: "example.org",
+			verify: func(t *testing.T, man *Manager, leaf *x509.Certificate) {
+				if _, ok := leaf.PublicKey.(*ecdsa.PublicKey); !ok {
+					t.Error("an ECDSA client was served a non-ECDSA certificate")
+				}
+			},
+		},
+		{
+			name:   "rsa",
+			hello:  clientHelloInfo("example.org", algRSA),
+			domain: "example.org",
+			verify: func(t *testing.T, man *Manager, leaf *x509.Certificate) {
+				if _, ok := leaf.PublicKey.(*rsa.PublicKey); !ok {
+					t.Error("an RSA client was served a non-RSA certificate")
+				}
+			},
+		},
+		{
+			name:   "wrongCacheKeyType",
+			hello:  clientHelloInfo("example.org", algECDSA),
+			domain: "example.org",
+			prepare: func(t *testing.T, man *Manager, s *acmetest.CAServer) {
+				// Make an RSA cert and cache it without suffix.
+				c := s.Start().LeafCert(exampleDomain, "RSA", time.Now(), time.Now().Add(90*24*time.Hour))
+				if err := man.cachePut(context.Background(), exampleCertKey, c); err != nil {
+					t.Fatalf("man.cachePut: %v", err)
+				}
+			},
+			verify: func(t *testing.T, man *Manager, leaf *x509.Certificate) {
+				// The RSA cached cert should be silently ignored and replaced.
+				if _, ok := leaf.PublicKey.(*ecdsa.PublicKey); !ok {
+					t.Error("an ECDSA client was served a non-ECDSA certificate")
+				}
+				if numCerts := man.Cache.(*memCache).numCerts(); numCerts != 1 {
+					t.Errorf("found %d certificates in cache; want %d", numCerts, 1)
+				}
+			},
+		},
+		{
+			name:   "expiredCache",
+			hello:  clientHelloInfo("example.org", algECDSA),
+			domain: "example.org",
+			prepare: func(t *testing.T, man *Manager, s *acmetest.CAServer) {
+				man.RenewBefore = 24 * time.Hour
+				// Cache an almost expired cert.
+				c := s.Start().LeafCert(exampleDomain, "ECDSA", time.Now(), time.Now().Add(10*time.Minute))
+				if err := man.cachePut(context.Background(), exampleCertKey, c); err != nil {
+					t.Fatalf("man.cachePut: %v", err)
+				}
+			},
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			man := testManager(t)
+			s := acmetest.NewCAServer(t)
+			if !tt.disableALPN {
+				s.ResolveGetCertificate(tt.domain, man.GetCertificate)
+			}
+			if !tt.disableHTTP {
+				s.ResolveHandler(tt.domain, man.HTTPHandler(nil))
+			}
 
-func TestGetCertificate_trailingDot(t *testing.T) {
-	man := &Manager{Prompt: AcceptTOS}
-	defer man.stopRenew()
-	hello := clientHelloInfo("example.org.", algECDSA)
-	testGetCertificate(t, man, "example.org", hello)
-}
+			if tt.prepare != nil {
+				tt.prepare(t, man, s)
+			}
 
-func TestGetCertificate_unicodeIDN(t *testing.T) {
-	man := &Manager{Prompt: AcceptTOS}
-	defer man.stopRenew()
+			s.Start()
 
-	hello := clientHelloInfo("éé.com", algECDSA)
-	testGetCertificate(t, man, "xn--9caa.com", hello)
+			man.Client = &acme.Client{DirectoryURL: s.URL()}
 
-	hello = clientHelloInfo("éÉ.com", algECDSA)
-	testGetCertificate(t, man, "xn--9caa.com", hello)
-}
+			var tlscert *tls.Certificate
+			var err error
+			done := make(chan struct{})
+			go func() {
+				tlscert, err = man.GetCertificate(tt.hello)
+				close(done)
+			}()
+			select {
+			case <-time.After(time.Minute):
+				t.Fatal("man.GetCertificate took too long to return")
+			case <-done:
+			}
+			if tt.expectError != "" {
+				if err == nil {
+					t.Fatal("expected error, got certificate")
+				}
+				if !strings.Contains(err.Error(), tt.expectError) {
+					t.Errorf("got %q, expected %q", err, tt.expectError)
+				}
+				return
+			}
+			if err != nil {
+				t.Fatalf("man.GetCertificate: %v", err)
+			}
 
-func TestGetCertificate_mixedcase(t *testing.T) {
-	man := &Manager{Prompt: AcceptTOS}
-	defer man.stopRenew()
+			leaf, err := x509.ParseCertificate(tlscert.Certificate[0])
+			if err != nil {
+				t.Fatal(err)
+			}
+			opts := x509.VerifyOptions{
+				DNSName:       tt.domain,
+				Intermediates: x509.NewCertPool(),
+				Roots:         s.Roots(),
+			}
+			for _, cert := range tlscert.Certificate[1:] {
+				c, err := x509.ParseCertificate(cert)
+				if err != nil {
+					t.Fatal(err)
+				}
+				opts.Intermediates.AddCert(c)
+			}
+			if _, err := leaf.Verify(opts); err != nil {
+				t.Error(err)
+			}
 
-	hello := clientHelloInfo("example.org", algECDSA)
-	testGetCertificate(t, man, "example.org", hello)
+			if san := leaf.DNSNames[0]; san != tt.domain {
+				t.Errorf("got SAN %q, expected %q", san, tt.domain)
+			}
 
-	hello = clientHelloInfo("EXAMPLE.ORG", algECDSA)
-	testGetCertificate(t, man, "example.org", hello)
-}
-
-func TestGetCertificate_ForceRSA(t *testing.T) {
-	man := &Manager{
-		Prompt:   AcceptTOS,
-		Cache:    newMemCache(t),
-		ForceRSA: true,
-	}
-	defer man.stopRenew()
-	hello := clientHelloInfo(exampleDomain, algECDSA)
-	testGetCertificate(t, man, exampleDomain, hello)
-
-	// 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.(*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, tokenCertFn(man, algECDSA), "example.org")
-	defer finish()
-	man.Client = &acme.Client{DirectoryURL: url}
-	hello := clientHelloInfo("example.org", algECDSA)
-	if _, err := man.GetCertificate(hello); err == nil {
-		t.Error("got certificate for example.org; wanted error")
-	}
-}
-
-func TestGetCertificate_goodCache(t *testing.T) {
-	// Make a valid cert and cache it.
-	pk, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
-	if err != nil {
-		t.Fatal(err)
-	}
-	serial := randomSerial()
-	tmpl := &x509.Certificate{
-		SerialNumber: serial,
-		DNSNames:     []string{exampleDomain},
-		// Use a time before the Let's Encrypt revocation cutoff to also test
-		// that non-Let's Encrypt certificates are not renewed.
-		NotBefore: time.Date(2022, time.January, 1, 0, 0, 0, 0, time.UTC),
-		NotAfter:  time.Date(2122, time.January, 1, 0, 0, 0, 0, time.UTC),
-	}
-	pub, err := x509.CreateCertificate(rand.Reader, tmpl, tmpl, &pk.PublicKey, pk)
-	if err != nil {
-		t.Fatal(err)
-	}
-	tlscert := &tls.Certificate{
-		Certificate: [][]byte{pub},
-		PrivateKey:  pk,
-	}
-
-	man := &Manager{Prompt: AcceptTOS, Cache: newMemCache(t)}
-	defer man.stopRenew()
-	if err := man.cachePut(context.Background(), exampleCertKey, tlscert); err != nil {
-		t.Fatalf("man.cachePut: %v", err)
-	}
-
-	hello := clientHelloInfo(exampleDomain, algECDSA)
-	gotCert := testGetCertificate(t, man, exampleDomain, hello)
-	if gotCert.SerialNumber.Cmp(serial) != 0 {
-		t.Error("good certificate was replaced")
-	}
-}
-
-func TestGetCertificate_expiredCache(t *testing.T) {
-	// Make an expired cert and cache it.
-	pk, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
-	if err != nil {
-		t.Fatal(err)
-	}
-	serial := randomSerial()
-	tmpl := &x509.Certificate{
-		SerialNumber: serial,
-		DNSNames:     []string{exampleDomain},
-		NotBefore:    time.Now().Add(-1 * time.Minute),
-		NotAfter:     time.Now(),
-	}
-	pub, err := x509.CreateCertificate(rand.Reader, tmpl, tmpl, &pk.PublicKey, pk)
-	if err != nil {
-		t.Fatal(err)
-	}
-	tlscert := &tls.Certificate{
-		Certificate: [][]byte{pub},
-		PrivateKey:  pk,
-	}
-
-	man := &Manager{Prompt: AcceptTOS, Cache: newMemCache(t)}
-	defer man.stopRenew()
-	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 := clientHelloInfo(exampleDomain, algECDSA)
-	gotCert := testGetCertificate(t, man, exampleDomain, hello)
-	if gotCert.SerialNumber.Cmp(serial) == 0 {
-		t.Error("expired certificate was not replaced")
-	}
-}
-
-func TestGetCertificate_goodLetsEncrypt(t *testing.T) {
-	pk, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
-	if err != nil {
-		t.Fatal(err)
-	}
-	issuer := &x509.Certificate{
-		Subject: pkix.Name{Country: []string{"US"},
-			Organization: []string{"Let's Encrypt"}, CommonName: "R3"},
-	}
-	serial := randomSerial()
-	tmpl := &x509.Certificate{
-		SerialNumber: serial,
-		DNSNames:     []string{exampleDomain},
-		NotBefore:    time.Date(2022, time.January, 26, 12, 0, 0, 0, time.UTC),
-		NotAfter:     time.Date(2122, time.January, 1, 0, 0, 0, 0, time.UTC),
-	}
-	pub, err := x509.CreateCertificate(rand.Reader, tmpl, issuer, &pk.PublicKey, pk)
-	if err != nil {
-		t.Fatal(err)
-	}
-	tlscert := &tls.Certificate{
-		Certificate: [][]byte{pub},
-		PrivateKey:  pk,
-	}
-
-	man := &Manager{Prompt: AcceptTOS, Cache: newMemCache(t)}
-	defer man.stopRenew()
-	if err := man.cachePut(context.Background(), exampleCertKey, tlscert); err != nil {
-		t.Fatalf("man.cachePut: %v", err)
-	}
-
-	hello := clientHelloInfo(exampleDomain, algECDSA)
-	gotCert := testGetCertificate(t, man, exampleDomain, hello)
-	if gotCert.SerialNumber.Cmp(serial) != 0 {
-		t.Error("good certificate was replaced")
-	}
-}
-
-func TestGetCertificate_revokedLetsEncrypt(t *testing.T) {
-	// Make a presumably revoked Let's Encrypt cert and cache it.
-	pk, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
-	if err != nil {
-		t.Fatal(err)
-	}
-	issuer := &x509.Certificate{
-		Subject: pkix.Name{Country: []string{"US"},
-			Organization: []string{"Let's Encrypt"}, CommonName: "R3"},
-	}
-	serial := randomSerial()
-	tmpl := &x509.Certificate{
-		SerialNumber: serial,
-		DNSNames:     []string{exampleDomain},
-		NotBefore:    time.Date(2022, time.January, 1, 0, 0, 0, 0, time.UTC),
-		NotAfter:     time.Date(2122, time.January, 1, 0, 0, 0, 0, time.UTC),
-	}
-	pub, err := x509.CreateCertificate(rand.Reader, tmpl, issuer, &pk.PublicKey, pk)
-	if err != nil {
-		t.Fatal(err)
-	}
-	tlscert := &tls.Certificate{
-		Certificate: [][]byte{pub},
-		PrivateKey:  pk,
-	}
-
-	man := &Manager{Prompt: AcceptTOS, Cache: newMemCache(t)}
-	defer man.stopRenew()
-	if err := man.cachePut(context.Background(), exampleCertKey, tlscert); err != nil {
-		t.Fatalf("man.cachePut: %v", err)
-	}
-
-	// The presumably revoked cached cert should trigger a new cert issuance
-	// and return without an error.
-	hello := clientHelloInfo(exampleDomain, algECDSA)
-	gotCert := testGetCertificate(t, man, exampleDomain, hello)
-	if gotCert.SerialNumber.Cmp(serial) == 0 {
-		t.Error("certificate was not replaced")
+			if tt.verify != nil {
+				tt.verify(t, man, leaf)
+			}
+		})
 	}
 }
 
@@ -481,489 +514,34 @@
 	}
 }
 
-// testGetCertificate_tokenCache tests the fallback of token certificate fetches
-// to cache when Manager.certTokens misses.
-// algorithmSupport refers to the CA when verifying the certificate token.
-func testGetCertificate_tokenCache(t *testing.T, tokenAlg algorithmSupport) {
-	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, tokenCertFn(man2, tokenAlg), "example.org")
-	defer finish()
-	man1.Client = &acme.Client{DirectoryURL: url}
-	man2.Client = &acme.Client{DirectoryURL: url}
-	hello := clientHelloInfo("example.org", algECDSA)
-	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, algECDSA)
-	})
-	t.Run("ecdsaSupport=false", func(t *testing.T) {
-		testGetCertificate_tokenCache(t, algRSA)
-	})
-}
-
-func TestGetCertificate_ecdsaVsRSA(t *testing.T) {
-	cache := newMemCache(t)
-	man := &Manager{Prompt: AcceptTOS, Cache: cache}
-	defer man.stopRenew()
-	url, finish := startACMEServerStub(t, tokenCertFn(man, algECDSA), "example.org")
-	defer finish()
-	man.Client = &acme.Client{DirectoryURL: url}
-
-	cert, err := man.GetCertificate(clientHelloInfo("example.org", algECDSA))
-	if err != nil {
-		t.Fatal(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", algRSA))
-	if err != nil {
-		t.Fatal(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", algECDSA)); err != nil {
-		t.Error(err)
-	}
-	if _, err := man.GetCertificate(clientHelloInfo("example.org", algRSA)); 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, tokenCertFn(man, algECDSA), exampleDomain)
-	defer finish()
-	man.Client = &acme.Client{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),
-		DNSNames:     []string{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, algECDSA))
-	if err != nil {
-		t.Fatal(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)
-	}
-}
-
-type getCertificateFunc func(domain string) (*tls.Certificate, error)
-
-// startACMEServerStub runs an ACME server
-// The domain argument is the expected domain name of a certificate request.
-// TODO: Drop this in favour of x/crypto/acme/autocert/internal/acmetest.
-func startACMEServerStub(t *testing.T, tokenCert getCertificateFunc, domain string) (url string, finish func()) {
-	verifyTokenCert := func() {
-		tlscert, err := tokenCert(domain)
-		if err != nil {
-			t.Errorf("verifyTokenCert: tokenCert(%q): %v", domain, err)
-			return
-		}
-		crt, err := x509.ParseCertificate(tlscert.Certificate[0])
-		if err != nil {
-			t.Errorf("verifyTokenCert: x509.ParseCertificate: %v", err)
-		}
-		if err := crt.VerifyHostname(domain); err != nil {
-			t.Errorf("verifyTokenCert: %v", err)
-		}
-		// See https://tools.ietf.org/html/draft-ietf-acme-tls-alpn-05#section-5.1
-		oid := asn1.ObjectIdentifier{1, 3, 6, 1, 5, 5, 7, 1, 31}
-		for _, x := range crt.Extensions {
-			if x.Id.Equal(oid) {
-				// No need to check the extension value here.
-				// This is done in acme package tests.
-				return
-			}
-		}
-		t.Error("verifyTokenCert: no id-pe-acmeIdentifier extension found")
-	}
-
-	// ACME CA server stub
-	var ca *httptest.Server
-	ca = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
-		w.Header().Set("Replay-Nonce", "nonce")
-		if r.Method == "HEAD" {
-			// a nonce request
-			return
-		}
-
-		switch r.URL.Path {
-		// discovery
-		case "/":
-			if err := discoTmpl.Execute(w, ca.URL); err != nil {
-				t.Errorf("discoTmpl: %v", err)
-			}
-		// client key registration
-		case "/new-reg":
-			w.Write([]byte("{}"))
-		// domain authorization
-		case "/new-authz":
-			w.Header().Set("Location", ca.URL+"/authz/1")
-			w.WriteHeader(http.StatusCreated)
-			if err := authzTmpl.Execute(w, ca.URL); err != nil {
-				t.Errorf("authzTmpl: %v", err)
-			}
-		// accept tls-alpn-01 challenge
-		case "/challenge/tls-alpn-01":
-			verifyTokenCert()
-			w.Write([]byte("{}"))
-		// authorization status
-		case "/authz/1":
-			w.Write([]byte(`{"status": "valid"}`))
-		// cert request
-		case "/new-cert":
-			var req struct {
-				CSR string `json:"csr"`
-			}
-			decodePayload(&req, r.Body)
-			b, _ := base64.RawURLEncoding.DecodeString(req.CSR)
-			csr, err := x509.ParseCertificateRequest(b)
-			if err != nil {
-				t.Errorf("new-cert: CSR: %v", err)
-			}
-			if csr.Subject.CommonName != domain {
-				t.Errorf("CommonName in CSR = %q; want %q", csr.Subject.CommonName, domain)
-			}
-			der, err := dummyCert(csr.PublicKey, domain)
-			if err != nil {
-				t.Errorf("new-cert: dummyCert: %v", err)
-			}
-			chainUp := fmt.Sprintf("<%s/ca-cert>; rel=up", ca.URL)
-			w.Header().Set("Link", chainUp)
-			w.WriteHeader(http.StatusCreated)
-			w.Write(der)
-		// CA chain cert
-		case "/ca-cert":
-			der, err := dummyCert(nil, "ca")
-			if err != nil {
-				t.Errorf("ca-cert: dummyCert: %v", err)
-			}
-			w.Write(der)
-		default:
-			t.Errorf("unrecognized r.URL.Path: %s", r.URL.Path)
-		}
-	}))
-	finish = func() {
-		ca.Close()
-
-		// make sure token cert was removed
-		cancel := make(chan struct{})
-		done := make(chan struct{})
-		go func() {
-			defer close(done)
-			tick := time.NewTicker(100 * time.Millisecond)
-			defer tick.Stop()
-			for {
-				if _, err := tokenCert(domain); err != nil {
-					return
-				}
-				select {
-				case <-tick.C:
-				case <-cancel:
-					return
-				}
-			}
-		}()
-		select {
-		case <-done:
-		case <-time.After(5 * time.Second):
-			close(cancel)
-			t.Error("token cert was not removed")
-			<-done
-		}
-	}
-	return ca.URL, finish
-}
-
-// 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) *x509.Certificate {
-	url, finish := startACMEServerStub(t, tokenCertFn(man, algECDSA), domain)
-	defer finish()
-	man.Client = &acme.Client{DirectoryURL: url}
-
-	// simulate tls.Config.GetCertificate
-	var tlscert *tls.Certificate
-	var err error
-	done := make(chan struct{})
-	go func() {
-		tlscert, err = man.GetCertificate(hello)
-		close(done)
-	}()
-	select {
-	case <-time.After(time.Minute):
-		t.Fatal("man.GetCertificate took too long to return")
-	case <-done:
-	}
-	if err != nil {
-		t.Fatalf("man.GetCertificate: %v", err)
-	}
-
-	// verify the tlscert is the same we responded with from the CA stub
-	if len(tlscert.Certificate) == 0 {
-		t.Fatal("len(tlscert.Certificate) is 0")
-	}
-	cert, err := x509.ParseCertificate(tlscert.Certificate[0])
-	if err != nil {
-		t.Fatalf("x509.ParseCertificate: %v", err)
-	}
-	if len(cert.DNSNames) == 0 || cert.DNSNames[0] != domain {
-		t.Errorf("cert.DNSNames = %v; want %q", cert.DNSNames, domain)
-	}
-
-	return cert
-}
-
-func TestVerifyHTTP01(t *testing.T) {
-	var (
-		http01 http.Handler
-
-		authzCount      int // num. of created authorizations
-		didAcceptHTTP01 bool
-	)
-
-	verifyHTTPToken := func() {
-		r := httptest.NewRequest("GET", "/.well-known/acme-challenge/token-http-01", nil)
-		w := httptest.NewRecorder()
-		http01.ServeHTTP(w, r)
-		if w.Code != http.StatusOK {
-			t.Errorf("http token: w.Code = %d; want %d", w.Code, http.StatusOK)
-		}
-		if v := w.Body.String(); !strings.HasPrefix(v, "token-http-01.") {
-			t.Errorf("http token value = %q; want 'token-http-01.' prefix", v)
-		}
-	}
-
-	// ACME CA server stub, only the needed bits.
-	// TODO: Replace this with x/crypto/acme/autocert/internal/acmetest.
-	var ca *httptest.Server
-	ca = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
-		w.Header().Set("Replay-Nonce", "nonce")
-		if r.Method == "HEAD" {
-			// a nonce request
-			return
-		}
-
-		switch r.URL.Path {
-		// Discovery.
-		case "/":
-			if err := discoTmpl.Execute(w, ca.URL); err != nil {
-				t.Errorf("discoTmpl: %v", err)
-			}
-		// Client key registration.
-		case "/new-reg":
-			w.Write([]byte("{}"))
-		// New domain authorization.
-		case "/new-authz":
-			authzCount++
-			w.Header().Set("Location", fmt.Sprintf("%s/authz/%d", ca.URL, authzCount))
-			w.WriteHeader(http.StatusCreated)
-			if err := authzTmpl.Execute(w, ca.URL); err != nil {
-				t.Errorf("authzTmpl: %v", err)
-			}
-		// Reject tls-alpn-01.
-		case "/challenge/tls-alpn-01":
-			http.Error(w, "won't accept tls-sni-01", http.StatusBadRequest)
-		// Should not accept dns-01.
-		case "/challenge/dns-01":
-			t.Errorf("dns-01 challenge was accepted")
-			http.Error(w, "won't accept dns-01", http.StatusBadRequest)
-		// Accept http-01.
-		case "/challenge/http-01":
-			didAcceptHTTP01 = true
-			verifyHTTPToken()
-			w.Write([]byte("{}"))
-		// Authorization statuses.
-		case "/authz/1": // tls-alpn-01
-			w.Write([]byte(`{"status": "invalid"}`))
-		case "/authz/2": // http-01
-			w.Write([]byte(`{"status": "valid"}`))
-		default:
-			http.NotFound(w, r)
-			t.Errorf("unrecognized r.URL.Path: %s", r.URL.Path)
-		}
-	}))
-	defer ca.Close()
-
-	m := &Manager{
-		Client: &acme.Client{
-			DirectoryURL: ca.URL,
-		},
-	}
-	http01 = m.HTTPHandler(nil)
-	ctx := context.Background()
-	client, err := m.acmeClient(ctx)
-	if err != nil {
-		t.Fatalf("m.acmeClient: %v", err)
-	}
-	if err := m.verify(ctx, client, "example.org"); err != nil {
-		t.Errorf("m.verify: %v", err)
-	}
-	// Only tls-alpn-01 and http-01 must be accepted.
-	// The dns-01 challenge is unsupported.
-	if authzCount != 2 {
-		t.Errorf("authzCount = %d; want 2", authzCount)
-	}
-	if !didAcceptHTTP01 {
-		t.Error("did not accept http-01 challenge")
-	}
-}
-
 func TestRevokeFailedAuthz(t *testing.T) {
-	// Prefill authorization URIs expected to be revoked.
-	// The challenges are selected in a specific order,
-	// each tried within a newly created authorization.
-	// This means each authorization URI corresponds to a different challenge type.
-	revokedAuthz := map[string]bool{
-		"/authz/0": false, // tls-alpn-01
-		"/authz/1": false, // http-01
-		"/authz/2": false, // no viable challenge, but authz is created
+	ca := acmetest.NewCAServer(t)
+	// Make the authz unfulfillable on the client side, so it will be left
+	// pending at the end of the verification attempt.
+	ca.ChallengeTypes("fake-01", "fake-02")
+	ca.Start()
+
+	m := testManager(t)
+	m.Client = &acme.Client{DirectoryURL: ca.URL()}
+
+	_, err := m.GetCertificate(clientHelloInfo("example.org", algECDSA))
+	if err == nil {
+		t.Fatal("expected GetCertificate to fail")
 	}
 
-	var authzCount int          // num. of created authorizations
-	var revokeCount int         // num. of revoked authorizations
-	done := make(chan struct{}) // closed when revokeCount is 3
-
-	// ACME CA server stub, only the needed bits.
-	// TODO: Replace this with x/crypto/acme/autocert/internal/acmetest.
-	var ca *httptest.Server
-	ca = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
-		w.Header().Set("Replay-Nonce", "nonce")
-		if r.Method == "HEAD" {
-			// a nonce request
+	start := time.Now()
+	for time.Since(start) < 3*time.Second {
+		authz, err := m.Client.GetAuthorization(context.Background(), ca.URL()+"/authz/0")
+		if err != nil {
+			t.Fatal(err)
+		}
+		if authz.Status == acme.StatusDeactivated {
 			return
 		}
+		time.Sleep(50 * time.Millisecond)
+	}
+	t.Error("revocations took too long")
 
-		switch r.URL.Path {
-		// Discovery.
-		case "/":
-			if err := discoTmpl.Execute(w, ca.URL); err != nil {
-				t.Errorf("discoTmpl: %v", err)
-			}
-		// Client key registration.
-		case "/new-reg":
-			w.Write([]byte("{}"))
-		// New domain authorization.
-		case "/new-authz":
-			w.Header().Set("Location", fmt.Sprintf("%s/authz/%d", ca.URL, authzCount))
-			w.WriteHeader(http.StatusCreated)
-			if err := authzTmpl.Execute(w, ca.URL); err != nil {
-				t.Errorf("authzTmpl: %v", err)
-			}
-			authzCount++
-		// tls-alpn-01 challenge "accept" request.
-		case "/challenge/tls-alpn-01":
-			// Refuse.
-			http.Error(w, "won't accept tls-alpn-01 challenge", http.StatusBadRequest)
-		// http-01 challenge "accept" request.
-		case "/challenge/http-01":
-			// Refuse.
-			w.WriteHeader(http.StatusBadRequest)
-			w.Write([]byte(`{"status":"invalid"}`))
-		// Authorization requests.
-		case "/authz/0", "/authz/1", "/authz/2":
-			// Revocation requests.
-			if r.Method == "POST" {
-				var req struct{ Status string }
-				if err := decodePayload(&req, r.Body); err != nil {
-					t.Errorf("%s: decodePayload: %v", r.URL, err)
-				}
-				switch req.Status {
-				case "deactivated":
-					revokedAuthz[r.URL.Path] = true
-					revokeCount++
-					if revokeCount >= 3 {
-						// Last authorization is revoked.
-						defer close(done)
-					}
-				default:
-					t.Errorf("%s: req.Status = %q; want 'deactivated'", r.URL, req.Status)
-				}
-				w.Write([]byte(`{"status": "invalid"}`))
-				return
-			}
-			// Authorization status requests.
-			w.Write([]byte(`{"status":"pending"}`))
-		default:
-			http.NotFound(w, r)
-			t.Errorf("unrecognized r.URL.Path: %s", r.URL.Path)
-		}
-	}))
-	defer ca.Close()
-
-	m := &Manager{
-		Client: &acme.Client{DirectoryURL: ca.URL},
-	}
-	m.HTTPHandler(nil) // enable http-01 challenge type
-	// Should fail and revoke 3 authorizations.
-	// The first 2 are tls-alpn-01 and http-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(), exampleCertKey); err == nil {
-		t.Errorf("m.createCert returned nil error")
-	}
-	select {
-	case <-time.After(3 * time.Second):
-		t.Error("revocations took too long")
-	case <-done:
-		// revokeCount is at least 3.
-	}
-	for uri, ok := range revokedAuthz {
-		if !ok {
-			t.Errorf("%q authorization was not revoked", uri)
-		}
-	}
 }
 
 func TestHTTPHandlerDefaultFallback(t *testing.T) {
@@ -1306,18 +884,16 @@
 	}
 }
 
-// TODO: add same end-to-end for http-01 challenge type.
-func TestEndToEnd(t *testing.T) {
+func TestEndToEndALPN(t *testing.T) {
 	const domain = "example.org"
 
 	// ACME CA server
-	ca := acmetest.NewCAServer([]string{"tls-alpn-01"}, []string{domain})
-	defer ca.Close()
+	ca := acmetest.NewCAServer(t).Start()
 
-	// User dummy server.
+	// User HTTPS server.
 	m := &Manager{
 		Prompt: AcceptTOS,
-		Client: &acme.Client{DirectoryURL: ca.URL},
+		Client: &acme.Client{DirectoryURL: ca.URL()},
 	}
 	us := httptest.NewUnstartedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
 		w.Write([]byte("OK"))
@@ -1339,10 +915,10 @@
 	// where to dial to instead.
 	ca.Resolve(domain, strings.TrimPrefix(us.URL, "https://"))
 
-	// A client visiting user dummy server.
+	// A client visiting user's HTTPS server.
 	tr := &http.Transport{
 		TLSClientConfig: &tls.Config{
-			RootCAs:    ca.Roots,
+			RootCAs:    ca.Roots(),
 			ServerName: domain,
 		},
 	}
@@ -1360,3 +936,57 @@
 		t.Errorf("user server response: %q; want 'OK'", v)
 	}
 }
+
+func TestEndToEndHTTP(t *testing.T) {
+	const domain = "example.org"
+
+	// ACME CA server.
+	ca := acmetest.NewCAServer(t).ChallengeTypes("http-01").Start()
+
+	// User HTTP server for the ACME challenge.
+	m := testManager(t)
+	m.Client = &acme.Client{DirectoryURL: ca.URL()}
+	s := httptest.NewServer(m.HTTPHandler(nil))
+	defer s.Close()
+
+	// User HTTPS server.
+	ss := httptest.NewUnstartedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+		w.Write([]byte("OK"))
+	}))
+	ss.TLS = &tls.Config{
+		NextProtos: []string{"http/1.1", acme.ALPNProto},
+		GetCertificate: func(hello *tls.ClientHelloInfo) (*tls.Certificate, error) {
+			cert, err := m.GetCertificate(hello)
+			if err != nil {
+				t.Errorf("m.GetCertificate: %v", err)
+			}
+			return cert, err
+		},
+	}
+	ss.StartTLS()
+	defer ss.Close()
+
+	// Redirect the CA requests to the HTTP server.
+	ca.Resolve(domain, strings.TrimPrefix(s.URL, "http://"))
+
+	// A client visiting user's HTTPS server.
+	tr := &http.Transport{
+		TLSClientConfig: &tls.Config{
+			RootCAs:    ca.Roots(),
+			ServerName: domain,
+		},
+	}
+	client := &http.Client{Transport: tr}
+	res, err := client.Get(ss.URL)
+	if err != nil {
+		t.Fatal(err)
+	}
+	defer res.Body.Close()
+	b, err := ioutil.ReadAll(res.Body)
+	if err != nil {
+		t.Fatal(err)
+	}
+	if v := string(b); v != "OK" {
+		t.Errorf("user server response: %q; want 'OK'", v)
+	}
+}
diff --git a/acme/autocert/internal/acmetest/ca.go b/acme/autocert/internal/acmetest/ca.go
index faffd20..bc0984f 100644
--- a/acme/autocert/internal/acmetest/ca.go
+++ b/acme/autocert/internal/acmetest/ca.go
@@ -8,27 +8,30 @@
 package acmetest
 
 import (
+	"context"
 	"crypto"
 	"crypto/ecdsa"
 	"crypto/elliptic"
 	"crypto/rand"
+	"crypto/rsa"
 	"crypto/tls"
 	"crypto/x509"
 	"crypto/x509/pkix"
+	"encoding/asn1"
 	"encoding/base64"
 	"encoding/json"
 	"encoding/pem"
 	"fmt"
 	"io"
-	"log"
 	"math/big"
+	"net"
 	"net/http"
 	"net/http/httptest"
 	"path"
-	"sort"
 	"strconv"
 	"strings"
 	"sync"
+	"testing"
 	"time"
 
 	"golang.org/x/crypto/acme"
@@ -36,56 +39,64 @@
 
 // CAServer is a simple test server which implements ACME spec bits needed for testing.
 type CAServer struct {
-	URL   string         // server URL after it has been started
-	Roots *x509.CertPool // CA root certificates; initialized in NewCAServer
-
 	rootKey      crypto.Signer
 	rootCert     []byte // DER encoding
 	rootTemplate *x509.Certificate
 
-	server           *httptest.Server
-	challengeTypes   []string // supported challenge types
-	domainsWhitelist []string // only these domains are valid for issuing, unless empty
+	t              *testing.T
+	server         *httptest.Server
+	issuer         pkix.Name
+	challengeTypes []string
+	url            string
+	roots          *x509.CertPool
 
 	mu             sync.Mutex
-	certCount      int                       // number of issued certs
-	domainAddr     map[string]string         // domain name to addr:port resolution
-	authorizations map[string]*authorization // keyed by domain name
-	orders         []*order                  // index is used as order ID
-	errors         []error                   // encountered client errors
+	certCount      int                           // number of issued certs
+	acctRegistered bool                          // set once an account has been registered
+	domainAddr     map[string]string             // domain name to addr:port resolution
+	domainGetCert  map[string]getCertificateFunc // domain name to GetCertificate function
+	domainHandler  map[string]http.Handler       // domain name to Handle function
+	validAuthz     map[string]*authorization     // valid authz, keyed by domain name
+	authorizations []*authorization              // all authz, index is used as ID
+	orders         []*order                      // index is used as order ID
+	errors         []error                       // encountered client errors
 }
 
-// NewCAServer creates a new ACME test server and starts serving requests.
-// The returned CAServer issues certs signed with the CA roots
-// available in the Roots field.
-//
-// The challengeTypes argument defines the supported ACME challenge types
-// sent to a client in a response for a domain authorization.
-// If domainsWhitelist is non-empty, the certs will be issued only for the specified
-// list of domains. Otherwise, any domain name is allowed.
-func NewCAServer(challengeTypes []string, domainsWhitelist []string) *CAServer {
-	var whitelist []string
-	for _, name := range domainsWhitelist {
-		whitelist = append(whitelist, name)
-	}
-	sort.Strings(whitelist)
-	ca := &CAServer{
-		challengeTypes:   challengeTypes,
-		domainsWhitelist: whitelist,
-		domainAddr:       make(map[string]string),
-		authorizations:   make(map[string]*authorization),
+type getCertificateFunc func(hello *tls.ClientHelloInfo) (*tls.Certificate, error)
+
+// NewCAServer creates a new ACME test server. The returned CAServer issues
+// certs signed with the CA roots available in the Roots field.
+func NewCAServer(t *testing.T) *CAServer {
+	ca := &CAServer{t: t,
+		challengeTypes: []string{"fake-01", "tls-alpn-01", "http-01"},
+		domainAddr:     make(map[string]string),
+		domainGetCert:  make(map[string]getCertificateFunc),
+		domainHandler:  make(map[string]http.Handler),
+		validAuthz:     make(map[string]*authorization),
 	}
 
+	ca.server = httptest.NewUnstartedServer(http.HandlerFunc(ca.handle))
+
+	r, err := rand.Int(rand.Reader, big.NewInt(1000000))
+	if err != nil {
+		panic(fmt.Sprintf("rand.Int: %v", err))
+	}
+	ca.issuer = pkix.Name{
+		Organization: []string{"Test Acme Co"},
+		CommonName:   "Root CA " + r.String(),
+	}
+
+	return ca
+}
+
+func (ca *CAServer) generateRoot() {
 	key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
 	if err != nil {
 		panic(fmt.Sprintf("ecdsa.GenerateKey: %v", err))
 	}
 	tmpl := &x509.Certificate{
-		SerialNumber: big.NewInt(1),
-		Subject: pkix.Name{
-			Organization: []string{"Test Acme Co"},
-			CommonName:   "Root CA",
-		},
+		SerialNumber:          big.NewInt(1),
+		Subject:               ca.issuer,
 		NotBefore:             time.Now(),
 		NotAfter:              time.Now().Add(365 * 24 * time.Hour),
 		KeyUsage:              x509.KeyUsageCertSign,
@@ -100,40 +111,87 @@
 	if err != nil {
 		panic(fmt.Sprintf("x509.ParseCertificate: %v", err))
 	}
-	ca.Roots = x509.NewCertPool()
-	ca.Roots.AddCert(cert)
+	ca.roots = x509.NewCertPool()
+	ca.roots.AddCert(cert)
 	ca.rootKey = key
 	ca.rootCert = der
 	ca.rootTemplate = tmpl
+}
 
-	ca.server = httptest.NewServer(http.HandlerFunc(ca.handle))
-	ca.URL = ca.server.URL
+// IssuerName sets the name of the issuing CA.
+func (ca *CAServer) IssuerName(name pkix.Name) *CAServer {
+	if ca.url != "" {
+		panic("IssuerName must be called before Start")
+	}
+	ca.issuer = name
 	return ca
 }
 
-// Close shuts down the server and blocks until all outstanding
-// requests on this server have completed.
-func (ca *CAServer) Close() {
-	ca.server.Close()
+// ChallengeTypes sets the supported challenge types.
+func (ca *CAServer) ChallengeTypes(types ...string) *CAServer {
+	if ca.url != "" {
+		panic("ChallengeTypes must be called before Start")
+	}
+	ca.challengeTypes = types
+	return ca
+}
+
+// URL returns the server address, after Start has been called.
+func (ca *CAServer) URL() string {
+	if ca.url == "" {
+		panic("URL called before Start")
+	}
+	return ca.url
+}
+
+// Roots returns a pool cointaining the CA root.
+func (ca *CAServer) Roots() *x509.CertPool {
+	if ca.url == "" {
+		panic("Roots called before Start")
+	}
+	return ca.roots
+}
+
+// Start starts serving requests. The server address becomes available in the
+// URL field.
+func (ca *CAServer) Start() *CAServer {
+	if ca.url == "" {
+		ca.generateRoot()
+		ca.server.Start()
+		ca.t.Cleanup(ca.server.Close)
+		ca.url = ca.server.URL
+	}
+	return ca
 }
 
 func (ca *CAServer) serverURL(format string, arg ...interface{}) string {
 	return ca.server.URL + fmt.Sprintf(format, arg...)
 }
 
-func (ca *CAServer) addr(domain string) (string, error) {
+func (ca *CAServer) addr(domain string) (string, bool) {
 	ca.mu.Lock()
 	defer ca.mu.Unlock()
 	addr, ok := ca.domainAddr[domain]
-	if !ok {
-		return "", fmt.Errorf("CAServer: no addr resolution for %q", domain)
-	}
-	return addr, nil
+	return addr, ok
+}
+
+func (ca *CAServer) getCert(domain string) (getCertificateFunc, bool) {
+	ca.mu.Lock()
+	defer ca.mu.Unlock()
+	f, ok := ca.domainGetCert[domain]
+	return f, ok
+}
+
+func (ca *CAServer) getHandler(domain string) (http.Handler, bool) {
+	ca.mu.Lock()
+	defer ca.mu.Unlock()
+	h, ok := ca.domainHandler[domain]
+	return h, ok
 }
 
 func (ca *CAServer) httpErrorf(w http.ResponseWriter, code int, format string, a ...interface{}) {
 	s := fmt.Sprintf(format, a...)
-	log.Println(s)
+	ca.t.Errorf(format, a...)
 	http.Error(w, s, code)
 }
 
@@ -145,6 +203,22 @@
 	ca.domainAddr[domain] = addr
 }
 
+// ResolveGetCertificate redirects TLS connections for domain to f when
+// validating challenges for the domain authorization.
+func (ca *CAServer) ResolveGetCertificate(domain string, f getCertificateFunc) {
+	ca.mu.Lock()
+	defer ca.mu.Unlock()
+	ca.domainGetCert[domain] = f
+}
+
+// ResolveHandler redirects HTTP requests for domain to f when
+// validating challenges for the domain authorization.
+func (ca *CAServer) ResolveHandler(domain string, h http.Handler) {
+	ca.mu.Lock()
+	defer ca.mu.Unlock()
+	ca.domainHandler[domain] = h
+}
+
 type discovery struct {
 	NewNonce string `json:"newNonce"`
 	NewReg   string `json:"newAccount"`
@@ -163,6 +237,7 @@
 	Challenges []challenge `json:"challenges"`
 
 	domain string
+	id     int
 }
 
 type order struct {
@@ -175,7 +250,7 @@
 }
 
 func (ca *CAServer) handle(w http.ResponseWriter, r *http.Request) {
-	log.Printf("%s %s", r.Method, r.URL)
+	ca.t.Logf("%s %s", r.Method, r.URL)
 	w.Header().Set("Replay-Nonce", "nonce")
 	// TODO: Verify nonce header for all POST requests.
 
@@ -189,7 +264,6 @@
 			NewNonce: ca.serverURL("/new-nonce"),
 			NewReg:   ca.serverURL("/new-reg"),
 			NewOrder: ca.serverURL("/new-order"),
-			NewAuthz: ca.serverURL("/new-authz"),
 		}
 		if err := json.NewEncoder(w).Encode(resp); err != nil {
 			panic(fmt.Sprintf("discovery response: %v", err))
@@ -202,6 +276,13 @@
 
 	// Client key registration request.
 	case r.URL.Path == "/new-reg":
+		ca.mu.Lock()
+		defer ca.mu.Unlock()
+		if ca.acctRegistered {
+			ca.httpErrorf(w, http.StatusServiceUnavailable, "multiple accounts are not implemented")
+			return
+		}
+		ca.acctRegistered = true
 		// TODO: Check the user account key against a ca.accountKeys?
 		w.Header().Set("Location", ca.serverURL("/accounts/1"))
 		w.WriteHeader(http.StatusCreated)
@@ -221,7 +302,7 @@
 		o := &order{Status: acme.StatusPending}
 		for _, id := range req.Identifiers {
 			z := ca.authz(id.Value)
-			o.AuthzURLs = append(o.AuthzURLs, ca.serverURL("/authz/%s", z.domain))
+			o.AuthzURLs = append(o.AuthzURLs, ca.serverURL("/authz/%d", z.id))
 		}
 		orderID := len(ca.orders)
 		ca.orders = append(ca.orders, o)
@@ -244,49 +325,49 @@
 			panic(err)
 		}
 
-	// Identifier authorization request.
-	case r.URL.Path == "/new-authz":
-		var req struct {
-			Identifier struct{ Value string }
-		}
-		if err := decodePayload(&req, r.Body); err != nil {
-			ca.httpErrorf(w, http.StatusBadRequest, err.Error())
-			return
-		}
+	// Accept challenge requests.
+	case strings.HasPrefix(r.URL.Path, "/challenge/"):
+		parts := strings.Split(r.URL.Path, "/")
+		typ, id := parts[len(parts)-2], parts[len(parts)-1]
 		ca.mu.Lock()
-		defer ca.mu.Unlock()
-		z := ca.authz(req.Identifier.Value)
-		w.Header().Set("Location", ca.serverURL("/authz/%s", z.domain))
-		w.WriteHeader(http.StatusCreated)
-		if err := json.NewEncoder(w).Encode(z); err != nil {
-			panic(fmt.Sprintf("new authz response: %v", err))
+		supported := false
+		for _, suppTyp := range ca.challengeTypes {
+			if suppTyp == typ {
+				supported = true
+			}
 		}
-
-	// Accept tls-alpn-01 challenge type requests.
-	case strings.HasPrefix(r.URL.Path, "/challenge/tls-alpn-01/"):
-		domain := strings.TrimPrefix(r.URL.Path, "/challenge/tls-alpn-01/")
-		ca.mu.Lock()
-		_, exist := ca.authorizations[domain]
+		a, err := ca.storedAuthz(id)
 		ca.mu.Unlock()
-		if !exist {
-			ca.httpErrorf(w, http.StatusBadRequest, "challenge accept: no authz for %q", domain)
+		if !supported {
+			ca.httpErrorf(w, http.StatusBadRequest, "unsupported challenge: %v", typ)
 			return
 		}
-		go ca.validateChallenge("tls-alpn-01", domain)
+		if err != nil {
+			ca.httpErrorf(w, http.StatusBadRequest, "challenge accept: %v", err)
+			return
+		}
+		go ca.validateChallenge(a, typ)
 		w.Write([]byte("{}"))
 
 	// Get authorization status requests.
 	case strings.HasPrefix(r.URL.Path, "/authz/"):
-		domain := strings.TrimPrefix(r.URL.Path, "/authz/")
+		var req struct{ Status string }
+		decodePayload(&req, r.Body)
+		deactivate := req.Status == "deactivated"
 		ca.mu.Lock()
 		defer ca.mu.Unlock()
-		authz, ok := ca.authorizations[domain]
-		if !ok {
-			ca.httpErrorf(w, http.StatusNotFound, "no authz for %q", domain)
+		authz, err := ca.storedAuthz(strings.TrimPrefix(r.URL.Path, "/authz/"))
+		if err != nil {
+			ca.httpErrorf(w, http.StatusNotFound, "%v", err)
 			return
 		}
+		if deactivate {
+			// Note we don't invalidate authorized orders as we should.
+			authz.Status = "deactivated"
+			ca.t.Logf("authz %d is now %s", authz.id, authz.Status)
+		}
 		if err := json.NewEncoder(w).Encode(authz); err != nil {
-			panic(fmt.Sprintf("get authz for %q response: %v", domain, err))
+			panic(fmt.Sprintf("encoding authz %d: %v", authz.id, err))
 		}
 
 	// Certificate issuance request.
@@ -314,15 +395,6 @@
 			ca.httpErrorf(w, http.StatusBadRequest, err.Error())
 			return
 		}
-		names := unique(append(csr.DNSNames, csr.Subject.CommonName))
-		if err := ca.matchWhitelist(names); err != nil {
-			ca.httpErrorf(w, http.StatusUnauthorized, err.Error())
-			return
-		}
-		if err := ca.authorized(names); err != nil {
-			ca.httpErrorf(w, http.StatusUnauthorized, err.Error())
-			return
-		}
 		// Issue the certificate.
 		der, err := ca.leafCert(csr)
 		if err != nil {
@@ -355,25 +427,6 @@
 	}
 }
 
-// matchWhitelist reports whether all dnsNames are whitelisted.
-// The whitelist is provided in NewCAServer.
-func (ca *CAServer) matchWhitelist(dnsNames []string) error {
-	if len(ca.domainsWhitelist) == 0 {
-		return nil
-	}
-	var nomatch []string
-	for _, name := range dnsNames {
-		i := sort.SearchStrings(ca.domainsWhitelist, name)
-		if i == len(ca.domainsWhitelist) || ca.domainsWhitelist[i] != name {
-			nomatch = append(nomatch, name)
-		}
-	}
-	if len(nomatch) > 0 {
-		return fmt.Errorf("matchWhitelist: some domains don't match: %q", nomatch)
-	}
-	return nil
-}
-
 // storedOrder retrieves a previously created order at index i.
 // It requires ca.mu to be locked.
 func (ca *CAServer) storedOrder(i string) (*order, error) {
@@ -390,43 +443,45 @@
 	return ca.orders[idx], nil
 }
 
-// authz returns an existing authorization for the identifier or creates a new one.
+// storedAuthz retrieves a previously created authz at index i.
 // It requires ca.mu to be locked.
+func (ca *CAServer) storedAuthz(i string) (*authorization, error) {
+	idx, err := strconv.Atoi(i)
+	if err != nil {
+		return nil, fmt.Errorf("storedAuthz: %v", err)
+	}
+	if idx < 0 {
+		return nil, fmt.Errorf("storedAuthz: invalid authz index %d", idx)
+	}
+	if idx > len(ca.authorizations)-1 {
+		return nil, fmt.Errorf("storedAuthz: no such authz %d", idx)
+	}
+	return ca.authorizations[idx], nil
+}
+
+// authz returns an existing valid authorization for the identifier or creates a
+// new one. It requires ca.mu to be locked.
 func (ca *CAServer) authz(identifier string) *authorization {
-	authz, ok := ca.authorizations[identifier]
+	authz, ok := ca.validAuthz[identifier]
 	if !ok {
+		authzId := len(ca.authorizations)
 		authz = &authorization{
+			id:     authzId,
 			domain: identifier,
 			Status: acme.StatusPending,
 		}
 		for _, typ := range ca.challengeTypes {
 			authz.Challenges = append(authz.Challenges, challenge{
 				Type:  typ,
-				URI:   ca.serverURL("/challenge/%s/%s", typ, authz.domain),
-				Token: challengeToken(authz.domain, typ),
+				URI:   ca.serverURL("/challenge/%s/%d", typ, authzId),
+				Token: challengeToken(authz.domain, typ, authzId),
 			})
 		}
-		ca.authorizations[authz.domain] = authz
+		ca.authorizations = append(ca.authorizations, authz)
 	}
 	return authz
 }
 
-// authorized reports whether all authorizations for dnsNames have been satisfied.
-// It requires ca.mu to be locked.
-func (ca *CAServer) authorized(dnsNames []string) error {
-	var noauthz []string
-	for _, name := range dnsNames {
-		authz, ok := ca.authorizations[name]
-		if !ok || authz.Status != acme.StatusValid {
-			noauthz = append(noauthz, name)
-		}
-	}
-	if len(noauthz) > 0 {
-		return fmt.Errorf("CAServer: no authz for %q", noauthz)
-	}
-	return nil
-}
-
 // leafCert issues a new certificate.
 // It requires ca.mu to be locked.
 func (ca *CAServer) leafCert(csr *x509.CertificateRequest) (der []byte, err error) {
@@ -447,24 +502,72 @@
 	return x509.CreateCertificate(rand.Reader, leaf, ca.rootTemplate, csr.PublicKey, ca.rootKey)
 }
 
-// TODO: Only tls-alpn-01 is currently supported: implement http-01 and dns-01.
-func (ca *CAServer) validateChallenge(typ, identifier string) {
+// LeafCert issues a leaf certificate.
+func (ca *CAServer) LeafCert(name, keyType string, notBefore, notAfter time.Time) *tls.Certificate {
+	if ca.url == "" {
+		panic("LeafCert called before Start")
+	}
+
+	ca.mu.Lock()
+	defer ca.mu.Unlock()
+	var pk crypto.Signer
+	switch keyType {
+	case "RSA":
+		var err error
+		pk, err = rsa.GenerateKey(rand.Reader, 1024)
+		if err != nil {
+			ca.t.Fatal(err)
+		}
+	case "ECDSA":
+		var err error
+		pk, err = ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
+		if err != nil {
+			ca.t.Fatal(err)
+		}
+	default:
+		panic("LeafCert: unknown key type")
+	}
+	ca.certCount++ // next leaf cert serial number
+	leaf := &x509.Certificate{
+		SerialNumber:          big.NewInt(int64(ca.certCount)),
+		Subject:               pkix.Name{Organization: []string{"Test Acme Co"}},
+		NotBefore:             notBefore,
+		NotAfter:              notAfter,
+		KeyUsage:              x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment,
+		ExtKeyUsage:           []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
+		DNSNames:              []string{name},
+		BasicConstraintsValid: true,
+	}
+	der, err := x509.CreateCertificate(rand.Reader, leaf, ca.rootTemplate, pk.Public(), ca.rootKey)
+	if err != nil {
+		ca.t.Fatal(err)
+	}
+	return &tls.Certificate{
+		Certificate: [][]byte{der},
+		PrivateKey:  pk,
+	}
+}
+
+func (ca *CAServer) validateChallenge(authz *authorization, typ string) {
 	var err error
 	switch typ {
 	case "tls-alpn-01":
-		err = ca.verifyALPNChallenge(identifier)
+		err = ca.verifyALPNChallenge(authz)
+	case "http-01":
+		err = ca.verifyHTTPChallenge(authz)
 	default:
 		panic(fmt.Sprintf("validation of %q is not implemented", typ))
 	}
 	ca.mu.Lock()
 	defer ca.mu.Unlock()
-	authz := ca.authorizations[identifier]
 	if err != nil {
 		authz.Status = "invalid"
 	} else {
 		authz.Status = "valid"
+		ca.validAuthz[authz.domain] = authz
 	}
-	log.Printf("validated %q for %q; authz status is now: %s", typ, identifier, authz.Status)
+	ca.t.Logf("validated %q for %q, err: %v", typ, authz.domain, err)
+	ca.t.Logf("authz %d is now %s", authz.id, authz.Status)
 	// Update all pending orders.
 	// An order becomes "ready" if all authorizations are "valid".
 	// An order becomes "invalid" if any authorization is "invalid".
@@ -476,14 +579,14 @@
 		}
 		var countValid int
 		for _, zurl := range o.AuthzURLs {
-			z, ok := ca.authorizations[path.Base(zurl)]
-			if !ok {
-				log.Printf("no authz %q for order %d", zurl, i)
+			z, err := ca.storedAuthz(path.Base(zurl))
+			if err != nil {
+				ca.t.Logf("no authz %q for order %d", zurl, i)
 				continue OrdersLoop
 			}
 			if z.Status == acme.StatusInvalid {
 				o.Status = acme.StatusInvalid
-				log.Printf("order %d is now invalid", i)
+				ca.t.Logf("order %d is now invalid", i)
 				continue OrdersLoop
 			}
 			if z.Status == acme.StatusValid {
@@ -493,33 +596,125 @@
 		if countValid == len(o.AuthzURLs) {
 			o.Status = acme.StatusReady
 			o.FinalizeURL = ca.serverURL("/new-cert/%d", i)
-			log.Printf("order %d is now ready", i)
+			ca.t.Logf("order %d is now ready", i)
 		}
 	}
 }
 
-func (ca *CAServer) verifyALPNChallenge(domain string) error {
+func (ca *CAServer) verifyALPNChallenge(a *authorization) error {
 	const acmeALPNProto = "acme-tls/1"
 
-	addr, err := ca.addr(domain)
-	if err != nil {
-		return err
+	addr, haveAddr := ca.addr(a.domain)
+	getCert, haveGetCert := ca.getCert(a.domain)
+	if !haveAddr && !haveGetCert {
+		return fmt.Errorf("no resolution information for %q", a.domain)
 	}
-	conn, err := tls.Dial("tcp", addr, &tls.Config{
-		ServerName:         domain,
-		InsecureSkipVerify: true,
-		NextProtos:         []string{acmeALPNProto},
-	})
-	if err != nil {
-		return err
+	if haveAddr && haveGetCert {
+		return fmt.Errorf("overlapping resolution information for %q", a.domain)
 	}
-	if v := conn.ConnectionState().NegotiatedProtocol; v != acmeALPNProto {
-		return fmt.Errorf("CAServer: verifyALPNChallenge: negotiated proto is %q; want %q", v, acmeALPNProto)
+
+	var crt *x509.Certificate
+	switch {
+	case haveAddr:
+		conn, err := tls.Dial("tcp", addr, &tls.Config{
+			ServerName:         a.domain,
+			InsecureSkipVerify: true,
+			NextProtos:         []string{acmeALPNProto},
+			MinVersion:         tls.VersionTLS12,
+		})
+		if err != nil {
+			return err
+		}
+		if v := conn.ConnectionState().NegotiatedProtocol; v != acmeALPNProto {
+			return fmt.Errorf("CAServer: verifyALPNChallenge: negotiated proto is %q; want %q", v, acmeALPNProto)
+		}
+		if n := len(conn.ConnectionState().PeerCertificates); n != 1 {
+			return fmt.Errorf("len(PeerCertificates) = %d; want 1", n)
+		}
+		crt = conn.ConnectionState().PeerCertificates[0]
+	case haveGetCert:
+		hello := &tls.ClientHelloInfo{
+			ServerName: a.domain,
+			// TODO: support selecting ECDSA.
+			CipherSuites:      []uint16{tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305},
+			SupportedProtos:   []string{acme.ALPNProto},
+			SupportedVersions: []uint16{tls.VersionTLS12},
+		}
+		c, err := getCert(hello)
+		if err != nil {
+			return err
+		}
+		crt, err = x509.ParseCertificate(c.Certificate[0])
+		if err != nil {
+			return err
+		}
 	}
-	if n := len(conn.ConnectionState().PeerCertificates); n != 1 {
-		return fmt.Errorf("len(PeerCertificates) = %d; want 1", n)
+
+	if err := crt.VerifyHostname(a.domain); err != nil {
+		return fmt.Errorf("verifyALPNChallenge: VerifyHostname: %v", err)
 	}
-	// TODO: verify conn.ConnectionState().PeerCertificates[0]
+	// See RFC 8737, Section 6.1.
+	oid := asn1.ObjectIdentifier{1, 3, 6, 1, 5, 5, 7, 1, 31}
+	for _, x := range crt.Extensions {
+		if x.Id.Equal(oid) {
+			// TODO: check the token.
+			return nil
+		}
+	}
+	return fmt.Errorf("verifyTokenCert: no id-pe-acmeIdentifier extension found")
+}
+
+func (ca *CAServer) verifyHTTPChallenge(a *authorization) error {
+	addr, haveAddr := ca.addr(a.domain)
+	handler, haveHandler := ca.getHandler(a.domain)
+	if !haveAddr && !haveHandler {
+		return fmt.Errorf("no resolution information for %q", a.domain)
+	}
+	if haveAddr && haveHandler {
+		return fmt.Errorf("overlapping resolution information for %q", a.domain)
+	}
+
+	token := challengeToken(a.domain, "http-01", a.id)
+	path := "/.well-known/acme-challenge/" + token
+
+	var body string
+	switch {
+	case haveAddr:
+		t := &http.Transport{
+			DialContext: func(ctx context.Context, network, _ string) (net.Conn, error) {
+				return (&net.Dialer{}).DialContext(ctx, network, addr)
+			},
+		}
+		req, err := http.NewRequest("GET", "http://"+a.domain+path, nil)
+		if err != nil {
+			return err
+		}
+		res, err := t.RoundTrip(req)
+		if err != nil {
+			return err
+		}
+		if res.StatusCode != http.StatusOK {
+			return fmt.Errorf("http token: w.Code = %d; want %d", res.StatusCode, http.StatusOK)
+		}
+		b, err := io.ReadAll(res.Body)
+		if err != nil {
+			return err
+		}
+		body = string(b)
+	case haveHandler:
+		r := httptest.NewRequest("GET", path, nil)
+		r.Host = a.domain
+		w := httptest.NewRecorder()
+		handler.ServeHTTP(w, r)
+		if w.Code != http.StatusOK {
+			return fmt.Errorf("http token: w.Code = %d; want %d", w.Code, http.StatusOK)
+		}
+		body = w.Body.String()
+	}
+
+	if !strings.HasPrefix(body, token) {
+		return fmt.Errorf("http token value = %q; want 'token-http-01.' prefix", body)
+	}
 	return nil
 }
 
@@ -535,8 +730,8 @@
 	return json.Unmarshal(payload, v)
 }
 
-func challengeToken(domain, challType string) string {
-	return fmt.Sprintf("token-%s-%s", domain, challType)
+func challengeToken(domain, challType string, authzID int) string {
+	return fmt.Sprintf("token-%s-%s-%d", domain, challType, authzID)
 }
 
 func unique(a []string) []string {
diff --git a/acme/autocert/renewal_test.go b/acme/autocert/renewal_test.go
index d13d190..94bbc60 100644
--- a/acme/autocert/renewal_test.go
+++ b/acme/autocert/renewal_test.go
@@ -6,19 +6,13 @@
 
 import (
 	"context"
+	"crypto"
 	"crypto/ecdsa"
-	"crypto/elliptic"
-	"crypto/rand"
-	"crypto/tls"
-	"crypto/x509"
-	"encoding/base64"
-	"fmt"
-	"net/http"
-	"net/http/httptest"
 	"testing"
 	"time"
 
 	"golang.org/x/crypto/acme"
+	"golang.org/x/crypto/acme/autocert/internal/acmetest"
 )
 
 func TestRenewalNext(t *testing.T) {
@@ -48,86 +42,20 @@
 }
 
 func TestRenewFromCache(t *testing.T) {
-	// ACME CA server stub
-	var ca *httptest.Server
-	ca = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
-		w.Header().Set("Replay-Nonce", "nonce")
-		if r.Method == "HEAD" {
-			// a nonce request
-			return
-		}
+	man := testManager(t)
+	man.RenewBefore = 24 * time.Hour
 
-		switch r.URL.Path {
-		// discovery
-		case "/":
-			if err := discoTmpl.Execute(w, ca.URL); err != nil {
-				t.Fatalf("discoTmpl: %v", err)
-			}
-		// client key registration
-		case "/new-reg":
-			w.Write([]byte("{}"))
-		// domain authorization
-		case "/new-authz":
-			w.Header().Set("Location", ca.URL+"/authz/1")
-			w.WriteHeader(http.StatusCreated)
-			w.Write([]byte(`{"status": "valid"}`))
-		// authorization status request done by Manager's revokePendingAuthz.
-		case "/authz/1":
-			w.Write([]byte(`{"status": "valid"}`))
-		// cert request
-		case "/new-cert":
-			var req struct {
-				CSR string `json:"csr"`
-			}
-			decodePayload(&req, r.Body)
-			b, _ := base64.RawURLEncoding.DecodeString(req.CSR)
-			csr, err := x509.ParseCertificateRequest(b)
-			if err != nil {
-				t.Fatalf("new-cert: CSR: %v", err)
-			}
-			der, err := dummyCert(csr.PublicKey, exampleDomain)
-			if err != nil {
-				t.Fatalf("new-cert: dummyCert: %v", err)
-			}
-			chainUp := fmt.Sprintf("<%s/ca-cert>; rel=up", ca.URL)
-			w.Header().Set("Link", chainUp)
-			w.WriteHeader(http.StatusCreated)
-			w.Write(der)
-		// CA chain cert
-		case "/ca-cert":
-			der, err := dummyCert(nil, "ca")
-			if err != nil {
-				t.Fatalf("ca-cert: dummyCert: %v", err)
-			}
-			w.Write(der)
-		default:
-			t.Errorf("unrecognized r.URL.Path: %s", r.URL.Path)
-		}
-	}))
-	defer ca.Close()
+	ca := acmetest.NewCAServer(t).Start()
+	ca.ResolveGetCertificate(exampleDomain, man.GetCertificate)
 
-	man := &Manager{
-		Prompt:      AcceptTOS,
-		Cache:       newMemCache(t),
-		RenewBefore: 24 * time.Hour,
-		Client: &acme.Client{
-			DirectoryURL: ca.URL,
-		},
+	man.Client = &acme.Client{
+		DirectoryURL: ca.URL(),
 	}
-	defer man.stopRenew()
 
 	// cache an almost expired cert
-	key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
-	if err != nil {
-		t.Fatal(err)
-	}
 	now := time.Now()
-	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(), exampleCertKey, tlscert); err != nil {
+	c := ca.LeafCert(exampleDomain, "ECDSA", now.Add(-2*time.Hour), now.Add(time.Minute))
+	if err := man.cachePut(context.Background(), exampleCertKey, c); err != nil {
 		t.Fatal(err)
 	}
 
@@ -142,7 +70,7 @@
 			t.Errorf("testDidRenewLoop: %v", err)
 		}
 		// Next should be about 90 days:
-		// dummyCert creates 90days expiry + account for man.RenewBefore.
+		// CaServer creates 90days expiry + account for man.RenewBefore.
 		// Previous expiration was within 1 min.
 		future := 88 * 24 * time.Hour
 		if next < future {
@@ -190,45 +118,30 @@
 }
 
 func TestRenewFromCacheAlreadyRenewed(t *testing.T) {
-	man := &Manager{
-		Prompt:      AcceptTOS,
-		Cache:       newMemCache(t),
-		RenewBefore: 24 * time.Hour,
-		Client: &acme.Client{
-			DirectoryURL: "invalid",
-		},
+	ca := acmetest.NewCAServer(t).Start()
+	man := testManager(t)
+	man.RenewBefore = 24 * time.Hour
+	man.Client = &acme.Client{
+		DirectoryURL: "invalid",
 	}
-	defer man.stopRenew()
 
 	// cache a recently renewed cert with a different private key
-	newKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
-	if err != nil {
-		t.Fatal(err)
-	}
 	now := time.Now()
-	newCert, err := dateDummyCert(newKey.Public(), now.Add(-2*time.Hour), now.Add(time.Hour*24*90), exampleDomain)
-	if err != nil {
+	newCert := ca.LeafCert(exampleDomain, "ECDSA", now.Add(-2*time.Hour), now.Add(time.Hour*24*90))
+	if err := man.cachePut(context.Background(), exampleCertKey, newCert); err != nil {
 		t.Fatal(err)
 	}
-	newLeaf, err := validCert(exampleCertKey, [][]byte{newCert}, newKey, now)
+	newLeaf, err := validCert(exampleCertKey, newCert.Certificate, newCert.PrivateKey.(crypto.Signer), now)
 	if err != nil {
 		t.Fatal(err)
 	}
-	newTLSCert := &tls.Certificate{PrivateKey: newKey, Certificate: [][]byte{newCert}, Leaf: newLeaf}
-	if err := man.cachePut(context.Background(), exampleCertKey, newTLSCert); err != nil {
-		t.Fatal(err)
-	}
 
 	// set internal state to an almost expired cert
-	key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
+	oldCert := ca.LeafCert(exampleDomain, "ECDSA", now.Add(-2*time.Hour), now.Add(time.Minute))
 	if err != nil {
 		t.Fatal(err)
 	}
-	oldCert, err := dateDummyCert(key.Public(), now.Add(-2*time.Hour), now.Add(time.Minute), exampleDomain)
-	if err != nil {
-		t.Fatal(err)
-	}
-	oldLeaf, err := validCert(exampleCertKey, [][]byte{oldCert}, key, now)
+	oldLeaf, err := validCert(exampleCertKey, oldCert.Certificate, oldCert.PrivateKey.(crypto.Signer), now)
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -237,14 +150,14 @@
 		man.state = make(map[certKey]*certState)
 	}
 	s := &certState{
-		key:  key,
-		cert: [][]byte{oldCert},
+		key:  oldCert.PrivateKey.(crypto.Signer),
+		cert: oldCert.Certificate,
 		leaf: oldLeaf,
 	}
 	man.state[exampleCertKey] = s
 	man.stateMu.Unlock()
 
-	// veriy the renewal accepted the newer cached cert
+	// verify the renewal accepted the newer cached cert
 	defer func() {
 		testDidRenewLoop = func(next time.Duration, err error) {}
 	}()
@@ -278,8 +191,8 @@
 			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 {
-			t.Fatalf("state key was not updated from cache x: %v y: %v; want x: %v y: %v", stateKey.X, stateKey.Y, newKey.X, newKey.Y)
+		if !stateKey.Equal(newLeaf.PublicKey) {
+			t.Fatal("state key was not updated from cache")
 		}
 		tlscert, err = s.tlscert()
 		if err != nil {
@@ -295,8 +208,8 @@
 			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 {
-			t.Fatalf("renewal private key was not updated from cache x: %v y: %v; want x: %v y: %v", renewalKey.X, renewalKey.Y, newKey.X, newKey.Y)
+		if !renewalKey.Equal(newLeaf.PublicKey) {
+			t.Fatal("renewal private key was not updated from cache")
 		}
 
 	}
@@ -325,8 +238,8 @@
 		if err != nil {
 			t.Fatal(err)
 		}
-		if !newTLSCert.Leaf.NotAfter.Equal(tlscert.Leaf.NotAfter) {
-			t.Errorf("state leaf.NotAfter = %v; want == %v", tlscert.Leaf.NotAfter, newTLSCert.Leaf.NotAfter)
+		if !newLeaf.NotAfter.Equal(tlscert.Leaf.NotAfter) {
+			t.Errorf("state leaf.NotAfter = %v; want == %v", tlscert.Leaf.NotAfter, newLeaf.NotAfter)
 		}
 	}
 }