acme: update existing methods for RFC 8555

This adds RFC support to the existing methods which,
in conjunction with the new order based methods
implemented in golang.org/cl/192779, completes a Client
capable of obtaining certificates from RFC compliant CAs.

Updates golang/go#21081

Change-Id: I3aabc50928d3e4e49ee202eb6695135d5ad86821
Reviewed-on: https://go-review.googlesource.com/c/crypto/+/194379
Reviewed-by: Filippo Valsorda <filippo@golang.org>
diff --git a/acme/acme.go b/acme/acme.go
index 31d07e3..fde2acb 100644
--- a/acme/acme.go
+++ b/acme/acme.go
@@ -5,7 +5,7 @@
 // Package acme provides an implementation of the
 // Automatic Certificate Management Environment (ACME) spec.
 // The intial implementation was based on ACME draft-02 and
-// is now being extended to comply with RFC8555.
+// 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.
 //
@@ -60,7 +60,10 @@
 
 const (
 	maxChainLen = 5       // max depth and breadth of a certificate chain
-	maxCertSize = 1 << 20 // max size of a certificate, in bytes
+	maxCertSize = 1 << 20 // max size of a certificate, in DER bytes
+	// Used for decoding certs from application/pem-certificate-chain response,
+	// the default when in RFC mode.
+	maxCertChainSize = maxCertSize * maxChainLen
 
 	// Max number of collected nonces kept in memory.
 	// Expect usual peak of 1 or 2.
@@ -139,8 +142,7 @@
 func (c *Client) accountKID(ctx context.Context) keyID {
 	c.cacheMu.Lock()
 	defer c.cacheMu.Unlock()
-	if c.dir.OrderURL == "" {
-		// Assume legacy CA.
+	if !c.dir.rfcCompliant() {
 		return noKeyID
 	}
 	if c.kid != noKeyID {
@@ -233,6 +235,9 @@
 }
 
 // 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.
+//
 // 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.
@@ -284,12 +289,22 @@
 // It retries the request until the certificate is successfully retrieved,
 // context is cancelled by the caller or an error response is received.
 //
-// The returned value will also contain the CA (issuer) certificate if the bundle argument is true.
+// If the bundle argument is true, the returned value also contains the CA (issuer)
+// certificate chain.
 //
 // FetchCert 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 expected features.
 func (c *Client) FetchCert(ctx context.Context, url string, bundle bool) ([][]byte, error) {
+	dir, err := c.Discover(ctx)
+	if 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
@@ -304,10 +319,15 @@
 // 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 {
-	if _, err := c.Discover(ctx); err != nil {
+	dir, err := c.Discover(ctx)
+	if 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"`
@@ -317,7 +337,7 @@
 		Cert:     base64.RawURLEncoding.EncodeToString(cert),
 		Reason:   int(reason),
 	}
-	res, err := c.post(ctx, key, c.dir.RevokeURL, body, wantStatus(http.StatusOK))
+	res, err := c.post(ctx, key, dir.RevokeURL, body, wantStatus(http.StatusOK))
 	if err != nil {
 		return err
 	}
@@ -337,7 +357,7 @@
 // Register calls prompt with a TOS URL provided by the CA. Prompt should report
 // whether the caller agrees to the terms. To always accept the terms, the caller can use AcceptTOS.
 //
-// When interfacing with RFC compliant CA, non-RFC8555 compliant fields of acct are ignored
+// When interfacing with an RFC-compliant CA, non-RFC 8555 fields of acct are ignored
 // and prompt is called if Directory's Terms field is non-zero.
 // Also see Error's Instance field for when a CA requires already registered accounts to agree
 // to an updated Terms of Service.
@@ -346,9 +366,7 @@
 	if err != nil {
 		return nil, err
 	}
-
-	// RFC8555 compliant account registration.
-	if dir.OrderURL != "" {
+	if dir.rfcCompliant() {
 		return c.registerRFC(ctx, acct, prompt)
 	}
 
@@ -370,16 +388,14 @@
 
 // GetReg retrieves an existing account associated with c.Key.
 //
-// The url argument is an Account URI used with pre-RFC8555 CAs.
-// It is ignored when interfacing with an RFC compliant CA.
+// The url argument is an Account URI used with pre-RFC 8555 CAs.
+// It is ignored when interfacing with an RFC-compliant CA.
 func (c *Client) GetReg(ctx context.Context, url string) (*Account, error) {
 	dir, err := c.Discover(ctx)
 	if err != nil {
 		return nil, err
 	}
-
-	// Assume RFC8555 compliant CA.
-	if dir.OrderURL != "" {
+	if dir.rfcCompliant() {
 		return c.getRegRFC(ctx)
 	}
 
@@ -395,16 +411,14 @@
 // 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
+// When interfacing with RFC-compliant CAs, a.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 {
 		return nil, err
 	}
-
-	// Assume RFC8555 compliant CA.
-	if dir.OrderURL != "" {
+	if dir.rfcCompliant() {
 		return c.updateRegRFC(ctx, acct)
 	}
 
@@ -418,13 +432,21 @@
 	return a, nil
 }
 
-// Authorize performs the initial step in an authorization flow.
+// Authorize performs the initial step in the pre-authorization flow,
+// as opposed to order-based flow.
 // The caller will then need to choose from and perform a set of returned
 // challenges using c.Accept in order to successfully complete authorization.
 //
+// Once complete, the caller can use AuthorizeOrder which the CA
+// should provision with the already satisfied authorization.
+// For pre-RFC CAs, the caller can proceed directly to requesting a certificate
+// using CreateCert method.
+//
 // If an authorization has been previously granted, the CA may return
-// a valid authorization (Authorization.Status is StatusValid). If so, the caller
-// need not fulfill any challenge and can proceed to requesting a certificate.
+// a valid authorization which has its Status field set to StatusValid.
+//
+// More about pre-authorization can be found at
+// https://tools.ietf.org/html/rfc8555#section-7.4.1.
 func (c *Client) Authorize(ctx context.Context, domain string) (*Authorization, error) {
 	return c.authorize(ctx, "dns", domain)
 }
@@ -476,7 +498,17 @@
 // 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) {
-	res, err := c.get(ctx, url, wantStatus(http.StatusOK, http.StatusAccepted))
+	dir, err := c.Discover(ctx)
+	if 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))
+	}
 	if err != nil {
 		return nil, err
 	}
@@ -493,8 +525,8 @@
 // The url argument is an Authorization.URI value.
 //
 // If successful, the caller will be required to obtain a new authorization
-// using the Authorize method before being able to request a new certificate
-// for the domain associated with the authorization.
+// using the Authorize or AuthorizeOrder methods before being able to request
+// a new certificate for the domain associated with the authorization.
 //
 // It does not revoke existing certificates.
 func (c *Client) RevokeAuthorization(ctx context.Context, url string) error {
@@ -528,8 +560,18 @@
 // 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 {
+		return nil, err
+	}
+	getfn := c.postAsGet
+	if !dir.rfcCompliant() {
+		getfn = c.get
+	}
+
 	for {
-		res, err := c.get(ctx, url, wantStatus(http.StatusOK, http.StatusAccepted))
+		res, err := getfn(ctx, url, wantStatus(http.StatusOK, http.StatusAccepted))
 		if err != nil {
 			return nil, err
 		}
@@ -572,10 +614,21 @@
 //
 // A client typically polls a challenge status using this method.
 func (c *Client) GetChallenge(ctx context.Context, url string) (*Challenge, error) {
-	res, err := c.get(ctx, url, wantStatus(http.StatusOK, http.StatusAccepted))
+	// Required for c.accountKID() when in RFC mode.
+	dir, err := c.Discover(ctx)
 	if err != nil {
 		return nil, err
 	}
+
+	getfn := c.postAsGet
+	if !dir.rfcCompliant() {
+		getfn = c.get
+	}
+	res, err := getfn(ctx, url, wantStatus(http.StatusOK, http.StatusAccepted))
+	if err != nil {
+		return nil, err
+	}
+
 	defer res.Body.Close()
 	v := wireChallenge{URI: url}
 	if err := json.NewDecoder(res.Body).Decode(&v); err != nil {
@@ -590,23 +643,26 @@
 // 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.
-	if _, err := c.Discover(ctx); err != nil {
-		return nil, err
-	}
-
-	auth, err := keyAuth(c.Key.Public(), chal.Token)
+	dir, err := c.Discover(ctx)
 	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,
+	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(
 		http.StatusOK,       // according to the spec
@@ -658,21 +714,8 @@
 }
 
 // TLSSNI01ChallengeCert creates a certificate for TLS-SNI-01 challenge response.
-// Servers can present the certificate to validate the challenge and prove control
-// over a domain name.
 //
-// The implementation is incomplete in that the returned value is a single certificate,
-// computed only for Z0 of the key authorization. ACME CAs are expected to update
-// their implementations to use the newer version, TLS-SNI-02.
-// For more details on TLS-SNI-01 see https://tools.ietf.org/html/draft-ietf-acme-acme-01#section-7.3.
-//
-// The token argument is a Challenge.Token value.
-// If a WithKey option is provided, its private part signs the returned cert,
-// and the public part is used to specify the signee.
-// If no WithKey option is provided, a new ECDSA key is generated using P-256 curve.
-//
-// The returned certificate is valid for the next 24 hours and must be presented only when
-// the server name of the TLS ClientHello matches exactly the returned name value.
+// Deprecated: This challenge type is unused in both draft-02 and RFC versions of 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 {
@@ -689,17 +732,8 @@
 }
 
 // TLSSNI02ChallengeCert creates a certificate for TLS-SNI-02 challenge response.
-// Servers can present the certificate to validate the challenge and prove control
-// over a domain name. For more details on TLS-SNI-02 see
-// https://tools.ietf.org/html/draft-ietf-acme-acme-03#section-7.3.
 //
-// The token argument is a Challenge.Token value.
-// If a WithKey option is provided, its private part signs the returned cert,
-// and the public part is used to specify the signee.
-// If no WithKey option is provided, a new ECDSA key is generated using P-256 curve.
-//
-// The returned certificate is valid for the next 24 hours and must be presented only when
-// the server name in the TLS ClientHello matches exactly the returned name value.
+// Deprecated: This challenge type is unused in both draft-02 and RFC versions of 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[:])
@@ -766,7 +800,7 @@
 	return tlsChallengeCert([]string{domain}, newOpt)
 }
 
-// doReg sends all types of registration requests.
+// 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.
 //
diff --git a/acme/acme_test.go b/acme/acme_test.go
index e17f375..0417a7e 100644
--- a/acme/acme_test.go
+++ b/acme/acme_test.go
@@ -484,7 +484,7 @@
 	}))
 	defer ts.Close()
 
-	cl := Client{Key: testKeyEC}
+	cl := Client{Key: testKeyEC, DirectoryURL: ts.URL}
 	auth, err := cl.GetAuthorization(context.Background(), ts.URL)
 	if err != nil {
 		t.Fatal(err)
@@ -614,7 +614,7 @@
 	}
 	ch := make(chan res, 1)
 	go func() {
-		var client Client
+		var client = Client{DirectoryURL: ts.URL}
 		a, err := client.WaitAuthorization(ctx, ts.URL)
 		ch <- res{a, err}
 	}()
@@ -684,7 +684,7 @@
 	}))
 	defer ts.Close()
 
