acme: remove support for pre-RFC 8555 ACME spec

LetsEncrypt removed it anyway.

No API changes. Just a lot of deleted code.

Fixes golang/go#46654

Co-authored-by: Brad Fitzpatrick <bradfitz@golang.org>
Change-Id: I65cd0d33236033682b767403ad92aa572bee4fdd
Reviewed-on: https://go-review.googlesource.com/c/crypto/+/380314
Trust: Filippo Valsorda <filippo@golang.org>
Reviewed-by: Brad Fitzpatrick <bradfitz@golang.org>
Trust: Brad Fitzpatrick <bradfitz@golang.org>
diff --git a/acme/acme.go b/acme/acme.go
index 271df26..f2d23f6 100644
--- a/acme/acme.go
+++ b/acme/acme.go
@@ -3,17 +3,20 @@
 // license that can be found in the LICENSE file.
 
 // Package acme provides an implementation of the
-// Automatic Certificate Management Environment (ACME) spec.
-// The initial implementation was based on ACME draft-02 and
-// is now being extended to comply with RFC 8555.
-// See https://tools.ietf.org/html/draft-ietf-acme-acme-02
-// and https://tools.ietf.org/html/rfc8555 for details.
+// Automatic Certificate Management Environment (ACME) spec,
+// most famously used by Let's Encrypt.
+//
+// The initial implementation of this package was based on an early version
+// of the spec. The current implementation supports only the modern
+// RFC 8555 but some of the old API surface remains for compatibility.
+// While code using the old API will still compile, it will return an error.
+// Note the deprecation comments to update your code.
+//
+// See https://tools.ietf.org/html/rfc8555 for the spec.
 //
 // Most common scenarios will want to use autocert subdirectory instead,
 // which provides automatic access to certificates from Let's Encrypt
 // and any other ACME-based CA.
-//
-// This package is a work in progress and makes no API stability promises.
 package acme
 
 import (
@@ -33,8 +36,6 @@
 	"encoding/pem"
 	"errors"
 	"fmt"
-	"io"
-	"io/ioutil"
 	"math/big"
 	"net/http"
 	"strings"
@@ -72,6 +73,7 @@
 )
 
 // Client is an ACME client.
+//
 // The only required field is Key. An example of creating a client with a new key
 // is as follows:
 //
@@ -145,9 +147,6 @@
 func (c *Client) accountKID(ctx context.Context) KeyID {
 	c.cacheMu.Lock()
 	defer c.cacheMu.Unlock()
-	if !c.dir.rfcCompliant() {
-		return noKeyID
-	}
 	if c.KID != noKeyID {
 		return c.KID
 	}
@@ -159,6 +158,8 @@
 	return c.KID
 }
 
+var errPreRFC = errors.New("acme: server does not support the RFC 8555 version of ACME")
+
 // Discover performs ACME server discovery using c.DirectoryURL.
 //
 // It caches successful result. So, subsequent calls will not result in
@@ -179,53 +180,36 @@
 	c.addNonce(res.Header)
 
 	var v struct {
-		Reg          string `json:"new-reg"`
-		RegRFC       string `json:"newAccount"`
-		Authz        string `json:"new-authz"`
-		AuthzRFC     string `json:"newAuthz"`
-		OrderRFC     string `json:"newOrder"`
-		Cert         string `json:"new-cert"`
-		Revoke       string `json:"revoke-cert"`
-		RevokeRFC    string `json:"revokeCert"`
-		NonceRFC     string `json:"newNonce"`
-		KeyChangeRFC string `json:"keyChange"`
-		Meta         struct {
-			Terms           string   `json:"terms-of-service"`
-			TermsRFC        string   `json:"termsOfService"`
-			WebsiteRFC      string   `json:"website"`
-			CAA             []string `json:"caa-identities"`
-			CAARFC          []string `json:"caaIdentities"`
-			ExternalAcctRFC bool     `json:"externalAccountRequired"`
+		Reg       string `json:"newAccount"`
+		Authz     string `json:"newAuthz"`
+		Order     string `json:"newOrder"`
+		Revoke    string `json:"revokeCert"`
+		Nonce     string `json:"newNonce"`
+		KeyChange string `json:"keyChange"`
+		Meta      struct {
+			Terms        string   `json:"termsOfService"`
+			Website      string   `json:"website"`
+			CAA          []string `json:"caaIdentities"`
+			ExternalAcct bool     `json:"externalAccountRequired"`
 		}
 	}
 	if err := json.NewDecoder(res.Body).Decode(&v); err != nil {
 		return Directory{}, err
 	}
-	if v.OrderRFC == "" {
-		// Non-RFC compliant ACME CA.
-		c.dir = &Directory{
-			RegURL:    v.Reg,
-			AuthzURL:  v.Authz,
-			CertURL:   v.Cert,
-			RevokeURL: v.Revoke,
-			Terms:     v.Meta.Terms,
-			Website:   v.Meta.WebsiteRFC,
-			CAA:       v.Meta.CAA,
-		}
-		return *c.dir, nil
+	if v.Order == "" {
+		return Directory{}, errPreRFC
 	}
-	// RFC compliant ACME CA.
 	c.dir = &Directory{
-		RegURL:                  v.RegRFC,
-		AuthzURL:                v.AuthzRFC,
-		OrderURL:                v.OrderRFC,
-		RevokeURL:               v.RevokeRFC,
-		NonceURL:                v.NonceRFC,
-		KeyChangeURL:            v.KeyChangeRFC,
-		Terms:                   v.Meta.TermsRFC,
-		Website:                 v.Meta.WebsiteRFC,
-		CAA:                     v.Meta.CAARFC,
-		ExternalAccountRequired: v.Meta.ExternalAcctRFC,
+		RegURL:                  v.Reg,
+		AuthzURL:                v.Authz,
+		OrderURL:                v.Order,
+		RevokeURL:               v.Revoke,
+		NonceURL:                v.Nonce,
+		KeyChangeURL:            v.KeyChange,
+		Terms:                   v.Meta.Terms,
+		Website:                 v.Meta.Website,
+		CAA:                     v.Meta.CAA,
+		ExternalAccountRequired: v.Meta.ExternalAcct,
 	}
 	return *c.dir, nil
 }
@@ -237,55 +221,11 @@
 	return LetsEncryptURL
 }
 
-// CreateCert requests a new certificate using the Certificate Signing Request csr encoded in DER format.
-// It is incompatible with RFC 8555. Callers should use CreateOrderCert when interfacing
-// with an RFC-compliant CA.
+// CreateCert was part of the old version of ACME. It is incompatible with RFC 8555.
 //
-// The exp argument indicates the desired certificate validity duration. CA may issue a certificate
-// with a different duration.
-// If the bundle argument is true, the returned value will also contain the CA (issuer) certificate chain.
-//
-// In the case where CA server does not provide the issued certificate in the response,
-// CreateCert will poll certURL using c.FetchCert, which will result in additional round-trips.
-// In such a scenario, the caller can cancel the polling with ctx.
-//
-// CreateCert returns an error if the CA's response or chain was unreasonably large.
-// Callers are encouraged to parse the returned value to ensure the certificate is valid and has the expected features.
+// Deprecated: this was for the pre-RFC 8555 version of ACME. Callers should use CreateOrderCert.
 func (c *Client) CreateCert(ctx context.Context, csr []byte, exp time.Duration, bundle bool) (der [][]byte, certURL string, err error) {
-	if _, err := c.Discover(ctx); err != nil {
-		return nil, "", err
-	}
-
-	req := struct {
-		Resource  string `json:"resource"`
-		CSR       string `json:"csr"`
-		NotBefore string `json:"notBefore,omitempty"`
-		NotAfter  string `json:"notAfter,omitempty"`
-	}{
-		Resource: "new-cert",
-		CSR:      base64.RawURLEncoding.EncodeToString(csr),
-	}
-	now := timeNow()
-	req.NotBefore = now.Format(time.RFC3339)
-	if exp > 0 {
-		req.NotAfter = now.Add(exp).Format(time.RFC3339)
-	}
-
-	res, err := c.post(ctx, nil, c.dir.CertURL, req, wantStatus(http.StatusCreated))
-	if err != nil {
-		return nil, "", err
-	}
-	defer res.Body.Close()
-
-	curl := res.Header.Get("Location") // cert permanent URL
-	if res.ContentLength == 0 {
-		// no cert in the body; poll until we get it
-		cert, err := c.FetchCert(ctx, curl, bundle)
-		return cert, curl, err
-	}
-	// slurp issued cert and CA chain, if requested
-	cert, err := c.responseCert(ctx, res, bundle)
-	return cert, curl, err
+	return nil, "", errPreRFC
 }
 
 // FetchCert retrieves already issued certificate from the given url, in DER format.
