Newer
Older
pokemon-go-trade / vendor / golang.org / x / crypto / acme / acme_test.go
// Copyright 2015 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package acme

import (
	"bytes"
	"context"
	"crypto/rand"
	"crypto/rsa"
	"crypto/tls"
	"crypto/x509"
	"crypto/x509/pkix"
	"encoding/base64"
	"encoding/hex"
	"encoding/json"
	"fmt"
	"io"
	"math/big"
	"net/http"
	"net/http/httptest"
	"reflect"
	"sort"
	"strings"
	"testing"
	"time"
)

// newTestClient creates a client with a non-nil Directory so that it skips
// the discovery which is otherwise done on the first call of almost every
// exported method.
func newTestClient() *Client {
	return &Client{
		Key: testKeyEC,
		dir: &Directory{}, // skip discovery
	}
}

// Decodes a JWS-encoded request and unmarshals the decoded JSON into a provided
// interface.
func decodeJWSRequest(t *testing.T, v interface{}, r io.Reader) {
	// Decode request
	var req struct{ Payload string }
	if err := json.NewDecoder(r).Decode(&req); err != nil {
		t.Fatal(err)
	}
	payload, err := base64.RawURLEncoding.DecodeString(req.Payload)
	if err != nil {
		t.Fatal(err)
	}
	err = json.Unmarshal(payload, v)
	if err != nil {
		t.Fatal(err)
	}
}

type jwsHead struct {
	Alg   string
	Nonce string
	URL   string            `json:"url"`
	KID   string            `json:"kid"`
	JWK   map[string]string `json:"jwk"`
}