-	cl := Client{Key: testKeyEC}
+	cl := Client{Key: testKeyEC, DirectoryURL: ts.URL}
 	chall, err := cl.GetChallenge(context.Background(), ts.URL)
 	if err != nil {
 		t.Fatal(err)
@@ -865,7 +865,8 @@
 		w.Write([]byte{count})
 	}))
 	defer ts.Close()
-	res, err := (&Client{}).FetchCert(context.Background(), ts.URL, true)
+	cl := &Client{dir: &Directory{}} // skip discovery
+	res, err := cl.FetchCert(context.Background(), ts.URL, true)
 	if err != nil {
 		t.Fatalf("FetchCert: %v", err)
 	}
@@ -887,7 +888,8 @@
 		w.Write([]byte{1})
 	}))
 	defer ts.Close()
-	res, err := (&Client{}).FetchCert(context.Background(), ts.URL, false)
+	cl := &Client{dir: &Directory{}} // skip discovery
+	res, err := cl.FetchCert(context.Background(), ts.URL, false)
 	if err != nil {
 		t.Fatalf("FetchCert: %v", err)
 	}
diff --git a/acme/http.go b/acme/http.go
index b145292..c51943e 100644
--- a/acme/http.go
+++ b/acme/http.go
@@ -155,6 +155,14 @@
 	}
 }
 