@@ -299,20 +239,10 @@
 // Callers are encouraged to parse the returned value to ensure the certificate is valid
 // and has expected features.
 func (c *Client) FetchCert(ctx context.Context, url string, bundle bool) ([][]byte, error) {
-	dir, err := c.Discover(ctx)
-	if err != nil {
+	if _, err := c.Discover(ctx); err != nil {
 		return nil, err
 	}
-	if dir.rfcCompliant() {
-		return c.fetchCertRFC(ctx, url, bundle)
-	}
-
-	// Legacy non-authenticated GET request.
-	res, err := c.get(ctx, url, wantStatus(http.StatusOK))
-	if err != nil {
-		return nil, err
-	}
-	return c.responseCert(ctx, res, bundle)
+	return c.fetchCertRFC(ctx, url, bundle)
 }
 
 // RevokeCert revokes a previously issued certificate cert, provided in DER format.
@@ -322,30 +252,10 @@
 // For instance, the key pair of the certificate may be authorized.
 // If the key is nil, c.Key is used instead.
 func (c *Client) RevokeCert(ctx context.Context, key crypto.Signer, cert []byte, reason CRLReasonCode) error {
-	dir, err := c.Discover(ctx)
-	if err != nil {
+	if _, err := c.Discover(ctx); err != nil {
 		return err
 	}
-	if dir.rfcCompliant() {
-		return c.revokeCertRFC(ctx, key, cert, reason)
-	}
-
-	// Legacy CA.
-	body := &struct {
-		Resource string `json:"resource"`
-		Cert     string `json:"certificate"`
-		Reason   int    `json:"reason"`
-	}{
-		Resource: "revoke-cert",
-		Cert:     base64.RawURLEncoding.EncodeToString(cert),
-		Reason:   int(reason),
-	}
-	res, err := c.post(ctx, key, dir.RevokeURL, body, wantStatus(http.StatusOK))
-	if err != nil {
-		return err
-	}
-	defer res.Body.Close()
-	return nil
+	return c.revokeCertRFC(ctx, key, cert, reason)
 }
 
 // AcceptTOS always returns true to indicate the acceptance of a CA's Terms of Service
@@ -368,75 +278,33 @@
 	if c.Key == nil {
 		return nil, errors.New("acme: client.Key must be set to Register")
 	}
-
-	dir, err := c.Discover(ctx)
-	if err != nil {
+	if _, err := c.Discover(ctx); err != nil {
 		return nil, err
 	}
-	if dir.rfcCompliant() {
-		return c.registerRFC(ctx, acct, prompt)
-	}
-
-	// Legacy ACME draft registration flow.
-	a, err := c.doReg(ctx, dir.RegURL, "new-reg", acct)
-	if err != nil {
-		return nil, err
-	}
-	var accept bool
-	if a.CurrentTerms != "" && a.CurrentTerms != a.AgreedTerms {
-		accept = prompt(a.CurrentTerms)
-	}
-	if accept {
-		a.AgreedTerms = a.CurrentTerms
-		a, err = c.UpdateReg(ctx, a)
-	}
-	return a, err
+	return c.registerRFC(ctx, acct, prompt)
 }
 
 // GetReg retrieves an existing account associated with c.Key.
 //
-// The url argument is an Account URI used with pre-RFC 8555 CAs.
-// It is ignored when interfacing with an RFC-compliant CA.
+// The url argument is a legacy artifact of the pre-RFC 8555 API
+// and is ignored.
 func (c *Client) GetReg(ctx context.Context, url string) (*Account, error) {
-	dir, err := c.Discover(ctx)
-	if err != nil {
+	if _, err := c.Discover(ctx); err != nil {
 		return nil, err
 	}
-	if dir.rfcCompliant() {
-		return c.getRegRFC(ctx)
-	}
-
-	// Legacy CA.
-	a, err := c.doReg(ctx, url, "reg", nil)
-	if err != nil {
-		return nil, err
-	}
-	a.URI = url
-	return a, nil
+	return c.getRegRFC(ctx)
 }
 
 // UpdateReg updates an existing registration.
 // It returns an updated account copy. The provided account is not modified.
 //
-// When interfacing with RFC-compliant CAs, a.URI is ignored and the account URL
-// associated with c.Key is used instead.
+// The account's URI is ignored and the account URL associated with
+// c.Key is used instead.
 func (c *Client) UpdateReg(ctx context.Context, acct *Account) (*Account, error) {
-	dir, err := c.Discover(ctx)
-	if err != nil {
+	if _, err := c.Discover(ctx); err != nil {
 		return nil, err
 	}
-	if dir.rfcCompliant() {
-		return c.updateRegRFC(ctx, acct)
-	}
-
-	// Legacy CA.
-	uri := acct.URI
-	a, err := c.doReg(ctx, uri, "reg", acct)
-	if err != nil {
-		return nil, err
-	}
-	a.URI = uri
-	return a, nil
+	return c.updateRegRFC(ctx, acct)
 }
 
 // Authorize performs the initial step in the pre-authorization flow,
@@ -505,17 +373,11 @@
 // If a caller needs to poll an authorization until its status is final,
 // see the WaitAuthorization method.
 func (c *Client) GetAuthorization(ctx context.Context, url string) (*Authorization, error) {
-	dir, err := c.Discover(ctx)
-	if err != nil {
+	if _, err := c.Discover(ctx); err != nil {
 		return nil, err
 	}
 
-	var res *http.Response
-	if dir.rfcCompliant() {
-		res, err = c.postAsGet(ctx, url, wantStatus(http.StatusOK))
-	} else {
-		res, err = c.get(ctx, url, wantStatus(http.StatusOK, http.StatusAccepted))
-	}
+	res, err := c.postAsGet(ctx, url, wantStatus(http.StatusOK))
 	if err != nil {
 		return nil, err
 	}
@@ -537,7 +399,6 @@
 //
 // It does not revoke existing certificates.
 func (c *Client) RevokeAuthorization(ctx context.Context, url string) error {
-	// Required for c.accountKID() when in RFC mode.
 	if _, err := c.Discover(ctx); err != nil {
 		return err
 	}
@@ -567,18 +428,11 @@
 // In all other cases WaitAuthorization returns an error.
 // If the Status is StatusInvalid, the returned error is of type *AuthorizationError.
 func (c *Client) WaitAuthorization(ctx context.Context, url string) (*Authorization, error) {
-	// Required for c.accountKID() when in RFC mode.
-	dir, err := c.Discover(ctx)
-	if err != nil {
+	if _, err := c.Discover(ctx); err != nil {
 		return nil, err
 	}
-	getfn := c.postAsGet
-	if !dir.rfcCompliant() {
-		getfn = c.get
-	}
-
 	for {
-		res, err := getfn(ctx, url, wantStatus(http.StatusOK, http.StatusAccepted))
+		res, err := c.postAsGet(ctx, url, wantStatus(http.StatusOK, http.StatusAccepted))
 		if err != nil {
 			return nil, err
 		}
@@ -621,17 +475,11 @@
 //
 // A client typically polls a challenge status using this method.
 func (c *Client) GetChallenge(ctx context.Context, url string) (*Challenge, error) {
-	// Required for c.accountKID() when in RFC mode.
-	dir, err := c.Discover(ctx)
-	if err != nil {
+	if _, err := c.Discover(ctx); err != nil {
 		return nil, err
 	}
 
-	getfn := c.postAsGet
-	if !dir.rfcCompliant() {
-		getfn = c.get
-	}
-	res, err := getfn(ctx, url, wantStatus(http.StatusOK, http.StatusAccepted))
+	res, err := c.postAsGet(ctx, url, wantStatus(http.StatusOK, http.StatusAccepted))
 	if err != nil {
 		return nil, err
 	}
@@ -649,29 +497,11 @@
 //
 // The server will then perform the validation asynchronously.
 func (c *Client) Accept(ctx context.Context, chal *Challenge) (*Challenge, error) {
-	// Required for c.accountKID() when in RFC mode.
-	dir, err := c.Discover(ctx)
-	if err != nil {
+	if _, err := c.Discover(ctx); err != nil {
 		return nil, err
 	}
 
-	var req interface{} = json.RawMessage("{}") // RFC-compliant CA
-	if !dir.rfcCompliant() {
-		auth, err := keyAuth(c.Key.Public(), chal.Token)
-		if err != nil {
-			return nil, err
-		}
-		req = struct {
-			Resource string `json:"resource"`
-			Type     string `json:"type"`
-			Auth     string `json:"keyAuthorization"`
-		}{
-			Resource: "challenge",
-			Type:     chal.Type,
-			Auth:     auth,
-		}
-	}
-	res, err := c.post(ctx, nil, chal.URI, req, wantStatus(
+	res, err := c.post(ctx, nil, chal.URI, json.RawMessage("{}"), wantStatus(
 		http.StatusOK,       // according to the spec
 		http.StatusAccepted, // Let's Encrypt: see https://goo.gl/WsJ7VT (acme-divergences.md)
 	))
@@ -722,7 +552,7 @@
 
 // TLSSNI01ChallengeCert creates a certificate for TLS-SNI-01 challenge response.
 //
-// Deprecated: This challenge type is unused in both draft-02 and RFC versions of ACME spec.
+// Deprecated: This challenge type is unused in both draft-02 and RFC versions of the ACME spec.
 func (c *Client) TLSSNI01ChallengeCert(token string, opt ...CertOption) (cert tls.Certificate, name string, err error) {
 	ka, err := keyAuth(c.Key.Public(), token)
 	if err != nil {
@@ -740,7 +570,7 @@
 
 // TLSSNI02ChallengeCert creates a certificate for TLS-SNI-02 challenge response.
 //
-// Deprecated: This challenge type is unused in both draft-02 and RFC versions of ACME spec.
+// Deprecated: This challenge type is unused in both draft-02 and RFC versions of the ACME spec.
 func (c *Client) TLSSNI02ChallengeCert(token string, opt ...CertOption) (cert tls.Certificate, name string, err error) {
 	b := sha256.Sum256([]byte(token))
 	h := hex.EncodeToString(b[:])
@@ -807,63 +637,6 @@
 	return tlsChallengeCert([]string{domain}, newOpt)
 }
 
-// doReg sends all types of registration requests the old way (pre-RFC world).
-// The type of request is identified by typ argument, which is a "resource"
-// in the ACME spec terms.
-//
-// A non-nil acct argument indicates whether the intention is to mutate data
-// of the Account. Only Contact and Agreement of its fields are used
-// in such cases.
-func (c *Client) doReg(ctx context.Context, url string, typ string, acct *Account) (*Account, error) {
-	req := struct {
-		Resource  string   `json:"resource"`
-		Contact   []string `json:"contact,omitempty"`
-		Agreement string   `json:"agreement,omitempty"`
-	}{
-		Resource: typ,
-	}
-	if acct != nil {
-		req.Contact = acct.Contact
-		req.Agreement = acct.AgreedTerms
-	}
-	res, err := c.post(ctx, nil, url, req, wantStatus(
-		http.StatusOK,       // updates and deletes
-		http.StatusCreated,  // new account creation
-		http.StatusAccepted, // Let's Encrypt divergent implementation
-	))
-	if err != nil {
-		return nil, err
-	}
-	defer res.Body.Close()
-
-	var v struct {
-		Contact        []string
-		Agreement      string
-		Authorizations string
-		Certificates   string
-	}
-	if err := json.NewDecoder(res.Body).Decode(&v); err != nil {
-		return nil, fmt.Errorf("acme: invalid response: %v", err)
-	}
-	var tos string
-	if v := linkHeader(res.Header, "terms-of-service"); len(v) > 0 {
-		tos = v[0]
-	}
-	var authz string
-	if v := linkHeader(res.Header, "next"); len(v) > 0 {
-		authz = v[0]
-	}
-	return &Account{
-		URI:            res.Header.Get("Location"),
-		Contact:        v.Contact,
-		AgreedTerms:    v.Agreement,
-		CurrentTerms:   tos,
-		Authz:          authz,
-		Authorizations: v.Authorizations,
-		Certificates:   v.Certificates,
-	}, nil
-}
-
 // popNonce returns a nonce value previously stored with c.addNonce
 // or fetches a fresh one from c.dir.NonceURL.
 // If NonceURL is empty, it first tries c.directoryURL() and, failing that,
@@ -938,78 +711,6 @@
 	return h.Get("Replay-Nonce")
 }
 
-func (c *Client) responseCert(ctx context.Context, res *http.Response, bundle bool) ([][]byte, error) {
-	b, err := ioutil.ReadAll(io.LimitReader(res.Body, maxCertSize+1))
-	if err != nil {
-		return nil, fmt.Errorf("acme: response stream: %v", err)
-	}
-	if len(b) > maxCertSize {
-		return nil, errors.New("acme: certificate is too big")
-	}
-	cert := [][]byte{b}
-	if !bundle {
-		return cert, nil
-	}
-
-	// Append CA chain cert(s).
-	// At least one is required according to the spec:
-	// https://tools.ietf.org/html/draft-ietf-acme-acme-03#section-6.3.1
-	up := linkHeader(res.Header, "up")
-	if len(up) == 0 {
-		return nil, errors.New("acme: rel=up link not found")
-	}
-	if len(up) > maxChainLen {
-		return nil, errors.New("acme: rel=up link is too large")
-	}
-	for _, url := range up {
-		cc, err := c.chainCert(ctx, url, 0)
-		if err != nil {
-			return nil, err
-		}
-		cert = append(cert, cc...)
-	}
-	return cert, nil
-}
-
-// chainCert fetches CA certificate chain recursively by following "up" links.
-// Each recursive call increments the depth by 1, resulting in an error
-// if the recursion level reaches maxChainLen.
-//
-// First chainCert call starts with depth of 0.
-func (c *Client) chainCert(ctx context.Context, url string, depth int) ([][]byte, error) {
-	if depth >= maxChainLen {
-		return nil, errors.New("acme: certificate chain is too deep")
-	}
-
-	res, err := c.get(ctx, url, wantStatus(http.StatusOK))
-	if err != nil {
-		return nil, err
-	}
-	defer res.Body.Close()
-	b, err := ioutil.ReadAll(io.LimitReader(res.Body, maxCertSize+1))
-	if err != nil {
-		return nil, err
-	}
-	if len(b) > maxCertSize {
-		return nil, errors.New("acme: certificate is too big")
-	}
-	chain := [][]byte{b}
-
-	uplink := linkHeader(res.Header, "up")
-	if len(uplink) > maxChainLen {
-		return nil, errors.New("acme: certificate chain is too large")
-	}
-	for _, up := range uplink {
-		cc, err := c.chainCert(ctx, up, depth+1)
-		if err != nil {
-			return nil, err
-		}
-		chain = append(chain, cc...)
-	}
-
-	return chain, nil
-}
-
 // linkHeader returns URI-Reference values of all Link headers
 // with relation-type rel.
 // See https://tools.ietf.org/html/rfc5988#section-5 for details.
@@ -1100,5 +801,5 @@
 	return pem.EncodeToMemory(pb)
 }
 
-// timeNow is useful for testing for fixed current time.
+// timeNow is time.Now, except in tests which can mess with it.
 var timeNow = time.Now
diff --git a/acme/acme_test.go b/acme/acme_test.go
index b46f70d..a748d88 100644
--- a/acme/acme_test.go
+++ b/acme/acme_test.go
@@ -79,115 +79,6 @@
 	return &head, nil
 }
 
-func TestDiscover(t *testing.T) {
-	const (
-		reg    = "https://example.com/acme/new-reg"
-		authz  = "https://example.com/acme/new-authz"
-		cert   = "https://example.com/acme/new-cert"
-		revoke = "https://example.com/acme/revoke-cert"
-	)
-	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
-		w.Header().Set("Content-Type", "application/json")
-		w.Header().Set("Replay-Nonce", "testnonce")
-		fmt.Fprintf(w, `{
-			"new-reg": %q,
-			"new-authz": %q,
-			"new-cert": %q,
-			"revoke-cert": %q
-		}`, reg, authz, cert, revoke)
-	}))
-	defer ts.Close()
-	c := Client{DirectoryURL: ts.URL}
-	dir, err := c.Discover(context.Background())
-	if err != nil {
-		t.Fatal(err)
-	}
-	if dir.RegURL != reg {
-		t.Errorf("dir.RegURL = %q; want %q", dir.RegURL, reg)
-	}
-	if dir.AuthzURL != authz {
-		t.Errorf("dir.AuthzURL = %q; want %q", dir.AuthzURL, authz)
-	}
-	if dir.CertURL != cert {
-		t.Errorf("dir.CertURL = %q; want %q", dir.CertURL, cert)
-	}
-	if dir.RevokeURL != revoke {
-		t.Errorf("dir.RevokeURL = %q; want %q", dir.RevokeURL, revoke)
-	}
-	if _, exist := c.nonces["testnonce"]; !exist {
-		t.Errorf("c.nonces = %q; want 'testnonce' in the map", c.nonces)
-	}
-}
-
-func TestRegister(t *testing.T) {
-	contacts := []string{"mailto:admin@example.com"}
-
-	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
-		if r.Method == "HEAD" {
-			w.Header().Set("Replay-Nonce", "test-nonce")
-			return
-		}
-		if r.Method != "POST" {
-			t.Errorf("r.Method = %q; want POST", r.Method)
-		}
-
-		var j struct {
-			Resource  string
-			Contact   []string
-			Agreement string
-		}
-		decodeJWSRequest(t, &j, r.Body)
-
-		// Test request
-		if j.Resource != "new-reg" {
-			t.Errorf("j.Resource = %q; want new-reg", j.Resource)
-		}
-		if !reflect.DeepEqual(j.Contact, contacts) {
-			t.Errorf("j.Contact = %v; want %v", j.Contact, contacts)
-		}
-
-		w.Header().Set("Location", "https://ca.tld/acme/reg/1")
-		w.Header().Set("Link", `<https://ca.tld/acme/new-authz>;rel="next"`)
-		w.Header().Add("Link", `<https://ca.tld/acme/recover-reg>;rel="recover"`)
-		w.Header().Add("Link", `<https://ca.tld/acme/terms>;rel="terms-of-service"`)
-		w.WriteHeader(http.StatusCreated)
-		b, _ := json.Marshal(contacts)
-		fmt.Fprintf(w, `{"contact": %s}`, b)
-	}))
-	defer ts.Close()
-
-	prompt := func(url string) bool {
-		const terms = "https://ca.tld/acme/terms"
-		if url != terms {
-			t.Errorf("prompt url = %q; want %q", url, terms)
-		}
-		return false
-	}
-
-	c := Client{
-		Key:          testKeyEC,
-		DirectoryURL: ts.URL,
-		dir:          &Directory{RegURL: ts.URL},
-	}
-	a := &Account{Contact: contacts}
-	var err error
-	if a, err = c.Register(context.Background(), a, prompt); err != nil {
-		t.Fatal(err)
-	}
-	if a.URI != "https://ca.tld/acme/reg/1" {
-		t.Errorf("a.URI = %q; want https://ca.tld/acme/reg/1", a.URI)
-	}
-	if a.Authz != "https://ca.tld/acme/new-authz" {
-		t.Errorf("a.Authz = %q; want https://ca.tld/acme/new-authz", a.Authz)
-	}
-	if a.CurrentTerms != "https://ca.tld/acme/terms" {
-		t.Errorf("a.CurrentTerms = %q; want https://ca.tld/acme/terms", a.CurrentTerms)
-	}
-	if !reflect.DeepEqual(a.Contact, contacts) {
-		t.Errorf("a.Contact = %v; want %v", a.Contact, contacts)
-	}
-}
-
 func TestRegisterWithoutKey(t *testing.T) {
 	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
 		if r.Method == "HEAD" {
@@ -213,134 +104,6 @@
 	}
 }
 
-func TestUpdateReg(t *testing.T) {
-	const terms = "https://ca.tld/acme/terms"
-	contacts := []string{"mailto:admin@example.com"}
-
-	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
-		if r.Method == "HEAD" {
-			w.Header().Set("Replay-Nonce", "test-nonce")
-			return
-		}
-		if r.Method != "POST" {
-			t.Errorf("r.Method = %q; want POST", r.Method)
-		}
-
-		var j struct {
-			Resource  string
-			Contact   []string
-			Agreement string
-		}
-		decodeJWSRequest(t, &j, r.Body)
-
-		// Test request
-		if j.Resource != "reg" {
-			t.Errorf("j.Resource = %q; want reg", j.Resource)
-		}
-		if j.Agreement != terms {
-			t.Errorf("j.Agreement = %q; want %q", j.Agreement, terms)
-		}
-		if !reflect.DeepEqual(j.Contact, contacts) {
-			t.Errorf("j.Contact = %v; want %v", j.Contact, contacts)
-		}
-
-		w.Header().Set("Link", `<https://ca.tld/acme/new-authz>;rel="next"`)
-		w.Header().Add("Link", `<https://ca.tld/acme/recover-reg>;rel="recover"`)
-		w.Header().Add("Link", fmt.Sprintf(`<%s>;rel="terms-of-service"`, terms))
-		w.WriteHeader(http.StatusOK)
-		b, _ := json.Marshal(contacts)
-		fmt.Fprintf(w, `{"contact":%s, "agreement":%q}`, b, terms)
-	}))
-	defer ts.Close()
-
-	c := Client{
-		Key:          testKeyEC,
-		DirectoryURL: ts.URL,       // don't dial outside of localhost
-		dir:          &Directory{}, // don't do discovery
-	}
-	a := &Account{URI: ts.URL, Contact: contacts, AgreedTerms: terms}
-	var err error
-	if a, err = c.UpdateReg(context.Background(), a); err != nil {
-		t.Fatal(err)
-	}
-	if a.Authz != "https://ca.tld/acme/new-authz" {
-		t.Errorf("a.Authz = %q; want https://ca.tld/acme/new-authz", a.Authz)
-	}
-	if a.AgreedTerms != terms {
-		t.Errorf("a.AgreedTerms = %q; want %q", a.AgreedTerms, terms)
-	}
-	if a.CurrentTerms != terms {
-		t.Errorf("a.CurrentTerms = %q; want %q", a.CurrentTerms, terms)
-	}
-	if a.URI != ts.URL {
-		t.Errorf("a.URI = %q; want %q", a.URI, ts.URL)
-	}
-}
-
-func TestGetReg(t *testing.T) {
-	const terms = "https://ca.tld/acme/terms"
-	const newTerms = "https://ca.tld/acme/new-terms"
-	contacts := []string{"mailto:admin@example.com"}
-
-	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
-		if r.Method == "HEAD" {
-			w.Header().Set("Replay-Nonce", "test-nonce")
-			return
-		}
-		if r.Method != "POST" {
-			t.Errorf("r.Method = %q; want POST", r.Method)
-		}
-
-		var j struct {
-			Resource  string
-			Contact   []string
-			Agreement string
-		}
-		decodeJWSRequest(t, &j, r.Body)
-
-		// Test request
-		if j.Resource != "reg" {
-			t.Errorf("j.Resource = %q; want reg", j.Resource)
-		}
-		if len(j.Contact) != 0 {
-			t.Errorf("j.Contact = %v", j.Contact)
-		}
-		if j.Agreement != "" {
-			t.Errorf("j.Agreement = %q", j.Agreement)
-		}
-
-		w.Header().Set("Link", `<https://ca.tld/acme/new-authz>;rel="next"`)
-		w.Header().Add("Link", `<https://ca.tld/acme/recover-reg>;rel="recover"`)
-		w.Header().Add("Link", fmt.Sprintf(`<%s>;rel="terms-of-service"`, newTerms))
-		w.WriteHeader(http.StatusOK)
-		b, _ := json.Marshal(contacts)
-		fmt.Fprintf(w, `{"contact":%s, "agreement":%q}`, b, terms)
-	}))
-	defer ts.Close()
-
-	c := Client{
-		Key:          testKeyEC,
-		DirectoryURL: ts.URL,       // don't dial outside of localhost
-		dir:          &Directory{}, // don't do discovery
-	}
-	a, err := c.GetReg(context.Background(), ts.URL)
-	if err != nil {
-		t.Fatal(err)
-	}
-	if a.Authz != "https://ca.tld/acme/new-authz" {
-		t.Errorf("a.AuthzURL = %q; want https://ca.tld/acme/new-authz", a.Authz)
-	}
-	if a.AgreedTerms != terms {
-		t.Errorf("a.AgreedTerms = %q; want %q", a.AgreedTerms, terms)
-	}
-	if a.CurrentTerms != newTerms {
-		t.Errorf("a.CurrentTerms = %q; want %q", a.CurrentTerms, newTerms)
-	}
-	if a.URI != ts.URL {
-		t.Errorf("a.URI = %q; want %q", a.URI, ts.URL)
-	}
-}
-
 func TestAuthorize(t *testing.T) {
 	tt := []struct{ typ, value string }{
 		{"dns", "example.com"},
@@ -491,82 +254,6 @@
 	}
 }
 
-func TestGetAuthorization(t *testing.T) {
-	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
-		if r.Method != "GET" {
-			t.Errorf("r.Method = %q; want GET", r.Method)
-		}
-
-		w.WriteHeader(http.StatusOK)
-		fmt.Fprintf(w, `{
-			"identifier": {"type":"dns","value":"example.com"},
-			"status":"pending",
-			"challenges":[
-				{
-					"type":"http-01",
-					"status":"pending",
-					"uri":"https://ca.tld/acme/challenge/publickey/id1",
-					"token":"token1"
-				},
-				{
-					"type":"tls-sni-01",
-					"status":"pending",
-					"uri":"https://ca.tld/acme/challenge/publickey/id2",
-					"token":"token2"
-				}
-			],
-			"combinations":[[0],[1]]}`)
-	}))
-	defer ts.Close()
-
-	cl := Client{Key: testKeyEC, DirectoryURL: ts.URL}
-	auth, err := cl.GetAuthorization(context.Background(), ts.URL)
-	if err != nil {
-		t.Fatal(err)
-	}
-
-	if auth.Status != "pending" {
-		t.Errorf("Status = %q; want pending", auth.Status)
-	}
-	if auth.Identifier.Type != "dns" {
-		t.Errorf("Identifier.Type = %q; want dns", auth.Identifier.Type)
-	}
-	if auth.Identifier.Value != "example.com" {
-		t.Errorf("Identifier.Value = %q; want example.com", auth.Identifier.Value)
-	}
-
-	if n := len(auth.Challenges); n != 2 {
-		t.Fatalf("len(set.Challenges) = %d; want 2", n)
-	}
-
-	c := auth.Challenges[0]
-	if c.Type != "http-01" {
-		t.Errorf("c.Type = %q; want http-01", c.Type)
-	}
-	if c.URI != "https://ca.tld/acme/challenge/publickey/id1" {
-		t.Errorf("c.URI = %q; want https://ca.tld/acme/challenge/publickey/id1", c.URI)
-	}
-	if c.Token != "token1" {
-		t.Errorf("c.Token = %q; want token1", c.Token)
-	}
-
-	c = auth.Challenges[1]
-	if c.Type != "tls-sni-01" {
-		t.Errorf("c.Type = %q; want tls-sni-01", c.Type)
-	}
-	if c.URI != "https://ca.tld/acme/challenge/publickey/id2" {
-		t.Errorf("c.URI = %q; want https://ca.tld/acme/challenge/publickey/id2", c.URI)
-	}
-	if c.Token != "token2" {
-		t.Errorf("c.Token = %q; want token2", c.Token)
-	}
-
-	combs := [][]int{{0}, {1}}
-	if !reflect.DeepEqual(auth.Combinations, combs) {
-		t.Errorf("auth.Combinations: %+v\nwant: %+v\n", auth.Combinations, combs)
-	}
-}
-
 func TestWaitAuthorization(t *testing.T) {
 	t.Run("wait loop", func(t *testing.T) {
 		var count int
@@ -678,9 +365,13 @@
 		}
 	})
 }