func decodeJWSHead(r io.Reader) (*jwsHead, error) {
	var req struct{ Protected string }
	if err := json.NewDecoder(r).Decode(&req); err != nil {
		return nil, err
	}
	b, err := base64.RawURLEncoding.DecodeString(req.Protected)
	if err != nil {
		return nil, err
	}
	var head jwsHead
	if err := json.Unmarshal(b, &head); err != nil {
		return nil, err
	}
	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 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"},
		{"ip", "1.2.3.4"},
	}
	for _, test := range tt {
		t.Run(test.typ, func(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
					Identifier struct {
						Type  string
						Value string
					}
				}
				decodeJWSRequest(t, &j, r.Body)

				// Test request
				if j.Resource != "new-authz" {
					t.Errorf("j.Resource = %q; want new-authz", j.Resource)
				}
				if j.Identifier.Type != test.typ {
					t.Errorf("j.Identifier.Type = %q; want %q", j.Identifier.Type, test.typ)
				}
				if j.Identifier.Value != test.value {
					t.Errorf("j.Identifier.Value = %q; want %q", j.Identifier.Value, test.value)
				}

				w.Header().Set("Location", "https://ca.tld/acme/auth/1")
				w.WriteHeader(http.StatusCreated)
				fmt.Fprintf(w, `{
					"identifier": {"type":%q,"value":%q},
					"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]]
				}`, test.typ, test.value)
			}))
			defer ts.Close()

			var (
				auth *Authorization
				err  error
			)
			cl := Client{
				Key:          testKeyEC,
				DirectoryURL: ts.URL,
				dir:          &Directory{AuthzURL: ts.URL},
			}
			switch test.typ {
			case "dns":
				auth, err = cl.Authorize(context.Background(), test.value)
			case "ip":
				auth, err = cl.AuthorizeIP(context.Background(), test.value)
			default:
				t.Fatalf("unknown identifier type: %q", test.typ)
			}
			if err != nil {
				t.Fatal(err)
			}

			if auth.URI != "https://ca.tld/acme/auth/1" {
				t.Errorf("URI = %q; want https://ca.tld/acme/auth/1", auth.URI)
			}
			if auth.Status != "pending" {
				t.Errorf("Status = %q; want pending", auth.Status)
			}
			if auth.Identifier.Type != test.typ {
				t.Errorf("Identifier.Type = %q; want %q", auth.Identifier.Type, test.typ)
			}
			if auth.Identifier.Value != test.value {
				t.Errorf("Identifier.Value = %q; want %q", auth.Identifier.Value, test.value)
			}

			if n := len(auth.Challenges); n != 2 {
				t.Fatalf("len(auth.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 TestAuthorizeValid(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
		}
		w.WriteHeader(http.StatusCreated)
		w.Write([]byte(`{"status":"valid"}`))
	}))
	defer ts.Close()
	client := Client{
		Key:          testKey,
		DirectoryURL: ts.URL,
		dir:          &Directory{AuthzURL: ts.URL},
	}
	_, err := client.Authorize(context.Background(), "example.com")
	if err != nil {
		t.Errorf("err = %v", err)
	}
}

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
		authz, err := runWaitAuthorization(context.Background(), t, func(w http.ResponseWriter, r *http.Request) {
			count++
			w.Header().Set("Retry-After", "0")
			if count > 1 {
				fmt.Fprintf(w, `{"status":"valid"}`)
				return
			}
			fmt.Fprintf(w, `{"status":"pending"}`)
		})
		if err != nil {
			t.Fatalf("non-nil error: %v", err)
		}
		if authz == nil {
			t.Fatal("authz is nil")
		}
	})
	t.Run("invalid status", func(t *testing.T) {
		_, err := runWaitAuthorization(context.Background(), t, func(w http.ResponseWriter, r *http.Request) {
			fmt.Fprintf(w, `{"status":"invalid"}`)
		})
		if _, ok := err.(*AuthorizationError); !ok {
			t.Errorf("err is %v (%T); want non-nil *AuthorizationError", err, err)
		}
	})
	t.Run("invalid status with error returns the authorization error", func(t *testing.T) {
		_, err := runWaitAuthorization(context.Background(), t, func(w http.ResponseWriter, r *http.Request) {
			fmt.Fprintf(w, `{
				"type": "dns-01",
				"status": "invalid",
				"error": {
				  "type": "urn:ietf:params:acme:error:caa",
				  "detail": "CAA record for <domain> prevents issuance",
				  "status": 403
				},
				"url": "https://acme-v02.api.letsencrypt.org/acme/chall-v3/xxx/xxx",
				"token": "xxx",
				"validationRecord": [
				  {
					"hostname": "<domain>"
				  }
				]
			  }`)
		})

		want := &AuthorizationError{
			Errors: []error{
				(&wireError{
					Status: 403,
					Type:   "urn:ietf:params:acme:error:caa",
					Detail: "CAA record for <domain> prevents issuance",
				}).error(nil),
			},
		}

		_, ok := err.(*AuthorizationError)
		if !ok {
			t.Errorf("err is %T; want non-nil *AuthorizationError", err)
		}

		if err.Error() != want.Error() {
			t.Errorf("err is %v; want %v", err, want)
		}
	})
	t.Run("non-retriable error", func(t *testing.T) {
		const code = http.StatusBadRequest
		_, err := runWaitAuthorization(context.Background(), t, func(w http.ResponseWriter, r *http.Request) {
			w.WriteHeader(code)
		})
		res, ok := err.(*Error)
		if !ok {
			t.Fatalf("err is %v (%T); want a non-nil *Error", err, err)
		}
		if res.StatusCode != code {
			t.Errorf("res.StatusCode = %d; want %d", res.StatusCode, code)
		}
	})
	for _, code := range []int{http.StatusTooManyRequests, http.StatusInternalServerError} {
		t.Run(fmt.Sprintf("retriable %d error", code), func(t *testing.T) {
			var count int
			authz, err := runWaitAuthorization(context.Background(), t, func(w http.ResponseWriter, r *http.Request) {
				count++
				w.Header().Set("Retry-After", "0")
				if count > 1 {
					fmt.Fprintf(w, `{"status":"valid"}`)
					return
				}
				w.WriteHeader(code)
			})
			if err != nil {
				t.Fatalf("non-nil error: %v", err)
			}
			if authz == nil {
				t.Fatal("authz is nil")
			}
		})
	}
	t.Run("context cancel", func(t *testing.T) {
		ctx, cancel := context.WithTimeout(context.Background(), 200*time.Millisecond)
		defer cancel()
		_, err := runWaitAuthorization(ctx, t, func(w http.ResponseWriter, r *http.Request) {
			w.Header().Set("Retry-After", "60")
			fmt.Fprintf(w, `{"status":"pending"}`)
		})
		if err == nil {
			t.Error("err is nil")
		}
	})
}
func runWaitAuthorization(ctx context.Context, t *testing.T, h http.HandlerFunc) (*Authorization, error) {
	t.Helper()
	ts := httptest.NewServer(h)
	defer ts.Close()
	type res struct {
		authz *Authorization
		err   error
	}
	ch := make(chan res, 1)
	go func() {
		var client = Client{DirectoryURL: ts.URL}
		a, err := client.WaitAuthorization(ctx, ts.URL)
		ch <- res{a, err}
	}()
	select {
	case <-time.After(3 * time.Second):
		t.Fatal("WaitAuthorization took too long to return")
	case v := <-ch:
		return v.authz, v.err
	}
	panic("runWaitAuthorization: out of select")
}