+// postAsGet is POST-as-GET, a replacement for GET in RFC8555
+// as described in https://tools.ietf.org/html/rfc8555#section-6.3.
+// It makes a POST request in KID form with zero JWS payload.
+// See nopayload doc comments in jws.go.
+func (c *Client) postAsGet(ctx context.Context, url string, ok resOkay) (*http.Response, error) {
+	return c.post(ctx, nil, url, noPayload, ok)
+}
+
 // post issues a signed POST request in JWS format using the provided key
 // to the specified URL. If key is nil, c.Key is used instead.
 // It returns a non-error value only when ok reports true.
@@ -200,7 +208,7 @@
 // If key argument is nil and c.accountKID returns a non-zero keyID,
 // the request is sent in KID form. Otherwise, JWK form is used.
 //
-// In practice, when interfacing with RFC compliant CAs most requests are sent in KID form
+// In practice, when interfacing with RFC-compliant CAs most requests are sent in KID form
 // and JWK is used only when KID is unavailable: new account endpoint and certificate
 // revocation requests authenticated by a cert key.
 // See jwsEncodeJSON for other details.
diff --git a/acme/rfc8555.go b/acme/rfc8555.go
index 7517b05..dfb57a6 100644
--- a/acme/rfc8555.go
+++ b/acme/rfc8555.go
@@ -6,8 +6,14 @@
 
 import (
 	"context"
+	"crypto"
+	"encoding/base64"
 	"encoding/json"
+	"encoding/pem"
+	"errors"
 	"fmt"
+	"io"
+	"io/ioutil"
 	"net/http"
 	"time"
 )