+
 func runWaitAuthorization(ctx context.Context, t *testing.T, h http.HandlerFunc) (*Authorization, error) {
 	t.Helper()
-	ts := httptest.NewServer(h)
+	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+		w.Header().Set("Replay-Nonce", fmt.Sprintf("bad-test-nonce-%v", time.Now().UnixNano()))
+		h(w, r)
+	}))
 	defer ts.Close()
 	type res struct {
 		authz *Authorization
@@ -688,7 +379,12 @@
 	}
 	ch := make(chan res, 1)
 	go func() {
-		var client = Client{DirectoryURL: ts.URL}
+		client := &Client{
+			Key:          testKey,
+			DirectoryURL: ts.URL,
+			dir:          &Directory{},
+			KID:          "some-key-id", // set to avoid lookup attempt
+		}
 		a, err := client.WaitAuthorization(ctx, ts.URL)
 		ch <- res{a, err}
 	}()
@@ -743,236 +439,6 @@
 	}
 }
 
-func TestPollChallenge(t *testing.T) {
-	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
-		if r.Method != "GET" {
-			t.Errorf("r.Method = %q; want GET", r.Method)
-		}
-
-		w.WriteHeader(http.StatusOK)
-		fmt.Fprintf(w, `{
-			"type":"http-01",
-			"status":"pending",
-			"uri":"https://ca.tld/acme/challenge/publickey/id1",
-			"token":"token1"}`)
-	}))
-	defer ts.Close()
-
-	cl := Client{Key: testKeyEC, DirectoryURL: ts.URL}
-	chall, err := cl.GetChallenge(context.Background(), ts.URL)
-	if err != nil {
-		t.Fatal(err)
-	}
-
-	if chall.Status != "pending" {
-		t.Errorf("Status = %q; want pending", chall.Status)
-	}
-	if chall.Type != "http-01" {
-		t.Errorf("c.Type = %q; want http-01", chall.Type)
-	}
-	if chall.URI != "https://ca.tld/acme/challenge/publickey/id1" {
-		t.Errorf("c.URI = %q; want https://ca.tld/acme/challenge/publickey/id1", chall.URI)
-	}
-	if chall.Token != "token1" {
-		t.Errorf("c.Token = %q; want token1", chall.Token)
-	}
-}
-
-func TestAcceptChallenge(t *testing.T) {
-	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
-		if r.Method == "HEAD" {
-			w.Header().Set("Replay-Nonce", "test-nonce")
-			return
-		}
-		if r.Method != "POST" {
-			t.Errorf("r.Method = %q; want POST", r.Method)
-		}
-
-		var j struct {
-			Resource string
-			Type     string
-			Auth     string `json:"keyAuthorization"`
-		}
-		decodeJWSRequest(t, &j, r.Body)
-
-		// Test request
-		if j.Resource != "challenge" {
-			t.Errorf(`resource = %q; want "challenge"`, j.Resource)
-		}
-		if j.Type != "http-01" {
-			t.Errorf(`type = %q; want "http-01"`, j.Type)
-		}
-		keyAuth := "token1." + testKeyECThumbprint
-		if j.Auth != keyAuth {
-			t.Errorf(`keyAuthorization = %q; want %q`, j.Auth, keyAuth)
-		}
-
-		// Respond to request
-		w.WriteHeader(http.StatusAccepted)
-		fmt.Fprintf(w, `{
-			"type":"http-01",
-			"status":"pending",
-			"uri":"https://ca.tld/acme/challenge/publickey/id1",
-			"token":"token1",
-			"keyAuthorization":%q
-		}`, keyAuth)
-	}))
-	defer ts.Close()
-
-	cl := Client{
-		Key:          testKeyEC,
-		DirectoryURL: ts.URL,       // don't dial outside of localhost
-		dir:          &Directory{}, // don't do discovery
-	}
-	c, err := cl.Accept(context.Background(), &Challenge{
-		URI:   ts.URL,
-		Token: "token1",
-		Type:  "http-01",
-	})
-	if err != nil {
-		t.Fatal(err)
-	}
-
-	if c.Type != "http-01" {
-		t.Errorf("c.Type = %q; want http-01", c.Type)
-	}
-	if c.URI != "https://ca.tld/acme/challenge/publickey/id1" {
-		t.Errorf("c.URI = %q; want https://ca.tld/acme/challenge/publickey/id1", c.URI)
-	}
-	if c.Token != "token1" {
-		t.Errorf("c.Token = %q; want token1", c.Token)
-	}
-}
-
-func TestNewCert(t *testing.T) {
-	notBefore := time.Now()
-	notAfter := notBefore.AddDate(0, 2, 0)
-	timeNow = func() time.Time { return notBefore }
-
-	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
-		if r.Method == "HEAD" {
-			w.Header().Set("Replay-Nonce", "test-nonce")
-			return
-		}
-		if r.Method != "POST" {
-			t.Errorf("r.Method = %q; want POST", r.Method)
-		}
-
-		var j struct {
-			Resource  string `json:"resource"`
-			CSR       string `json:"csr"`
-			NotBefore string `json:"notBefore,omitempty"`
-			NotAfter  string `json:"notAfter,omitempty"`
-		}
-		decodeJWSRequest(t, &j, r.Body)
-
-		// Test request
-		if j.Resource != "new-cert" {
-			t.Errorf(`resource = %q; want "new-cert"`, j.Resource)
-		}
-		if j.NotBefore != notBefore.Format(time.RFC3339) {
-			t.Errorf(`notBefore = %q; wanted %q`, j.NotBefore, notBefore.Format(time.RFC3339))
-		}
-		if j.NotAfter != notAfter.Format(time.RFC3339) {
-			t.Errorf(`notAfter = %q; wanted %q`, j.NotAfter, notAfter.Format(time.RFC3339))
-		}
-
-		// Respond to request
-		template := x509.Certificate{
-			SerialNumber: big.NewInt(int64(1)),
-			Subject: pkix.Name{
-				Organization: []string{"goacme"},
-			},
-			NotBefore: notBefore,
-			NotAfter:  notAfter,
-
-			KeyUsage:              x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
-			ExtKeyUsage:           []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
-			BasicConstraintsValid: true,
-		}
-
-		sampleCert, err := x509.CreateCertificate(rand.Reader, &template, &template, &testKeyEC.PublicKey, testKeyEC)
-		if err != nil {
-			t.Fatalf("Error creating certificate: %v", err)
-		}
-
-		w.Header().Set("Location", "https://ca.tld/acme/cert/1")
-		w.WriteHeader(http.StatusCreated)
-		w.Write(sampleCert)
-	}))
-	defer ts.Close()
-
-	csr := x509.CertificateRequest{
-		Version: 0,
-		Subject: pkix.Name{
-			CommonName:   "example.com",
-			Organization: []string{"goacme"},
-		},
-	}
-	csrb, err := x509.CreateCertificateRequest(rand.Reader, &csr, testKeyEC)
-	if err != nil {
-		t.Fatal(err)
-	}
-
-	c := Client{Key: testKeyEC, dir: &Directory{CertURL: ts.URL}}
-	cert, certURL, err := c.CreateCert(context.Background(), csrb, notAfter.Sub(notBefore), false)
-	if err != nil {
-		t.Fatal(err)
-	}
-	if cert == nil {
-		t.Errorf("cert is nil")
-	}
-	if certURL != "https://ca.tld/acme/cert/1" {
-		t.Errorf("certURL = %q; want https://ca.tld/acme/cert/1", certURL)
-	}
-}
-
-func TestFetchCert(t *testing.T) {
-	var count byte
-	var ts *httptest.Server
-	ts = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
-		count++
-		if count < 3 {
-			up := fmt.Sprintf("<%s>;rel=up", ts.URL)
-			w.Header().Set("Link", up)
-		}
-		w.Write([]byte{count})
-	}))
-	defer ts.Close()
-	cl := newTestClient()
-	res, err := cl.FetchCert(context.Background(), ts.URL, true)
-	if err != nil {
-		t.Fatalf("FetchCert: %v", err)
-	}
-	cert := [][]byte{{1}, {2}, {3}}
-	if !reflect.DeepEqual(res, cert) {
-		t.Errorf("res = %v; want %v", res, cert)
-	}
-}
-
-func TestFetchCertRetry(t *testing.T) {
-	var count int
-	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
-		if count < 1 {
-			w.Header().Set("Retry-After", "0")
-			w.WriteHeader(http.StatusTooManyRequests)
-			count++
-			return
-		}
-		w.Write([]byte{1})
-	}))
-	defer ts.Close()
-	cl := newTestClient()
-	res, err := cl.FetchCert(context.Background(), ts.URL, false)
-	if err != nil {
-		t.Fatalf("FetchCert: %v", err)
-	}
-	cert := [][]byte{{1}}
-	if !reflect.DeepEqual(res, cert) {
-		t.Errorf("res = %v; want %v", res, cert)
-	}
-}
-
 func TestFetchCertCancel(t *testing.T) {
 	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
 		<-r.Context().Done()
@@ -1044,42 +510,6 @@
 	}
 }
 