func TestRevokeAuthorization(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
		}
		switch r.URL.Path {
		case "/1":
			var req struct {
				Resource string
				Status   string
				Delete   bool
			}
			decodeJWSRequest(t, &req, r.Body)
			if req.Resource != "authz" {
				t.Errorf("req.Resource = %q; want authz", req.Resource)
			}
			if req.Status != "deactivated" {
				t.Errorf("req.Status = %q; want deactivated", req.Status)
			}
			if !req.Delete {
				t.Errorf("req.Delete is false")
			}
		case "/2":
			w.WriteHeader(http.StatusBadRequest)
		}
	}))
	defer ts.Close()
	client := &Client{
		Key:          testKey,
		DirectoryURL: ts.URL,       // don't dial outside of localhost
		dir:          &Directory{}, // don't do discovery
	}
	ctx := context.Background()
	if err := client.RevokeAuthorization(ctx, ts.URL+"/1"); err != nil {
		t.Errorf("err = %v", err)
	}
	if client.RevokeAuthorization(ctx, ts.URL+"/2") == nil {
		t.Error("nil error")
	}
}

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) {
		w.Header().Set("Retry-After", "0")
		w.WriteHeader(http.StatusBadRequest)
	}))
	defer ts.Close()
	ctx, cancel := context.WithCancel(context.Background())
	done := make(chan struct{})
	var err error
	go func() {
		cl := newTestClient()
		_, err = cl.FetchCert(ctx, ts.URL, false)
		close(done)
	}()
	cancel()
	<-done
	if err != context.Canceled {
		t.Errorf("err = %v; want %v", err, context.Canceled)
	}
}

func TestFetchCertDepth(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 > maxChainLen+1 {
			t.Errorf("count = %d; want at most %d", count, maxChainLen+1)
			w.WriteHeader(http.StatusInternalServerError)
		}
		w.Header().Set("Link", fmt.Sprintf("<%s>;rel=up", ts.URL))
		w.Write([]byte{count})
	}))
	defer ts.Close()
	cl := newTestClient()
	_, err := cl.FetchCert(context.Background(), ts.URL, true)
	if err == nil {
		t.Errorf("err is nil")
	}
}

func TestFetchCertBreadth(t *testing.T) {
	var ts *httptest.Server
	ts = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		for i := 0; i < maxChainLen+1; i++ {
			w.Header().Add("Link", fmt.Sprintf("<%s>;rel=up", ts.URL))
		}
		w.Write([]byte{1})
	}))
	defer ts.Close()
	cl := newTestClient()
	_, err := cl.FetchCert(context.Background(), ts.URL, true)
	if err == nil {
		t.Errorf("err is nil")
	}
}

func TestFetchCertSize(t *testing.T) {
	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		b := bytes.Repeat([]byte{1}, maxCertSize+1)
		w.Write(b)
	}))
	defer ts.Close()
	cl := newTestClient()
	_, err := cl.FetchCert(context.Background(), ts.URL, false)
	if err == nil {
		t.Errorf("err is nil")
	}
}

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"}})
	c.addNonce(http.Header{"Replay-Nonce": {}})
	c.addNonce(http.Header{"Replay-Nonce": {"nonce"}})

	nonces := map[string]struct{}{"nonce": {}}
	if !reflect.DeepEqual(c.nonces, nonces) {
		t.Errorf("c.nonces = %q; want %q", c.nonces, nonces)
	}
}

func TestNonce_addMax(t *testing.T) {
	c := &Client{nonces: make(map[string]struct{})}
	for i := 0; i < maxNonces; i++ {
		c.nonces[fmt.Sprintf("%d", i)] = struct{}{}
	}
	c.addNonce(http.Header{"Replay-Nonce": {"nonce"}})
	if n := len(c.nonces); n != maxNonces {
		t.Errorf("len(c.nonces) = %d; want %d", n, maxNonces)
	}
}