@@ -16,7 +22,7 @@
 // A deactivated account can no longer request certificate issuance or access
 // resources related to the account, such as orders or authorizations.
 //
-// It works only with RFC8555 compliant CAs.
+// It only works with CAs implementing RFC 8555.
 func (c *Client) DeactivateReg(ctx context.Context) error {
 	url := string(c.accountKID(ctx))
 	if url == "" {
@@ -31,7 +37,7 @@
 	return nil
 }
 
-// registerRFC is quivalent to c.Register but for RFC-compliant CAs.
+// registerRFC is quivalent to c.Register but for CAs implementing RFC 8555.
 // It expects c.Discover to have already been called.
 // TODO: Implement externalAccountBinding.
 func (c *Client) registerRFC(ctx context.Context, acct *Account, prompt func(tosURL string) bool) (*Account, error) {
@@ -69,7 +75,7 @@
 	return a, nil
 }
 
-// updateGegRFC is equivalent to c.UpdateReg but for RFC-compliant CAs.
+// updateGegRFC is equivalent to c.UpdateReg but for CAs implementing RFC 8555.
 // It expects c.Discover to have already been called.
 func (c *Client) updateRegRFC(ctx context.Context, a *Account) (*Account, error) {
 	url := string(c.accountKID(ctx))
@@ -89,7 +95,7 @@
 	return responseAccount(res)
 }
 
-// getGegRFC is equivalent to c.GetReg but for RFC-compliant CAs.
+// getGegRFC is equivalent to c.GetReg but for CAs implementing RFC 8555.
 // It expects c.Discover to have already been called.
 func (c *Client) getRegRFC(ctx context.Context) (*Account, error) {
 	req := json.RawMessage(`{"onlyReturnExisting": true}`)
@@ -124,10 +130,12 @@
 
 // AuthorizeOrder initiates the order-based application for certificate issuance,
 // as opposed to pre-authorization in Authorize.
+// It is only supported by CAs implementing RFC 8555.
 //
-// The caller then needs to fetch each required authorization with GetAuthorization
-// and fulfill a challenge using Accept. Once all authorizations are satisfied,
-// the caller will typically want to poll order status using WaitOrder until it's in StatusReady state.
+// The caller then needs to fetch each authorization with GetAuthorization,
+// identify those with StatusPending status and fulfill a challenge using Accept.
+// Once all authorizations are satisfied, the caller will typically want to poll
+// order status using WaitOrder until it's in StatusReady state.
 // To finalize the order and obtain a certificate, the caller submits a CSR with CreateOrderCert.
 func (c *Client) AuthorizeOrder(ctx context.Context, id []AuthzID, opt ...OrderOption) (*Order, error) {
 	dir, err := c.Discover(ctx)
@@ -176,7 +184,7 @@
 		return nil, err
 	}
 
-	res, err := c.post(ctx, nil, url, noPayload, wantStatus(http.StatusOK))
+	res, err := c.postAsGet(ctx, url, wantStatus(http.StatusOK))
 	if err != nil {
 		return nil, err
 	}
@@ -190,13 +198,13 @@
 //
 // It returns a non-nil Order only if its Status is StatusReady or StatusValid.
 // In all other cases WaitOrder returns an error.
-// If the Status is StatusInvalid, the returned error is of type *WaitOrderError.
+// If the Status is StatusInvalid, the returned error is of type *OrderError.
 func (c *Client) WaitOrder(ctx context.Context, url string) (*Order, error) {
 	if _, err := c.Discover(ctx); err != nil {
 		return nil, err
 	}
 	for {
-		res, err := c.post(ctx, nil, url, noPayload, wantStatus(http.StatusOK))
+		res, err := c.postAsGet(ctx, url, wantStatus(http.StatusOK))
 		if err != nil {
 			return nil, err
 		}
@@ -206,7 +214,7 @@
 		case err != nil:
 			// Skip and retry.
 		case o.Status == StatusInvalid:
-			return nil, &WaitOrderError{OrderURL: o.URI, Status: o.Status}
+			return nil, &OrderError{OrderURL: o.URI, Status: o.Status}
 		case o.Status == StatusReady || o.Status == StatusValid:
 			return o, nil
 		}
@@ -261,3 +269,124 @@
 	}
 	return o, nil
 }
+
+// CreateOrderCert submits the CSR (Certificate Signing Request) to a CA at the specified URL.
+// The URL is the FinalizeURL field of an Order created with AuthorizeOrder.
+//
+// If the bundle argument is true, the returned value also contain the CA (issuer)
+// certificate chain. Otherwise, only a leaf certificate is returned.
+// The returned URL can be used to re-fetch the certificate using FetchCert.
+//
+// This method is only supported by CAs implementing RFC 8555. See CreateCert for pre-RFC CAs.
+//
+// CreateOrderCert returns an error if the CA's response is unreasonably large.
+// Callers are encouraged to parse the returned value to ensure the certificate is valid and has the expected features.
+func (c *Client) CreateOrderCert(ctx context.Context, url string, csr []byte, bundle bool) (der [][]byte, certURL string, err error) {
+	if _, err := c.Discover(ctx); err != nil { // required by c.accountKID
+		return nil, "", err
+	}
+
+	// RFC describes this as "finalize order" request.
+	req := struct {
+		CSR string `json:"csr"`
+	}{
+		CSR: base64.RawURLEncoding.EncodeToString(csr),
+	}
+	res, err := c.post(ctx, nil, url, req, wantStatus(http.StatusOK))
+	if err != nil {
+		return nil, "", err
+	}
+	defer res.Body.Close()
+	o, err := responseOrder(res)
+	if err != nil {
+		return nil, "", err
+	}
+
+	// Wait for CA to issue the cert if they haven't.
+	if o.Status != StatusValid {
+		o, err = c.WaitOrder(ctx, o.URI)
+	}
+	if err != nil {
+		return nil, "", err
+	}
+	// The only acceptable status post finalize and WaitOrder is "valid".
+	if o.Status != StatusValid {
+		return nil, "", &OrderError{OrderURL: o.URI, Status: o.Status}
+	}
+	crt, err := c.fetchCertRFC(ctx, o.CertURL, bundle)
+	return crt, o.CertURL, err
+}
+
+// fetchCertRFC downloads issued certificate from the given URL.
+// It expects the CA to respond with PEM-encoded certificate chain.
+//
+// The URL argument is the CertURL field of Order.
+func (c *Client) fetchCertRFC(ctx context.Context, url string, bundle bool) ([][]byte, error) {
+	res, err := c.postAsGet(ctx, url, wantStatus(http.StatusOK))
+	if err != nil {
+		return nil, err
+	}
+	defer res.Body.Close()
+
+	// Get all the bytes up to a sane maximum.
+	// Account very roughly for base64 overhead.
+	const max = maxCertChainSize + maxCertChainSize/33
+	b, err := ioutil.ReadAll(io.LimitReader(res.Body, max+1))
+	if err != nil {
+		return nil, fmt.Errorf("acme: fetch cert response stream: %v", err)
+	}
+	if len(b) > max {
+		return nil, errors.New("acme: certificate chain is too big")
+	}
+
+	// Decode PEM chain.
+	var chain [][]byte
+	for {
+		var p *pem.Block
+		p, b = pem.Decode(b)
+		if p == nil {
+			break
+		}
+		if p.Type != "CERTIFICATE" {
+			return nil, fmt.Errorf("acme: invalid PEM cert type %q", p.Type)
+		}
+
+		chain = append(chain, p.Bytes)
+		if !bundle {
+			return chain, nil
+		}
+		if len(chain) > maxChainLen {
+			return nil, errors.New("acme: certificate chain is too long")
+		}
+	}
+	if len(chain) == 0 {
+		return nil, errors.New("acme: certificate chain is empty")
+	}
+	return chain, nil
+}
+
+// sends a cert revocation request in either JWK form when key is non-nil or KID form otherwise.
+func (c *Client) revokeCertRFC(ctx context.Context, key crypto.Signer, cert []byte, reason CRLReasonCode) error {
+	req := &struct {
+		Cert   string `json:"certificate"`
+		Reason int    `json:"reason"`
+	}{
+		Cert:   base64.RawURLEncoding.EncodeToString(cert),
+		Reason: int(reason),
+	}
+	res, err := c.post(ctx, key, c.dir.RevokeURL, req, wantStatus(http.StatusOK))
+	if err != nil {
+		if isAlreadyRevoked(err) {
+			// Assume it is not an error to revoke an already revoked cert.
+			return nil
+		}
+		return err
+	}
+	defer res.Body.Close()
+	return nil
+}
+
+func isAlreadyRevoked(err error) bool {
+	e, ok := err.(*Error)
+	return ok && e.ProblemType == "urn:ietf:params:acme:error:alreadyRevoked"
+}
diff --git a/acme/rfc8555_test.go b/acme/rfc8555_test.go
index a109c8d..7e5e446 100644
--- a/acme/rfc8555_test.go
+++ b/acme/rfc8555_test.go
@@ -7,9 +7,14 @@
 import (
 	"bytes"
 	"context"
+	"crypto/rand"
+	"crypto/x509"
+	"crypto/x509/pkix"
 	"encoding/json"
+	"encoding/pem"
 	"fmt"
 	"io/ioutil"
+	"math/big"
 	"net/http"
 	"net/http/httptest"
 	"reflect"
@@ -569,12 +574,12 @@
 func TestRFC_WaitOrder(t *testing.T) {
 	for _, st := range []string{StatusReady, StatusValid} {
 		t.Run(st, func(t *testing.T) {
-			testWaitOrder(t, st)
+			testWaitOrderStatus(t, st)
 		})
 	}
 }
 
-func testWaitOrder(t *testing.T, okStatus string) {
+func testWaitOrderStatus(t *testing.T, okStatus string) {
 	s := newACMEServer()
 	s.handle("/acme/new-account", func(w http.ResponseWriter, r *http.Request) {
 		w.Header().Set("Location", s.url("/accounts/1"))
@@ -651,9 +656,9 @@
 		if err == nil {
 			t.Fatal("WaitOrder returned nil error")
 		}
-		e, ok := err.(*WaitOrderError)
+		e, ok := err.(*OrderError)
 		if !ok {
-			t.Fatalf("err = %v (%T); want WaitOrderError", err, err)
+			t.Fatalf("err = %v (%T); want OrderError", err, err)
 		}
 		if e.OrderURL != s.url("/orders/1") {
 			t.Errorf("e.OrderURL = %q; want %q", e.OrderURL, s.url("/orders/1"))
@@ -663,3 +668,76 @@
 		}
 	}
 }
+
+func TestRFC_CreateOrderCert(t *testing.T) {
+	q := &x509.CertificateRequest{
+		Subject: pkix.Name{CommonName: "example.org"},
+	}
+	csr, err := x509.CreateCertificateRequest(rand.Reader, q, testKeyEC)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	tmpl := &x509.Certificate{SerialNumber: big.NewInt(1)}
+	leaf, err := x509.CreateCertificate(rand.Reader, tmpl, tmpl, &testKeyEC.PublicKey, testKeyEC)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	s := newACMEServer()
+	s.handle("/acme/new-account", func(w http.ResponseWriter, r *http.Request) {
+		w.Header().Set("Location", s.url("/accounts/1"))
+		w.Write([]byte(`{"status": "valid"}`))
+	})
+	var count int
+	s.handle("/pleaseissue", func(w http.ResponseWriter, r *http.Request) {
+		w.Header().Set("Location", s.url("/pleaseissue"))
+		st := StatusProcessing
+		if count > 0 {
+			st = StatusValid
+		}
+		fmt.Fprintf(w, `{"status":%q, "certificate":%q}`, st, s.url("/crt"))
+		count++
+	})
+	s.handle("/crt", func(w http.ResponseWriter, r *http.Request) {
+		w.Header().Set("Content-Type", "application/pem-certificate-chain")
+		pem.Encode(w, &pem.Block{Type: "CERTIFICATE", Bytes: leaf})
+	})
+	s.start()
+	defer s.close()
+	ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
+	defer cancel()
+
+	cl := &Client{Key: testKeyEC, DirectoryURL: s.url("/")}
+	cert, curl, err := cl.CreateOrderCert(ctx, s.url("/pleaseissue"), csr, true)
+	if err != nil {
+		t.Fatalf("CreateOrderCert: %v", err)
+	}
+	if _, err := x509.ParseCertificate(cert[0]); err != nil {
+		t.Errorf("ParseCertificate: %v", err)
+	}
+	if !reflect.DeepEqual(cert[0], leaf) {
+		t.Errorf("cert and leaf bytes don't match")
+	}
+	if u := s.url("/crt"); curl != u {
+		t.Errorf("curl = %q; want %q", curl, u)
+	}
+}
+
+func TestRFC_AlreadyRevokedCert(t *testing.T) {
+	s := newACMEServer()
+	s.handle("/acme/revoke-cert", func(w http.ResponseWriter, r *http.Request) {
+		s.error(w, &wireError{
+			Status: http.StatusBadRequest,
+			Type:   "urn:ietf:params:acme:error:alreadyRevoked",
+		})
+	})
+	s.start()
+	defer s.close()
+
+	cl := &Client{Key: testKeyEC, DirectoryURL: s.url("/")}
+	err := cl.RevokeCert(context.Background(), testKeyEC, []byte{0}, CRLReasonUnspecified)
+	if err != nil {
+		t.Fatalf("RevokeCert: %v", err)
+	}
+}
diff --git a/acme/types.go b/acme/types.go
index c654fff..9c59097 100644
--- a/acme/types.go
+++ b/acme/types.go
@@ -18,10 +18,11 @@
 // See https://tools.ietf.org/html/rfc8555#section-7.1.6 for details.
 const (
 	StatusDeactivated = "deactivated"
+	StatusExpired     = "expired"
 	StatusInvalid     = "invalid"
-	StatusReady       = "ready"
 	StatusPending     = "pending"
 	StatusProcessing  = "processing"
+	StatusReady       = "ready"
 	StatusRevoked     = "revoked"
 	StatusUnknown     = "unknown"
 	StatusValid       = "valid"
@@ -104,16 +105,19 @@
 	return fmt.Sprintf("acme: authorization error for %s: %s", a.Identifier, strings.Join(e, "; "))
 }
 
-// WaitOrderError is returned from Client's WaitOrder.
+// OrderError is returned from Client's order related methods.
 // It indicates the order is unusable and the clients should start over with
 // AuthorizeOrder.
-type WaitOrderError struct {
+//
+// The clients can still fetch the order object from CA using GetOrder
+// to inspect its state.
+type OrderError struct {
 	OrderURL string
 	Status   string
 }
 
-func (we *WaitOrderError) Error() string {
-	return fmt.Sprintf("acme: wait order %s: status %s", we.OrderURL, we.Status)
+func (oe *OrderError) Error() string {
+	return fmt.Sprintf("acme: order %s status: %s", oe.OrderURL, oe.Status)
 }
 
 // RateLimit reports whether err represents a rate limit error and
@@ -138,11 +142,11 @@
 }
 
 // Account is a user account. It is associated with a private key.
-// Non-RFC8555 fields are empty when interfacing with a compliant CA.
+// Non-RFC 8555 fields are empty when interfacing with a compliant CA.
 type Account struct {
 	// URI is the account unique ID, which is also a URL used to retrieve
 	// account data from the CA.
-	// When interfacing with RFC8555-compliant CAs, URI is the "kid" field
+	// When interfacing with RFC 8555-compliant CAs, URI is the "kid" field
 	// value in JWS signed requests.
 	URI string
 
@@ -163,32 +167,32 @@
 	// A value not matching CurrentTerms indicates that the user hasn't agreed
 	// to the actual Terms of Service of the CA.
 	//
-	// It is non-RFC8555 compliant. Package users can store the ToS they agree to
+	// It is non-RFC 8555 compliant. Package users can store the ToS they agree to
 	// during Client's Register call in the prompt callback function.
 	AgreedTerms string
 
 	// Actual terms of a CA.
 	//
-	// It is non-RFC8555 compliant. Use Directory's Terms field.
+	// It is non-RFC 8555 compliant. Use Directory's Terms field.
 	// When a CA updates their terms and requires an account agreement,
 	// a URL at which instructions to do so is available in Error's Instance field.
 	CurrentTerms string
 
 	// Authz is the authorization URL used to initiate a new authz flow.
 	//
-	// It is non-RFC8555 compliant. Use Directory's AuthzURL or OrderURL.
+	// It is non-RFC 8555 compliant. Use Directory's AuthzURL or OrderURL.
 	Authz string
 
 	// Authorizations is a URI from which a list of authorizations
 	// granted to this account can be fetched via a GET request.
 	//
-	// It is non-RFC8555 compliant and is obsoleted by OrdersURL.
+	// It is non-RFC 8555 compliant and is obsoleted by OrdersURL.
 	Authorizations string
 
 	// Certificates is a URI from which a list of certificates
 	// issued for this account can be fetched via a GET request.
 	//
-	// It is non-RFC8555 compliant and is obsoleted by OrdersURL.
+	// It is non-RFC 8555 compliant and is obsoleted by OrdersURL.
 	Certificates string
 }
 
@@ -199,11 +203,11 @@
 	NonceURL string
 
 	// RegURL is an account endpoint URL, allowing for creating new accounts.
-	// Pre-RFC8555 CAs also allow modifying existing accounts at this URL.
+	// Pre-RFC 8555 CAs also allow modifying existing accounts at this URL.
 	RegURL string
 
 	// OrderURL is used to initiate the certificate issuance flow
-	// as described in RFC8555.
+	// as described in RFC 8555.
 	OrderURL string
 
 	// AuthzURL is used to initiate identifier pre-authorization flow.
@@ -211,7 +215,7 @@
 	AuthzURL string
 
 	// CertURL is a new certificate issuance endpoint URL.
-	// It is non-RFC8555 compliant and is obsoleted by OrderURL.
+	// It is non-RFC 8555 compliant and is obsoleted by OrderURL.
 	CertURL string
 
 	// RevokeURL is used to initiate a certificate revocation flow.
@@ -237,6 +241,14 @@
 	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 {
@@ -323,15 +335,26 @@
 	// URI uniquely identifies a authorization.
 	URI string
 
-	// Status identifies the status of an authorization.
+	// Status is the current status of an authorization.
+	// Possible values are StatusPending, StatusValid, StatusInvalid, StatusDeactivated,
+	// StatusExpired and StatusRevoked.
 	Status string
 
 	// Identifier is what the account is authorized to represent.
 	Identifier AuthzID
 
+	// The timestamp after which the CA considers the authorization invalid.
+	Expires time.Time
+
+	// Wildcard is true for authorizations of a wildcard domain name.
+	Wildcard bool
+
 	// Challenges that the client needs to fulfill in order to prove possession
 	// of the identifier (for pending authorizations).
-	// For final authorizations, the challenges that were used.
+	// For valid authorizations, the challenge that was validated.
+	// For invalid authorizations, the challenge that was attempted and failed.
+	//
+	// RFC 8555 compatible CAs require users to fuflfill only one of the challenges.
 	Challenges []*Challenge
 
 	// A collection of sets of challenges, each of which would be sufficient
@@ -339,6 +362,8 @@
 	// Clients must complete a set of challenges that covers at least one set.
 	// Challenges are identified by their indices in the challenges array.
 	// If this field is empty, the client needs to complete all challenges.
+	//
+	// This field is unused in RFC 8555.
 	Combinations [][]int
 }
 
@@ -376,10 +401,12 @@
 
 // wireAuthz is ACME JSON representation of Authorization objects.
 type wireAuthz struct {
+	Identifier   wireAuthzID
 	Status       string
+	Expires      time.Time
+	Wildcard     bool
 	Challenges   []wireChallenge
 	Combinations [][]int
-	Identifier   wireAuthzID
 }
 
 func (z *wireAuthz) authorization(uri string) *Authorization {
@@ -387,8 +414,10 @@
 		URI:          uri,
 		Status:       z.Status,
 		Identifier:   AuthzID{Type: z.Identifier.Type, Value: z.Identifier.Value},
-		Combinations: z.Combinations, // shallow copy
+		Expires:      z.Expires,
+		Wildcard:     z.Wildcard,
 		Challenges:   make([]*Challenge, len(z.Challenges)),
+		Combinations: z.Combinations, // shallow copy
 	}
 	for i, v := range z.Challenges {
 		a.Challenges[i] = v.challenge()
@@ -413,7 +442,7 @@
 // Its Error field may be non-nil if the challenge is part of an Authorization
 // with StatusInvalid.
 type Challenge struct {
-	// Type is the challenge type, e.g. "http-01", "tls-sni-02", "dns-01".
+	// Type is the challenge type, e.g. "http-01", "tls-alpn-01", "dns-01".
 	Type string
 
 	// URI is where a challenge response can be posted to.
@@ -423,8 +452,14 @@
 	Token string
 
 	// Status identifies the status of this challenge.
+	// In RFC 8555, possible values are StatusPending, StatusProcessing, StatusValid,
+	// and StatusInvalid.
 	Status string
 
+	// Validated is the time at which the CA validated this challenge.
+	// Always zero value in pre-RFC 8555.
+	Validated time.Time
+
 	// Error indicates the reason for an authorization failure
 	// when this challenge was used.
 	// The type of a non-nil value is *Error.
@@ -433,20 +468,25 @@
 
 // wireChallenge is ACME JSON challenge representation.
 type wireChallenge struct {
-	URI    string `json:"uri"`
-	Type   string
-	Token  string
-	Status string
-	Error  *wireError
+	URL       string `json:"url"` // RFC
+	URI       string `json:"uri"` // pre-RFC
+	Type      string
+	Token     string
+	Status    string
+	Validated time.Time
+	Error     *wireError
 }
 
 func (c *wireChallenge) challenge() *Challenge {
 	v := &Challenge{
-		URI:    c.URI,
+		URI:    c.URL,
 		Type:   c.Type,
 		Token:  c.Token,
 		Status: c.Status,
 	}
+	if v.URI == "" {
+		v.URI = c.URI // c.URL was empty; use legacy
+	}
 	if v.Status == "" {
 		v.Status = StatusPending
 	}