-func TestRevokeCert(t *testing.T) {
-	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
-		if r.Method == "HEAD" {
-			w.Header().Set("Replay-Nonce", "nonce")
-			return
-		}
-
-		var req struct {
-			Resource    string
-			Certificate string
-			Reason      int
-		}
-		decodeJWSRequest(t, &req, r.Body)
-		if req.Resource != "revoke-cert" {
-			t.Errorf("req.Resource = %q; want revoke-cert", req.Resource)
-		}
-		if req.Reason != 1 {
-			t.Errorf("req.Reason = %d; want 1", req.Reason)
-		}
-		// echo -n cert | base64 | tr -d '=' | tr '/+' '_-'
-		cert := "Y2VydA"
-		if req.Certificate != cert {
-			t.Errorf("req.Certificate = %q; want %q", req.Certificate, cert)
-		}
-	}))
-	defer ts.Close()
-	client := &Client{
-		Key: testKeyEC,
-		dir: &Directory{RevokeURL: ts.URL},
-	}
-	ctx := context.Background()
-	if err := client.RevokeCert(ctx, nil, []byte("cert"), CRLReasonKeyCompromise); err != nil {
-		t.Fatal(err)
-	}
-}
-
 func TestNonce_add(t *testing.T) {
 	var c Client
 	c.addNonce(http.Header{"Replay-Nonce": {"nonce"}})
@@ -1200,65 +630,6 @@
 	}
 }
 