func TestNonce_fetch(t *testing.T) {
	tests := []struct {
		code  int
		nonce string
	}{
		{http.StatusOK, "nonce1"},
		{http.StatusBadRequest, "nonce2"},
		{http.StatusOK, ""},
	}
	var i int
	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		if r.Method != "HEAD" {
			t.Errorf("%d: r.Method = %q; want HEAD", i, r.Method)
		}
		w.Header().Set("Replay-Nonce", tests[i].nonce)
		w.WriteHeader(tests[i].code)
	}))
	defer ts.Close()
	for ; i < len(tests); i++ {
		test := tests[i]
		c := newTestClient()
		n, err := c.fetchNonce(context.Background(), ts.URL)
		if n != test.nonce {
			t.Errorf("%d: n=%q; want %q", i, n, test.nonce)
		}
		switch {
		case err == nil && test.nonce == "":
			t.Errorf("%d: n=%q, err=%v; want non-nil error", i, n, err)
		case err != nil && test.nonce != "":
			t.Errorf("%d: n=%q, err=%v; want %q", i, n, err, test.nonce)
		}
	}
}

func TestNonce_fetchError(t *testing.T) {
	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		w.WriteHeader(http.StatusTooManyRequests)
	}))
	defer ts.Close()
	c := newTestClient()
	_, err := c.fetchNonce(context.Background(), ts.URL)
	e, ok := err.(*Error)
	if !ok {
		t.Fatalf("err is %T; want *Error", err)
	}
	if e.StatusCode != http.StatusTooManyRequests {
		t.Errorf("e.StatusCode = %d; want %d", e.StatusCode, http.StatusTooManyRequests)
	}
}

func TestNonce_popWhenEmpty(t *testing.T) {
	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		if r.Method != "HEAD" {
			t.Errorf("r.Method = %q; want HEAD", r.Method)
		}
		switch r.URL.Path {
		case "/dir-with-nonce":
			w.Header().Set("Replay-Nonce", "dirnonce")
		case "/new-nonce":
			w.Header().Set("Replay-Nonce", "newnonce")
		case "/dir-no-nonce", "/empty":
			// No nonce in the header.
		default:
			t.Errorf("Unknown URL: %s", r.URL)
		}
	}))
	defer ts.Close()
	ctx := context.Background()

	tt := []struct {
		dirURL, popURL, nonce string
		wantOK                bool
	}{
		{ts.URL + "/dir-with-nonce", ts.URL + "/new-nonce", "dirnonce", true},
		{ts.URL + "/dir-no-nonce", ts.URL + "/new-nonce", "newnonce", true},
		{ts.URL + "/dir-no-nonce", ts.URL + "/empty", "", false},
	}
	for _, test := range tt {
		t.Run(fmt.Sprintf("nonce:%s wantOK:%v", test.nonce, test.wantOK), func(t *testing.T) {
			c := Client{DirectoryURL: test.dirURL}
			v, err := c.popNonce(ctx, test.popURL)
			if !test.wantOK {
				if err == nil {
					t.Fatalf("c.popNonce(%q) returned nil error", test.popURL)
				}
				return
			}
			if err != nil {
				t.Fatalf("c.popNonce(%q): %v", test.popURL, err)
			}
			if v != test.nonce {
				t.Errorf("c.popNonce(%q) = %q; want %q", test.popURL, v, test.nonce)
			}
		})
	}
}

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"`,
		`<https://example.com/acme/recover-reg>; rel=recover`,
		`<https://example.com/acme/terms>; foo=bar; rel="terms-of-service"`,
		`<dup>;rel="next"`,
	}}
	tests := []struct {
		rel string
		out []string
	}{
		{"next", []string{"https://example.com/acme/new-authz", "dup"}},
		{"recover", []string{"https://example.com/acme/recover-reg"}},
		{"terms-of-service", []string{"https://example.com/acme/terms"}},
		{"empty", nil},
	}
	for i, test := range tests {
		if v := linkHeader(h, test.rel); !reflect.DeepEqual(v, test.out) {
			t.Errorf("%d: linkHeader(%q): %v; want %v", i, test.rel, v, test.out)
		}
	}
}

func TestTLSSNI01ChallengeCert(t *testing.T) {
	const (
		token = "evaGxfADs6pSRb2LAv9IZf17Dt3juxGJ-PCt92wr-oA"
		// echo -n <token.testKeyECThumbprint> | shasum -a 256
		san = "dbbd5eefe7b4d06eb9d1d9f5acb4c7cd.a27d320e4b30332f0b6cb441734ad7b0.acme.invalid"
	)

	tlscert, name, err := newTestClient().TLSSNI01ChallengeCert(token)
	if err != nil {
		t.Fatal(err)
	}

	if n := len(tlscert.Certificate); n != 1 {
		t.Fatalf("len(tlscert.Certificate) = %d; want 1", n)
	}
	cert, err := x509.ParseCertificate(tlscert.Certificate[0])
	if err != nil {
		t.Fatal(err)
	}
	if len(cert.DNSNames) != 1 || cert.DNSNames[0] != san {
		t.Fatalf("cert.DNSNames = %v; want %q", cert.DNSNames, san)
	}
	if cert.DNSNames[0] != name {
		t.Errorf("cert.DNSNames[0] != name: %q vs %q", cert.DNSNames[0], name)
	}
	if cn := cert.Subject.CommonName; cn != san {
		t.Errorf("cert.Subject.CommonName = %q; want %q", cn, san)
	}
}

func TestTLSSNI02ChallengeCert(t *testing.T) {
	const (
		token = "evaGxfADs6pSRb2LAv9IZf17Dt3juxGJ-PCt92wr-oA"
		// echo -n evaGxfADs6pSRb2LAv9IZf17Dt3juxGJ-PCt92wr-oA | shasum -a 256
		sanA = "7ea0aaa69214e71e02cebb18bb867736.09b730209baabf60e43d4999979ff139.token.acme.invalid"
		// echo -n <token.testKeyECThumbprint> | shasum -a 256
		sanB = "dbbd5eefe7b4d06eb9d1d9f5acb4c7cd.a27d320e4b30332f0b6cb441734ad7b0.ka.acme.invalid"
	)

	tlscert, name, err := newTestClient().TLSSNI02ChallengeCert(token)
	if err != nil {
		t.Fatal(err)
	}

	if n := len(tlscert.Certificate); n != 1 {
		t.Fatalf("len(tlscert.Certificate) = %d; want 1", n)
	}
	cert, err := x509.ParseCertificate(tlscert.Certificate[0])
	if err != nil {
		t.Fatal(err)
	}
	names := []string{sanA, sanB}
	if !reflect.DeepEqual(cert.DNSNames, names) {
		t.Fatalf("cert.DNSNames = %v;\nwant %v", cert.DNSNames, names)
	}
	sort.Strings(cert.DNSNames)
	i := sort.SearchStrings(cert.DNSNames, name)
	if i >= len(cert.DNSNames) || cert.DNSNames[i] != name {
		t.Errorf("%v doesn't have %q", cert.DNSNames, name)
	}
	if cn := cert.Subject.CommonName; cn != sanA {
		t.Errorf("CommonName = %q; want %q", cn, sanA)
	}
}

func TestTLSALPN01ChallengeCert(t *testing.T) {
	const (
		token   = "evaGxfADs6pSRb2LAv9IZf17Dt3juxGJ-PCt92wr-oA"
		keyAuth = "evaGxfADs6pSRb2LAv9IZf17Dt3juxGJ-PCt92wr-oA." + testKeyECThumbprint
		// echo -n <token.testKeyECThumbprint> | shasum -a 256
		h      = "0420dbbd5eefe7b4d06eb9d1d9f5acb4c7cda27d320e4b30332f0b6cb441734ad7b0"
		domain = "example.com"
	)

	extValue, err := hex.DecodeString(h)
	if err != nil {
		t.Fatal(err)
	}

	tlscert, err := newTestClient().TLSALPN01ChallengeCert(token, domain)
	if err != nil {
		t.Fatal(err)
	}

	if n := len(tlscert.Certificate); n != 1 {
		t.Fatalf("len(tlscert.Certificate) = %d; want 1", n)
	}
	cert, err := x509.ParseCertificate(tlscert.Certificate[0])
	if err != nil {
		t.Fatal(err)
	}
	names := []string{domain}
	if !reflect.DeepEqual(cert.DNSNames, names) {
		t.Fatalf("cert.DNSNames = %v;\nwant %v", cert.DNSNames, names)
	}
	if cn := cert.Subject.CommonName; cn != domain {
		t.Errorf("CommonName = %q; want %q", cn, domain)
	}
	acmeExts := []pkix.Extension{}
	for _, ext := range cert.Extensions {
		if idPeACMEIdentifier.Equal(ext.Id) {
			acmeExts = append(acmeExts, ext)
		}
	}
	if len(acmeExts) != 1 {
		t.Errorf("acmeExts = %v; want exactly one", acmeExts)
	}
	if !acmeExts[0].Critical {
		t.Errorf("acmeExt.Critical = %v; want true", acmeExts[0].Critical)
	}
	if bytes.Compare(acmeExts[0].Value, extValue) != 0 {
		t.Errorf("acmeExt.Value = %v; want %v", acmeExts[0].Value, extValue)
	}

}