-func TestNonce_postJWS(t *testing.T) {
-	var count int
-	seen := make(map[string]bool)
-	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
-		count++
-		w.Header().Set("Replay-Nonce", fmt.Sprintf("nonce%d", count))
-		if r.Method == "HEAD" {
-			// We expect the client do a HEAD request
-			// but only to fetch the first nonce.
-			return
-		}
-		// Make client.Authorize happy; we're not testing its result.
-		defer func() {
-			w.WriteHeader(http.StatusCreated)
-			w.Write([]byte(`{"status":"valid"}`))
-		}()
-
-		head, err := decodeJWSHead(r.Body)
-		if err != nil {
-			t.Errorf("decodeJWSHead: %v", err)
-			return
-		}
-		if head.Nonce == "" {
-			t.Error("head.Nonce is empty")
-			return
-		}
-		if seen[head.Nonce] {
-			t.Errorf("nonce is already used: %q", head.Nonce)
-		}
-		seen[head.Nonce] = true
-	}))
-	defer ts.Close()
-
-	client := Client{
-		Key:          testKey,
-		DirectoryURL: ts.URL, // nonces are fetched from here first
-		dir:          &Directory{AuthzURL: ts.URL},
-	}
-	if _, err := client.Authorize(context.Background(), "example.com"); err != nil {
-		t.Errorf("client.Authorize 1: %v", err)
-	}
-	// The second call should not generate another extra HEAD request.
-	if _, err := client.Authorize(context.Background(), "example.com"); err != nil {
-		t.Errorf("client.Authorize 2: %v", err)
-	}
-
-	if count != 3 {
-		t.Errorf("total requests count: %d; want 3", count)
-	}
-	if n := len(client.nonces); n != 1 {
-		t.Errorf("len(client.nonces) = %d; want 1", n)
-	}
-	for k := range seen {
-		if _, exist := client.nonces[k]; exist {
-			t.Errorf("used nonce %q in client.nonces", k)
-		}
-	}
-}
-
 func TestLinkHeader(t *testing.T) {
 	h := http.Header{"Link": {
 		`<https://example.com/acme/new-authz>;rel="next"`,
diff --git a/acme/autocert/autocert.go b/acme/autocert/autocert.go
index 37923f4..ca558e7 100644
--- a/acme/autocert/autocert.go
+++ b/acme/autocert/autocert.go
@@ -47,6 +47,8 @@
 // pseudoRand is safe for concurrent use.
 var pseudoRand *lockedMathRand
 
+var errPreRFC = errors.New("autocert: ACME server doesn't support RFC 8555")
+
 func init() {
 	src := mathrand.NewSource(time.Now().UnixNano())
 	pseudoRand = &lockedMathRand{rnd: mathrand.New(src)}
@@ -658,31 +660,19 @@
 	if err != nil {
 		return nil, nil, err
 	}
-
-	var chain [][]byte
-	switch {
-	// Pre-RFC legacy CA.
-	case dir.OrderURL == "":
-		if err := m.verify(ctx, client, ck.domain); err != nil {
-			return nil, nil, err
-		}
-		der, _, err := client.CreateCert(ctx, csr, 0, true)
-		if err != nil {
-			return nil, nil, err
-		}
-		chain = der
-	// RFC 8555 compliant CA.
-	default:
-		o, err := m.verifyRFC(ctx, client, ck.domain)
-		if err != nil {
-			return nil, nil, err
-		}
-		der, _, err := client.CreateOrderCert(ctx, o.FinalizeURL, csr, true)
-		if err != nil {
-			return nil, nil, err
-		}
-		chain = der
+	if dir.OrderURL == "" {
+		return nil, nil, errPreRFC
 	}
+
+	o, err := m.verifyRFC(ctx, client, ck.domain)
+	if err != nil {
+		return nil, nil, err
+	}
+	chain, _, err := client.CreateOrderCert(ctx, o.FinalizeURL, csr, true)
+	if err != nil {
+		return nil, nil, err
+	}
+
 	leaf, err = validCert(ck, chain, key, m.now())
 	if err != nil {
 		return nil, nil, err
@@ -690,69 +680,6 @@
 	return chain, leaf, nil
 }
 
-// verify runs the identifier (domain) pre-authorization flow for legacy CAs
-// using each applicable ACME challenge type.
-func (m *Manager) verify(ctx context.Context, client *acme.Client, domain string) error {
-	// Remove all hanging authorizations to reduce rate limit quotas
-	// after we're done.
-	var authzURLs []string
-	defer func() {
-		go m.deactivatePendingAuthz(authzURLs)
-	}()
-
-	// errs accumulates challenge failure errors, printed if all fail
-	errs := make(map[*acme.Challenge]error)
-	challengeTypes := m.supportedChallengeTypes()
-	var nextTyp int // challengeType index of the next challenge type to try
-	for {
-		// Start domain authorization and get the challenge.
-		authz, err := client.Authorize(ctx, domain)
-		if err != nil {
-			return err
-		}
-		authzURLs = append(authzURLs, authz.URI)
-		// No point in accepting challenges if the authorization status
-		// is in a final state.
-		switch authz.Status {
-		case acme.StatusValid:
-			return nil // already authorized
-		case acme.StatusInvalid:
-			return fmt.Errorf("acme/autocert: invalid authorization %q", authz.URI)
-		}
-
-		// Pick the next preferred challenge.
-		var chal *acme.Challenge
-		for chal == nil && nextTyp < len(challengeTypes) {
-			chal = pickChallenge(challengeTypes[nextTyp], authz.Challenges)
-			nextTyp++
-		}
-		if chal == nil {
-			errorMsg := fmt.Sprintf("acme/autocert: unable to authorize %q", domain)
-			for chal, err := range errs {
-				errorMsg += fmt.Sprintf("; challenge %q failed with error: %v", chal.Type, err)
-			}
-			return errors.New(errorMsg)
-		}
-		cleanup, err := m.fulfill(ctx, client, chal, domain)
-		if err != nil {
-			errs[chal] = err
-			continue
-		}
-		defer cleanup()
-		if _, err := client.Accept(ctx, chal); err != nil {
-			errs[chal] = err
-			continue
-		}
-
-		// A challenge is fulfilled and accepted: wait for the CA to validate.
-		if _, err := client.WaitAuthorization(ctx, authz.URI); err != nil {
-			errs[chal] = err
-			continue
-		}
-		return nil
-	}
-}
-
 // verifyRFC runs the identifier (domain) order-based authorization flow for RFC compliant CAs
 // using each applicable ACME challenge type.
 func (m *Manager) verifyRFC(ctx context.Context, client *acme.Client, domain string) (*acme.Order, error) {
diff --git a/acme/autocert/internal/acmetest/ca.go b/acme/autocert/internal/acmetest/ca.go
index bc0984f..8c4c642 100644
--- a/acme/autocert/internal/acmetest/ca.go
+++ b/acme/autocert/internal/acmetest/ca.go
@@ -220,10 +220,10 @@
 }
 
 type discovery struct {
-	NewNonce string `json:"newNonce"`
-	NewReg   string `json:"newAccount"`
-	NewOrder string `json:"newOrder"`
-	NewAuthz string `json:"newAuthz"`
+	NewNonce   string `json:"newNonce"`
+	NewAccount string `json:"newAccount"`
+	NewOrder   string `json:"newOrder"`
+	NewAuthz   string `json:"newAuthz"`
 }
 
 type challenge struct {
@@ -261,9 +261,9 @@
 	// Discovery request.
 	case r.URL.Path == "/":
 		resp := &discovery{
-			NewNonce: ca.serverURL("/new-nonce"),
-			NewReg:   ca.serverURL("/new-reg"),
-			NewOrder: ca.serverURL("/new-order"),
+			NewNonce:   ca.serverURL("/new-nonce"),
+			NewAccount: ca.serverURL("/new-account"),
+			NewOrder:   ca.serverURL("/new-order"),
 		}
 		if err := json.NewEncoder(w).Encode(resp); err != nil {
 			panic(fmt.Sprintf("discovery response: %v", err))
@@ -275,7 +275,7 @@
 		return
 
 	// Client key registration request.
-	case r.URL.Path == "/new-reg":
+	case r.URL.Path == "/new-account":
 		ca.mu.Lock()
 		defer ca.mu.Unlock()
 		if ca.acctRegistered {
@@ -365,6 +365,7 @@
 			// 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)
+			ca.updatePendingOrders()
 		}
 		if err := json.NewEncoder(w).Encode(authz); err != nil {
 			panic(fmt.Sprintf("encoding authz %d: %v", authz.id, err))
@@ -440,6 +441,8 @@
 	if idx > len(ca.orders)-1 {
 		return nil, fmt.Errorf("storedOrder: no such order %d", idx)
 	}
+
+	ca.updatePendingOrders()
 	return ca.orders[idx], nil
 }
 
@@ -568,30 +571,25 @@
 	}
 	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)
+
+	ca.updatePendingOrders()
+}
+
+func (ca *CAServer) updatePendingOrders() {
 	// Update all pending orders.
 	// An order becomes "ready" if all authorizations are "valid".
 	// An order becomes "invalid" if any authorization is "invalid".
 	// Status changes: https://tools.ietf.org/html/rfc8555#section-7.1.6
-OrdersLoop:
 	for i, o := range ca.orders {
 		if o.Status != acme.StatusPending {
 			continue
 		}
-		var countValid int
-		for _, zurl := range o.AuthzURLs {
-			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
-				ca.t.Logf("order %d is now invalid", i)
-				continue OrdersLoop
-			}
-			if z.Status == acme.StatusValid {
-				countValid++
-			}
+
+		countValid, countInvalid := ca.validateAuthzURLs(o.AuthzURLs, i)
+		if countInvalid > 0 {
+			o.Status = acme.StatusInvalid
+			ca.t.Logf("order %d is now invalid", i)
+			continue
 		}
 		if countValid == len(o.AuthzURLs) {
 			o.Status = acme.StatusReady
@@ -601,6 +599,23 @@
 	}
 }
 
+func (ca *CAServer) validateAuthzURLs(urls []string, orderNum int) (countValid, countInvalid int) {
+	for _, zurl := range urls {
+		z, err := ca.storedAuthz(path.Base(zurl))
+		if err != nil {
+			ca.t.Logf("no authz %q for order %d", zurl, orderNum)
+			continue
+		}
+		if z.Status == acme.StatusInvalid {
+			countInvalid++
+		}
+		if z.Status == acme.StatusValid {
+			countValid++
+		}
+	}
+	return countValid, countInvalid
+}
+
 func (ca *CAServer) verifyALPNChallenge(a *authorization) error {
 	const acmeALPNProto = "acme-tls/1"
 
diff --git a/acme/http_test.go b/acme/http_test.go
index cf1df36..f35e04a 100644
--- a/acme/http_test.go
+++ b/acme/http_test.go
@@ -115,8 +115,8 @@
 	if _, err := client.Authorize(context.Background(), "example.com"); err != nil {
 		t.Errorf("client.Authorize 1: %v", err)
 	}
-	if count != 4 {
-		t.Errorf("total requests count: %d; want 4", count)
+	if count != 3 {
+		t.Errorf("total requests count: %d; want 3", count)
 	}
 }
 
@@ -224,7 +224,7 @@
 			}
 
 			w.WriteHeader(http.StatusOK)
-			w.Write([]byte(`{}`))
+			w.Write([]byte(`{"newOrder": "sure"}`))
 		}))
 		defer ts.Close()
 
diff --git a/acme/internal/acmeprobe/prober.go b/acme/internal/acmeprobe/prober.go
index 55d702b..471707d 100644
--- a/acme/internal/acmeprobe/prober.go
+++ b/acme/internal/acmeprobe/prober.go
@@ -50,14 +50,13 @@
 
 var (
 	// ACME CA directory URL.
-	// Let's Encrypt v1 prod: https://acme-v01.api.letsencrypt.org/directory
 	// Let's Encrypt v2 prod: https://acme-v02.api.letsencrypt.org/directory
 	// Let's Encrypt v2 staging: https://acme-staging-v02.api.letsencrypt.org/directory
 	// See the following for more CAs implementing ACME protocol:
 	// https://en.wikipedia.org/wiki/Automated_Certificate_Management_Environment#CAs_&_PKIs_that_offer_ACME_certificates
 	directory = flag.String("d", "", "ACME directory URL.")
 	reginfo   = flag.String("r", "", "ACME account registration info.")
-	flow      = flag.String("f", "", "Flow to run: order, preauthz (RFC8555) or preauthz02 (draft-02).")
+	flow      = flag.String("f", "", `Flow to run: "order" or "preauthz" (RFC8555).`)
 	chaltyp   = flag.String("t", "", "Challenge type: tls-alpn-01, http-01 or dns-01.")
 	addr      = flag.String("a", "", "Local server address for tls-alpn-01 and http-01.")
 	dnsscript = flag.String("s", "", "Script to run for provisioning dns-01 challenges.")
@@ -127,8 +126,6 @@
 		p.runOrder(ctx, identifiers)
 	case "preauthz":
 		p.runPreauthz(ctx, identifiers)
-	case "preauthz02":
-		p.runPreauthzLegacy(ctx, identifiers)
 	default:
 		log.Fatalf("unknown flow: %q", *flow)
 	}
@@ -276,50 +273,6 @@
 	}
 }
 