func TestTLSChallengeCertOpt(t *testing.T) {
	key, err := rsa.GenerateKey(rand.Reader, 512)
	if err != nil {
		t.Fatal(err)
	}
	tmpl := &x509.Certificate{
		SerialNumber: big.NewInt(2),
		Subject:      pkix.Name{Organization: []string{"Test"}},
		DNSNames:     []string{"should-be-overwritten"},
	}
	opts := []CertOption{WithKey(key), WithTemplate(tmpl)}

	client := newTestClient()
	cert1, _, err := client.TLSSNI01ChallengeCert("token", opts...)
	if err != nil {
		t.Fatal(err)
	}
	cert2, _, err := client.TLSSNI02ChallengeCert("token", opts...)
	if err != nil {
		t.Fatal(err)
	}

	for i, tlscert := range []tls.Certificate{cert1, cert2} {
		// verify generated cert private key
		tlskey, ok := tlscert.PrivateKey.(*rsa.PrivateKey)
		if !ok {
			t.Errorf("%d: tlscert.PrivateKey is %T; want *rsa.PrivateKey", i, tlscert.PrivateKey)
			continue
		}
		if tlskey.D.Cmp(key.D) != 0 {
			t.Errorf("%d: tlskey.D = %v; want %v", i, tlskey.D, key.D)
		}
		// verify generated cert public key
		x509Cert, err := x509.ParseCertificate(tlscert.Certificate[0])
		if err != nil {
			t.Errorf("%d: %v", i, err)
			continue
		}
		tlspub, ok := x509Cert.PublicKey.(*rsa.PublicKey)
		if !ok {
			t.Errorf("%d: x509Cert.PublicKey is %T; want *rsa.PublicKey", i, x509Cert.PublicKey)
			continue
		}
		if tlspub.N.Cmp(key.N) != 0 {
			t.Errorf("%d: tlspub.N = %v; want %v", i, tlspub.N, key.N)
		}
		// verify template option
		sn := big.NewInt(2)
		if x509Cert.SerialNumber.Cmp(sn) != 0 {
			t.Errorf("%d: SerialNumber = %v; want %v", i, x509Cert.SerialNumber, sn)
		}
		org := []string{"Test"}
		if !reflect.DeepEqual(x509Cert.Subject.Organization, org) {
			t.Errorf("%d: Subject.Organization = %+v; want %+v", i, x509Cert.Subject.Organization, org)
		}
		for _, v := range x509Cert.DNSNames {
			if !strings.HasSuffix(v, ".acme.invalid") {
				t.Errorf("%d: invalid DNSNames element: %q", i, v)
			}
		}
	}
}

func TestHTTP01Challenge(t *testing.T) {
	const (
		token = "xxx"
		// thumbprint is precomputed for testKeyEC in jws_test.go
		value   = token + "." + testKeyECThumbprint
		urlpath = "/.well-known/acme-challenge/" + token
	)
	client := newTestClient()
	val, err := client.HTTP01ChallengeResponse(token)
	if err != nil {
		t.Fatal(err)
	}
	if val != value {
		t.Errorf("val = %q; want %q", val, value)
	}
	if path := client.HTTP01ChallengePath(token); path != urlpath {
		t.Errorf("path = %q; want %q", path, urlpath)
	}
}

func TestDNS01ChallengeRecord(t *testing.T) {
	// echo -n xxx.<testKeyECThumbprint> | \
	//      openssl dgst -binary -sha256 | \
	//      base64 | tr -d '=' | tr '/+' '_-'
	const value = "8DERMexQ5VcdJ_prpPiA0mVdp7imgbCgjsG4SqqNMIo"

	val, err := newTestClient().DNS01ChallengeRecord("xxx")
	if err != nil {
		t.Fatal(err)
	}
	if val != value {
		t.Errorf("val = %q; want %q", val, value)
	}
}