-func (p *prober) runPreauthzLegacy(ctx context.Context, identifiers []acme.AuthzID) {
-	var zurls []string
-	for _, id := range identifiers {
-		z, err := authorize(ctx, p.client, id)
-		if err != nil {
-			log.Fatalf("AuthorizeID(%+v): %v", id, err)
-		}
-		if z.Status == acme.StatusValid {
-			log.Printf("authz %s is valid; skipping", z.URI)
-			continue
-		}
-		if err := p.fulfill(ctx, z); err != nil {
-			log.Fatalf("fulfill(%s): %v", z.URI, err)
-		}
-		zurls = append(zurls, z.URI)
-		log.Printf("authorized for %+v", id)
-	}
-
-	// We should be all set now.
-	log.Print("all authorizations are done")
-	csr, certkey := newCSR(identifiers)
-	der, curl, err := p.client.CreateCert(ctx, csr, 48*time.Hour, true)
-	if err != nil {
-		log.Fatalf("CreateCert: %v", err)
-	}
-	log.Printf("cert URL: %s", curl)
-	if err := checkCert(der, identifiers); err != nil {
-		p.errorf("invalid cert: %v", err)
-	}
-
-	// Deactivate all authorizations we satisfied earlier.
-	for _, v := range zurls {
-		if err := p.client.RevokeAuthorization(ctx, v); err != nil {
-			p.errorf("RevokAuthorization(%q): %v", v, err)
-			continue
-		}
-	}
-	// Try revoking the issued cert using its private key.
-	if err := p.client.RevokeCert(ctx, certkey, der[0], acme.CRLReasonCessationOfOperation); err != nil {
-		p.errorf("RevokeCert: %v", err)
-	}
-
-}
-
 func (p *prober) fulfill(ctx context.Context, z *acme.Authorization) error {
 	var chal *acme.Challenge
 	for i, c := range z.Challenges {
diff --git a/acme/jws.go b/acme/jws.go
index 8a097da..403e5b0 100644
--- a/acme/jws.go
+++ b/acme/jws.go
@@ -51,6 +51,9 @@
 //
 // See https://tools.ietf.org/html/rfc7515#section-7.
 func jwsEncodeJSON(claimset interface{}, key crypto.Signer, kid KeyID, nonce, url string) ([]byte, error) {
+	if key == nil {
+		return nil, errors.New("nil key")
+	}
 	alg, sha := jwsHasher(key.Public())
 	if alg == "" || !sha.Available() {
 		return nil, ErrUnsupportedKey
diff --git a/acme/rfc8555_test.go b/acme/rfc8555_test.go
index 4882759..6762f2a 100644
--- a/acme/rfc8555_test.go
+++ b/acme/rfc8555_test.go
@@ -62,7 +62,7 @@
 		}`, nonce, reg, order, authz, revoke, keychange, metaTerms, metaWebsite, metaCAA)
 	}))
 	defer ts.Close()
-	c := Client{DirectoryURL: ts.URL}
+	c := &Client{DirectoryURL: ts.URL}
 	dir, err := c.Discover(context.Background())
 	if err != nil {
 		t.Fatal(err)
diff --git a/acme/types.go b/acme/types.go
index eaae452..67b8252 100644
--- a/acme/types.go
+++ b/acme/types.go
@@ -305,14 +305,6 @@
 	ExternalAccountRequired bool
 }
 
-// rfcCompliant reports whether the ACME server implements RFC 8555.
-// Note that some servers may have incomplete RFC implementation
-// even if the returned value is true.
-// If rfcCompliant reports false, the server most likely implements draft-02.
-func (d *Directory) rfcCompliant() bool {
-	return d.OrderURL != ""
-}
-
 // Order represents a client's request for a certificate.
 // It tracks the request flow progress through to issuance.
 type Order struct {