1// Copyright 2019 The Go Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style
3// license that can be found in the LICENSE file.
4
5package acme
6
7import (
8	"bytes"
9	"context"
10	"crypto/hmac"
11	"crypto/rand"
12	"crypto/sha256"
13	"crypto/x509"
14	"crypto/x509/pkix"
15	"encoding/base64"
16	"encoding/json"
17	"encoding/pem"
18	"fmt"
19	"io/ioutil"
20	"math/big"
21	"net/http"
22	"net/http/httptest"
23	"reflect"
24	"sync"
25	"testing"
26	"time"
27)
28
29// While contents of this file is pertinent only to RFC8555,
30// it is complementary to the tests in the other _test.go files
31// many of which are valid for both pre- and RFC8555.
32// This will make it easier to clean up the tests once non-RFC compliant
33// code is removed.
34
35func TestRFC_Discover(t *testing.T) {
36	const (
37		nonce       = "https://example.com/acme/new-nonce"
38		reg         = "https://example.com/acme/new-acct"
39		order       = "https://example.com/acme/new-order"
40		authz       = "https://example.com/acme/new-authz"
41		revoke      = "https://example.com/acme/revoke-cert"
42		keychange   = "https://example.com/acme/key-change"
43		metaTerms   = "https://example.com/acme/terms/2017-5-30"
44		metaWebsite = "https://www.example.com/"
45		metaCAA     = "example.com"
46	)
47	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
48		w.Header().Set("Content-Type", "application/json")
49		fmt.Fprintf(w, `{
50			"newNonce": %q,
51			"newAccount": %q,
52			"newOrder": %q,
53			"newAuthz": %q,
54			"revokeCert": %q,
55			"keyChange": %q,
56			"meta": {
57				"termsOfService": %q,
58				"website": %q,
59				"caaIdentities": [%q],
60				"externalAccountRequired": true
61			}
62		}`, nonce, reg, order, authz, revoke, keychange, metaTerms, metaWebsite, metaCAA)
63	}))
64	defer ts.Close()
65	c := Client{DirectoryURL: ts.URL}
66	dir, err := c.Discover(context.Background())
67	if err != nil {
68		t.Fatal(err)
69	}
70	if dir.NonceURL != nonce {
71		t.Errorf("dir.NonceURL = %q; want %q", dir.NonceURL, nonce)
72	}
73	if dir.RegURL != reg {
74		t.Errorf("dir.RegURL = %q; want %q", dir.RegURL, reg)
75	}
76	if dir.OrderURL != order {
77		t.Errorf("dir.OrderURL = %q; want %q", dir.OrderURL, order)
78	}
79	if dir.AuthzURL != authz {
80		t.Errorf("dir.AuthzURL = %q; want %q", dir.AuthzURL, authz)
81	}
82	if dir.RevokeURL != revoke {
83		t.Errorf("dir.RevokeURL = %q; want %q", dir.RevokeURL, revoke)
84	}
85	if dir.KeyChangeURL != keychange {
86		t.Errorf("dir.KeyChangeURL = %q; want %q", dir.KeyChangeURL, keychange)
87	}
88	if dir.Terms != metaTerms {
89		t.Errorf("dir.Terms = %q; want %q", dir.Terms, metaTerms)
90	}
91	if dir.Website != metaWebsite {
92		t.Errorf("dir.Website = %q; want %q", dir.Website, metaWebsite)
93	}
94	if len(dir.CAA) == 0 || dir.CAA[0] != metaCAA {
95		t.Errorf("dir.CAA = %q; want [%q]", dir.CAA, metaCAA)
96	}
97	if !dir.ExternalAccountRequired {
98		t.Error("dir.Meta.ExternalAccountRequired is false")
99	}
100}
101
102func TestRFC_popNonce(t *testing.T) {
103	var count int
104	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
105		// The Client uses only Directory.NonceURL when specified.
106		// Expect no other URL paths.
107		if r.URL.Path != "/new-nonce" {
108			t.Errorf("r.URL.Path = %q; want /new-nonce", r.URL.Path)
109		}
110		if count > 0 {
111			w.WriteHeader(http.StatusTooManyRequests)
112			return
113		}
114		count++
115		w.Header().Set("Replay-Nonce", "second")
116	}))
117	cl := &Client{
118		DirectoryURL: ts.URL,
119		dir:          &Directory{NonceURL: ts.URL + "/new-nonce"},
120	}
121	cl.addNonce(http.Header{"Replay-Nonce": {"first"}})
122
123	for i, nonce := range []string{"first", "second"} {
124		v, err := cl.popNonce(context.Background(), "")
125		if err != nil {
126			t.Errorf("%d: cl.popNonce: %v", i, err)
127		}
128		if v != nonce {
129			t.Errorf("%d: cl.popNonce = %q; want %q", i, v, nonce)
130		}
131	}
132	// No more nonces and server replies with an error past first nonce fetch.
133	// Expected to fail.
134	if _, err := cl.popNonce(context.Background(), ""); err == nil {
135		t.Error("last cl.popNonce returned nil error")
136	}
137}
138
139func TestRFC_postKID(t *testing.T) {
140	var ts *httptest.Server
141	ts = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
142		switch r.URL.Path {
143		case "/new-nonce":
144			w.Header().Set("Replay-Nonce", "nonce")
145		case "/new-account":
146			w.Header().Set("Location", "/account-1")
147			w.Write([]byte(`{"status":"valid"}`))
148		case "/post":
149			b, _ := ioutil.ReadAll(r.Body) // check err later in decodeJWSxxx
150			head, err := decodeJWSHead(bytes.NewReader(b))
151			if err != nil {
152				t.Errorf("decodeJWSHead: %v", err)
153				return
154			}
155			if head.KID != "/account-1" {
156				t.Errorf("head.KID = %q; want /account-1", head.KID)
157			}
158			if len(head.JWK) != 0 {
159				t.Errorf("head.JWK = %q; want zero map", head.JWK)
160			}
161			if v := ts.URL + "/post"; head.URL != v {
162				t.Errorf("head.URL = %q; want %q", head.URL, v)
163			}
164
165			var payload struct{ Msg string }
166			decodeJWSRequest(t, &payload, bytes.NewReader(b))
167			if payload.Msg != "ping" {
168				t.Errorf("payload.Msg = %q; want ping", payload.Msg)
169			}
170			w.Write([]byte("pong"))
171		default:
172			t.Errorf("unhandled %s %s", r.Method, r.URL)
173			w.WriteHeader(http.StatusBadRequest)
174		}
175	}))
176	defer ts.Close()
177
178	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
179	defer cancel()
180	cl := &Client{
181		Key:          testKey,
182		DirectoryURL: ts.URL,
183		dir: &Directory{
184			NonceURL: ts.URL + "/new-nonce",
185			RegURL:   ts.URL + "/new-account",
186			OrderURL: "/force-rfc-mode",
187		},
188	}
189	req := json.RawMessage(`{"msg":"ping"}`)
190	res, err := cl.post(ctx, nil /* use kid */, ts.URL+"/post", req, wantStatus(http.StatusOK))
191	if err != nil {
192		t.Fatal(err)
193	}
194	defer res.Body.Close()
195	b, _ := ioutil.ReadAll(res.Body) // don't care about err - just checking b
196	if string(b) != "pong" {
197		t.Errorf("res.Body = %q; want pong", b)
198	}
199}
200
201// acmeServer simulates a subset of RFC8555 compliant CA.
202//
203// TODO: We also have x/crypto/acme/autocert/acmetest and startACMEServerStub in autocert_test.go.
204// It feels like this acmeServer is a sweet spot between usefulness and added complexity.
205// Also, acmetest and startACMEServerStub were both written for draft-02, no RFC support.
206// The goal is to consolidate all into one ACME test server.
207type acmeServer struct {
208	ts      *httptest.Server
209	handler map[string]http.HandlerFunc // keyed by r.URL.Path
210
211	mu     sync.Mutex
212	nnonce int
213}
214
215func newACMEServer() *acmeServer {
216	return &acmeServer{handler: make(map[string]http.HandlerFunc)}
217}
218
219func (s *acmeServer) handle(path string, f func(http.ResponseWriter, *http.Request)) {
220	s.handler[path] = http.HandlerFunc(f)
221}
222
223func (s *acmeServer) start() {
224	s.ts = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
225		w.Header().Set("Content-Type", "application/json")
226
227		// Directory request.
228		if r.URL.Path == "/" {
229			fmt.Fprintf(w, `{
230				"newNonce": %q,
231				"newAccount": %q,
232				"newOrder": %q,
233				"newAuthz": %q,
234				"revokeCert": %q,
235				"meta": {"termsOfService": %q}
236				}`,
237				s.url("/acme/new-nonce"),
238				s.url("/acme/new-account"),
239				s.url("/acme/new-order"),
240				s.url("/acme/new-authz"),
241				s.url("/acme/revoke-cert"),
242				s.url("/terms"),
243			)
244			return
245		}
246
247		// All other responses contain a nonce value unconditionally.
248		w.Header().Set("Replay-Nonce", s.nonce())
249		if r.URL.Path == "/acme/new-nonce" {
250			return
251		}
252
253		h := s.handler[r.URL.Path]
254		if h == nil {
255			w.WriteHeader(http.StatusBadRequest)
256			fmt.Fprintf(w, "Unhandled %s", r.URL.Path)
257			return
258		}
259		h.ServeHTTP(w, r)
260	}))
261}
262
263func (s *acmeServer) close() {
264	s.ts.Close()
265}
266
267func (s *acmeServer) url(path string) string {
268	return s.ts.URL + path
269}
270
271func (s *acmeServer) nonce() string {
272	s.mu.Lock()
273	defer s.mu.Unlock()
274	s.nnonce++
275	return fmt.Sprintf("nonce%d", s.nnonce)
276}
277
278func (s *acmeServer) error(w http.ResponseWriter, e *wireError) {
279	w.WriteHeader(e.Status)
280	json.NewEncoder(w).Encode(e)
281}
282
283func TestRFC_Register(t *testing.T) {
284	const email = "mailto:user@example.org"
285
286	s := newACMEServer()
287	s.handle("/acme/new-account", func(w http.ResponseWriter, r *http.Request) {
288		w.Header().Set("Location", s.url("/accounts/1"))
289		w.WriteHeader(http.StatusCreated) // 201 means new account created
290		fmt.Fprintf(w, `{
291			"status": "valid",
292			"contact": [%q],
293			"orders": %q
294		}`, email, s.url("/accounts/1/orders"))
295
296		b, _ := ioutil.ReadAll(r.Body) // check err later in decodeJWSxxx
297		head, err := decodeJWSHead(bytes.NewReader(b))
298		if err != nil {
299			t.Errorf("decodeJWSHead: %v", err)
300			return
301		}
302		if len(head.JWK) == 0 {
303			t.Error("head.JWK is empty")
304		}
305
306		var req struct{ Contact []string }
307		decodeJWSRequest(t, &req, bytes.NewReader(b))
308		if len(req.Contact) != 1 || req.Contact[0] != email {
309			t.Errorf("req.Contact = %q; want [%q]", req.Contact, email)
310		}
311	})
312	s.start()
313	defer s.close()
314
315	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
316	defer cancel()
317	cl := &Client{
318		Key:          testKeyEC,
319		DirectoryURL: s.url("/"),
320	}
321
322	var didPrompt bool
323	a := &Account{Contact: []string{email}}
324	acct, err := cl.Register(ctx, a, func(tos string) bool {
325		didPrompt = true
326		terms := s.url("/terms")
327		if tos != terms {
328			t.Errorf("tos = %q; want %q", tos, terms)
329		}
330		return true
331	})
332	if err != nil {
333		t.Fatal(err)
334	}
335	okAccount := &Account{
336		URI:       s.url("/accounts/1"),
337		Status:    StatusValid,
338		Contact:   []string{email},
339		OrdersURL: s.url("/accounts/1/orders"),
340	}
341	if !reflect.DeepEqual(acct, okAccount) {
342		t.Errorf("acct = %+v; want %+v", acct, okAccount)
343	}
344	if !didPrompt {
345		t.Error("tos prompt wasn't called")
346	}
347	if v := cl.accountKID(ctx); v != keyID(okAccount.URI) {
348		t.Errorf("account kid = %q; want %q", v, okAccount.URI)
349	}
350}
351
352func TestRFC_RegisterExternalAccountBinding(t *testing.T) {
353	eab := &ExternalAccountBinding{
354		KID: "kid-1",
355		Key: []byte("secret"),
356	}
357
358	type protected struct {
359		Algorithm string `json:"alg"`
360		KID       string `json:"kid"`
361		URL       string `json:"url"`
362	}
363	const email = "mailto:user@example.org"
364
365	s := newACMEServer()
366	s.handle("/acme/new-account", func(w http.ResponseWriter, r *http.Request) {
367		w.Header().Set("Location", s.url("/accounts/1"))
368		if r.Method != "POST" {
369			t.Errorf("r.Method = %q; want POST", r.Method)
370		}
371
372		var j struct {
373			Protected              string
374			Contact                []string
375			TermsOfServiceAgreed   bool
376			ExternalaccountBinding struct {
377				Protected string
378				Payload   string
379				Signature string
380			}
381		}
382		decodeJWSRequest(t, &j, r.Body)
383		protData, err := base64.RawURLEncoding.DecodeString(j.ExternalaccountBinding.Protected)
384		if err != nil {
385			t.Fatal(err)
386		}
387
388		var prot protected
389		err = json.Unmarshal(protData, &prot)
390		if err != nil {
391			t.Fatal(err)
392		}
393
394		if !reflect.DeepEqual(j.Contact, []string{email}) {
395			t.Errorf("j.Contact = %v; want %v", j.Contact, []string{email})
396		}
397		if !j.TermsOfServiceAgreed {
398			t.Error("j.TermsOfServiceAgreed = false; want true")
399		}
400
401		// Ensure same KID.
402		if prot.KID != eab.KID {
403			t.Errorf("j.ExternalAccountBinding.KID = %s; want %s", prot.KID, eab.KID)
404		}
405		// Ensure expected Algorithm.
406		if prot.Algorithm != "HS256" {
407			t.Errorf("j.ExternalAccountBinding.Alg = %s; want %s",
408				prot.Algorithm, "HS256")
409		}
410
411		// Ensure same URL as outer JWS.
412		url := fmt.Sprintf("http://%s/acme/new-account", r.Host)
413		if prot.URL != url {
414			t.Errorf("j.ExternalAccountBinding.URL = %s; want %s",
415				prot.URL, url)
416		}
417
418		// Ensure payload is base64URL encoded string of JWK in outer JWS
419		jwk, err := jwkEncode(testKeyEC.Public())
420		if err != nil {
421			t.Fatal(err)
422		}
423		decodedPayload, err := base64.RawURLEncoding.DecodeString(j.ExternalaccountBinding.Payload)
424		if err != nil {
425			t.Fatal(err)
426		}
427		if jwk != string(decodedPayload) {
428			t.Errorf("j.ExternalAccountBinding.Payload = %s; want %s", decodedPayload, jwk)
429		}
430
431		// Check signature on inner external account binding JWS
432		hmac := hmac.New(sha256.New, []byte("secret"))
433		_, err = hmac.Write([]byte(j.ExternalaccountBinding.Protected + "." + j.ExternalaccountBinding.Payload))
434		if err != nil {
435			t.Fatal(err)
436		}
437		mac := hmac.Sum(nil)
438		encodedMAC := base64.RawURLEncoding.EncodeToString(mac)
439
440		if !bytes.Equal([]byte(encodedMAC), []byte(j.ExternalaccountBinding.Signature)) {
441			t.Errorf("j.ExternalAccountBinding.Signature = %v; want %v",
442				[]byte(j.ExternalaccountBinding.Signature), encodedMAC)
443		}
444
445		w.Header().Set("Location", s.url("/accounts/1"))
446		w.WriteHeader(http.StatusCreated)
447		b, _ := json.Marshal([]string{email})
448		fmt.Fprintf(w, `{"status":"valid","orders":"%s","contact":%s}`, s.url("/accounts/1/orders"), b)
449	})
450	s.start()
451	defer s.close()
452
453	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
454	defer cancel()
455	cl := &Client{
456		Key:          testKeyEC,
457		DirectoryURL: s.url("/"),
458	}
459
460	var didPrompt bool
461	a := &Account{Contact: []string{email}, ExternalAccountBinding: eab}
462	acct, err := cl.Register(ctx, a, func(tos string) bool {
463		didPrompt = true
464		terms := s.url("/terms")
465		if tos != terms {
466			t.Errorf("tos = %q; want %q", tos, terms)
467		}
468		return true
469	})
470	if err != nil {
471		t.Fatal(err)
472	}
473	okAccount := &Account{
474		URI:       s.url("/accounts/1"),
475		Status:    StatusValid,
476		Contact:   []string{email},
477		OrdersURL: s.url("/accounts/1/orders"),
478	}
479	if !reflect.DeepEqual(acct, okAccount) {
480		t.Errorf("acct = %+v; want %+v", acct, okAccount)
481	}
482	if !didPrompt {
483		t.Error("tos prompt wasn't called")
484	}
485	if v := cl.accountKID(ctx); v != keyID(okAccount.URI) {
486		t.Errorf("account kid = %q; want %q", v, okAccount.URI)
487	}
488}
489
490func TestRFC_RegisterExisting(t *testing.T) {
491	s := newACMEServer()
492	s.handle("/acme/new-account", func(w http.ResponseWriter, r *http.Request) {
493		w.Header().Set("Location", s.url("/accounts/1"))
494		w.WriteHeader(http.StatusOK) // 200 means account already exists
495		w.Write([]byte(`{"status": "valid"}`))
496	})
497	s.start()
498	defer s.close()
499
500	cl := &Client{Key: testKeyEC, DirectoryURL: s.url("/")}
501	_, err := cl.Register(context.Background(), &Account{}, AcceptTOS)
502	if err != ErrAccountAlreadyExists {
503		t.Errorf("err = %v; want %v", err, ErrAccountAlreadyExists)
504	}
505	kid := keyID(s.url("/accounts/1"))
506	if v := cl.accountKID(context.Background()); v != kid {
507		t.Errorf("account kid = %q; want %q", v, kid)
508	}
509}
510
511func TestRFC_UpdateReg(t *testing.T) {
512	const email = "mailto:user@example.org"
513
514	s := newACMEServer()
515	s.handle("/acme/new-account", func(w http.ResponseWriter, r *http.Request) {
516		w.Header().Set("Location", s.url("/accounts/1"))
517		w.WriteHeader(http.StatusOK)
518		w.Write([]byte(`{"status": "valid"}`))
519	})
520	var didUpdate bool
521	s.handle("/accounts/1", func(w http.ResponseWriter, r *http.Request) {
522		didUpdate = true
523		w.Header().Set("Location", s.url("/accounts/1"))
524		w.WriteHeader(http.StatusOK)
525		w.Write([]byte(`{"status": "valid"}`))
526
527		b, _ := ioutil.ReadAll(r.Body) // check err later in decodeJWSxxx
528		head, err := decodeJWSHead(bytes.NewReader(b))
529		if err != nil {
530			t.Errorf("decodeJWSHead: %v", err)
531			return
532		}
533		if len(head.JWK) != 0 {
534			t.Error("head.JWK is non-zero")
535		}
536		kid := s.url("/accounts/1")
537		if head.KID != kid {
538			t.Errorf("head.KID = %q; want %q", head.KID, kid)
539		}
540
541		var req struct{ Contact []string }
542		decodeJWSRequest(t, &req, bytes.NewReader(b))
543		if len(req.Contact) != 1 || req.Contact[0] != email {
544			t.Errorf("req.Contact = %q; want [%q]", req.Contact, email)
545		}
546	})
547	s.start()
548	defer s.close()
549
550	cl := &Client{Key: testKeyEC, DirectoryURL: s.url("/")}
551	_, err := cl.UpdateReg(context.Background(), &Account{Contact: []string{email}})
552	if err != nil {
553		t.Error(err)
554	}
555	if !didUpdate {
556		t.Error("UpdateReg didn't update the account")
557	}
558}
559
560func TestRFC_GetReg(t *testing.T) {
561	s := newACMEServer()
562	s.handle("/acme/new-account", func(w http.ResponseWriter, r *http.Request) {
563		w.Header().Set("Location", s.url("/accounts/1"))
564		w.WriteHeader(http.StatusOK)
565		w.Write([]byte(`{"status": "valid"}`))
566
567		head, err := decodeJWSHead(r.Body)
568		if err != nil {
569			t.Errorf("decodeJWSHead: %v", err)
570			return
571		}
572		if len(head.JWK) == 0 {
573			t.Error("head.JWK is empty")
574		}
575	})
576	s.start()
577	defer s.close()
578
579	cl := &Client{Key: testKeyEC, DirectoryURL: s.url("/")}
580	acct, err := cl.GetReg(context.Background(), "")
581	if err != nil {
582		t.Fatal(err)
583	}
584	okAccount := &Account{
585		URI:    s.url("/accounts/1"),
586		Status: StatusValid,
587	}
588	if !reflect.DeepEqual(acct, okAccount) {
589		t.Errorf("acct = %+v; want %+v", acct, okAccount)
590	}
591}
592
593func TestRFC_GetRegNoAccount(t *testing.T) {
594	s := newACMEServer()
595	s.handle("/acme/new-account", func(w http.ResponseWriter, r *http.Request) {
596		s.error(w, &wireError{
597			Status: http.StatusBadRequest,
598			Type:   "urn:ietf:params:acme:error:accountDoesNotExist",
599		})
600	})
601	s.start()
602	defer s.close()
603
604	cl := &Client{Key: testKeyEC, DirectoryURL: s.url("/")}
605	if _, err := cl.GetReg(context.Background(), ""); err != ErrNoAccount {
606		t.Errorf("err = %v; want %v", err, ErrNoAccount)
607	}
608}
609
610func TestRFC_GetRegOtherError(t *testing.T) {
611	s := newACMEServer()
612	s.handle("/acme/new-account", func(w http.ResponseWriter, r *http.Request) {
613		w.WriteHeader(http.StatusBadRequest)
614	})
615	s.start()
616	defer s.close()
617
618	cl := &Client{Key: testKeyEC, DirectoryURL: s.url("/")}
619	if _, err := cl.GetReg(context.Background(), ""); err == nil || err == ErrNoAccount {
620		t.Errorf("GetReg: %v; want any other non-nil err", err)
621	}
622}
623
624func TestRFC_AuthorizeOrder(t *testing.T) {
625	s := newACMEServer()
626	s.handle("/acme/new-account", func(w http.ResponseWriter, r *http.Request) {
627		w.Header().Set("Location", s.url("/accounts/1"))
628		w.WriteHeader(http.StatusOK)
629		w.Write([]byte(`{"status": "valid"}`))
630	})
631	s.handle("/acme/new-order", func(w http.ResponseWriter, r *http.Request) {
632		w.Header().Set("Location", s.url("/orders/1"))
633		w.WriteHeader(http.StatusCreated)
634		fmt.Fprintf(w, `{
635			"status": "pending",
636			"expires": "2019-09-01T00:00:00Z",
637			"notBefore": "2019-08-31T00:00:00Z",
638			"notAfter": "2019-09-02T00:00:00Z",
639			"identifiers": [{"type":"dns", "value":"example.org"}],
640			"authorizations": [%q]
641		}`, s.url("/authz/1"))
642	})
643	s.start()
644	defer s.close()
645
646	cl := &Client{Key: testKeyEC, DirectoryURL: s.url("/")}
647	o, err := cl.AuthorizeOrder(context.Background(), DomainIDs("example.org"),
648		WithOrderNotBefore(time.Date(2019, 8, 31, 0, 0, 0, 0, time.UTC)),
649		WithOrderNotAfter(time.Date(2019, 9, 2, 0, 0, 0, 0, time.UTC)),
650	)
651	if err != nil {
652		t.Fatal(err)
653	}
654	okOrder := &Order{
655		URI:         s.url("/orders/1"),
656		Status:      StatusPending,
657		Expires:     time.Date(2019, 9, 1, 0, 0, 0, 0, time.UTC),
658		NotBefore:   time.Date(2019, 8, 31, 0, 0, 0, 0, time.UTC),
659		NotAfter:    time.Date(2019, 9, 2, 0, 0, 0, 0, time.UTC),
660		Identifiers: []AuthzID{AuthzID{Type: "dns", Value: "example.org"}},
661		AuthzURLs:   []string{s.url("/authz/1")},
662	}
663	if !reflect.DeepEqual(o, okOrder) {
664		t.Errorf("AuthorizeOrder = %+v; want %+v", o, okOrder)
665	}
666}
667
668func TestRFC_GetOrder(t *testing.T) {
669	s := newACMEServer()
670	s.handle("/acme/new-account", func(w http.ResponseWriter, r *http.Request) {
671		w.Header().Set("Location", s.url("/accounts/1"))
672		w.WriteHeader(http.StatusOK)
673		w.Write([]byte(`{"status": "valid"}`))
674	})
675	s.handle("/orders/1", func(w http.ResponseWriter, r *http.Request) {
676		w.Header().Set("Location", s.url("/orders/1"))
677		w.WriteHeader(http.StatusOK)
678		w.Write([]byte(`{
679			"status": "invalid",
680			"expires": "2019-09-01T00:00:00Z",
681			"notBefore": "2019-08-31T00:00:00Z",
682			"notAfter": "2019-09-02T00:00:00Z",
683			"identifiers": [{"type":"dns", "value":"example.org"}],
684			"authorizations": ["/authz/1"],
685			"finalize": "/orders/1/fin",
686			"certificate": "/orders/1/cert",
687			"error": {"type": "badRequest"}
688		}`))
689	})
690	s.start()
691	defer s.close()
692
693	cl := &Client{Key: testKeyEC, DirectoryURL: s.url("/")}
694	o, err := cl.GetOrder(context.Background(), s.url("/orders/1"))
695	if err != nil {
696		t.Fatal(err)
697	}
698	okOrder := &Order{
699		URI:         s.url("/orders/1"),
700		Status:      StatusInvalid,
701		Expires:     time.Date(2019, 9, 1, 0, 0, 0, 0, time.UTC),
702		NotBefore:   time.Date(2019, 8, 31, 0, 0, 0, 0, time.UTC),
703		NotAfter:    time.Date(2019, 9, 2, 0, 0, 0, 0, time.UTC),
704		Identifiers: []AuthzID{AuthzID{Type: "dns", Value: "example.org"}},
705		AuthzURLs:   []string{"/authz/1"},
706		FinalizeURL: "/orders/1/fin",
707		CertURL:     "/orders/1/cert",
708		Error:       &Error{ProblemType: "badRequest"},
709	}
710	if !reflect.DeepEqual(o, okOrder) {
711		t.Errorf("GetOrder = %+v\nwant %+v", o, okOrder)
712	}
713}
714
715func TestRFC_WaitOrder(t *testing.T) {
716	for _, st := range []string{StatusReady, StatusValid} {
717		t.Run(st, func(t *testing.T) {
718			testWaitOrderStatus(t, st)
719		})
720	}
721}
722
723func testWaitOrderStatus(t *testing.T, okStatus string) {
724	s := newACMEServer()
725	s.handle("/acme/new-account", func(w http.ResponseWriter, r *http.Request) {
726		w.Header().Set("Location", s.url("/accounts/1"))
727		w.WriteHeader(http.StatusOK)
728		w.Write([]byte(`{"status": "valid"}`))
729	})
730	var count int
731	s.handle("/orders/1", func(w http.ResponseWriter, r *http.Request) {
732		w.Header().Set("Location", s.url("/orders/1"))
733		w.WriteHeader(http.StatusOK)
734		s := StatusPending
735		if count > 0 {
736			s = okStatus
737		}
738		fmt.Fprintf(w, `{"status": %q}`, s)
739		count++
740	})
741	s.start()
742	defer s.close()
743
744	var order *Order
745	var err error
746	done := make(chan struct{})
747	go func() {
748		cl := &Client{Key: testKeyEC, DirectoryURL: s.url("/")}
749		order, err = cl.WaitOrder(context.Background(), s.url("/orders/1"))
750		close(done)
751	}()
752	select {
753	case <-time.After(3 * time.Second):
754		t.Fatal("WaitOrder took too long to return")
755	case <-done:
756		if err != nil {
757			t.Fatalf("WaitOrder: %v", err)
758		}
759		if order.Status != okStatus {
760			t.Errorf("order.Status = %q; want %q", order.Status, okStatus)
761		}
762	}
763}
764
765func TestRFC_WaitOrderError(t *testing.T) {
766	s := newACMEServer()
767	s.handle("/acme/new-account", func(w http.ResponseWriter, r *http.Request) {
768		w.Header().Set("Location", s.url("/accounts/1"))
769		w.WriteHeader(http.StatusOK)
770		w.Write([]byte(`{"status": "valid"}`))
771	})
772	var count int
773	s.handle("/orders/1", func(w http.ResponseWriter, r *http.Request) {
774		w.Header().Set("Location", s.url("/orders/1"))
775		w.WriteHeader(http.StatusOK)
776		s := StatusPending
777		if count > 0 {
778			s = StatusInvalid
779		}
780		fmt.Fprintf(w, `{"status": %q}`, s)
781		count++
782	})
783	s.start()
784	defer s.close()
785
786	var err error
787	done := make(chan struct{})
788	go func() {
789		cl := &Client{Key: testKeyEC, DirectoryURL: s.url("/")}
790		_, err = cl.WaitOrder(context.Background(), s.url("/orders/1"))
791		close(done)
792	}()
793	select {
794	case <-time.After(3 * time.Second):
795		t.Fatal("WaitOrder took too long to return")
796	case <-done:
797		if err == nil {
798			t.Fatal("WaitOrder returned nil error")
799		}
800		e, ok := err.(*OrderError)
801		if !ok {
802			t.Fatalf("err = %v (%T); want OrderError", err, err)
803		}
804		if e.OrderURL != s.url("/orders/1") {
805			t.Errorf("e.OrderURL = %q; want %q", e.OrderURL, s.url("/orders/1"))
806		}
807		if e.Status != StatusInvalid {
808			t.Errorf("e.Status = %q; want %q", e.Status, StatusInvalid)
809		}
810	}
811}
812
813func TestRFC_CreateOrderCert(t *testing.T) {
814	q := &x509.CertificateRequest{
815		Subject: pkix.Name{CommonName: "example.org"},
816	}
817	csr, err := x509.CreateCertificateRequest(rand.Reader, q, testKeyEC)
818	if err != nil {
819		t.Fatal(err)
820	}
821
822	tmpl := &x509.Certificate{SerialNumber: big.NewInt(1)}
823	leaf, err := x509.CreateCertificate(rand.Reader, tmpl, tmpl, &testKeyEC.PublicKey, testKeyEC)
824	if err != nil {
825		t.Fatal(err)
826	}
827
828	s := newACMEServer()
829	s.handle("/acme/new-account", func(w http.ResponseWriter, r *http.Request) {
830		w.Header().Set("Location", s.url("/accounts/1"))
831		w.Write([]byte(`{"status": "valid"}`))
832	})
833	var count int
834	s.handle("/pleaseissue", func(w http.ResponseWriter, r *http.Request) {
835		w.Header().Set("Location", s.url("/pleaseissue"))
836		st := StatusProcessing
837		if count > 0 {
838			st = StatusValid
839		}
840		fmt.Fprintf(w, `{"status":%q, "certificate":%q}`, st, s.url("/crt"))
841		count++
842	})
843	s.handle("/crt", func(w http.ResponseWriter, r *http.Request) {
844		w.Header().Set("Content-Type", "application/pem-certificate-chain")
845		pem.Encode(w, &pem.Block{Type: "CERTIFICATE", Bytes: leaf})
846	})
847	s.start()
848	defer s.close()
849	ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
850	defer cancel()
851
852	cl := &Client{Key: testKeyEC, DirectoryURL: s.url("/")}
853	cert, curl, err := cl.CreateOrderCert(ctx, s.url("/pleaseissue"), csr, true)
854	if err != nil {
855		t.Fatalf("CreateOrderCert: %v", err)
856	}
857	if _, err := x509.ParseCertificate(cert[0]); err != nil {
858		t.Errorf("ParseCertificate: %v", err)
859	}
860	if !reflect.DeepEqual(cert[0], leaf) {
861		t.Errorf("cert and leaf bytes don't match")
862	}
863	if u := s.url("/crt"); curl != u {
864		t.Errorf("curl = %q; want %q", curl, u)
865	}
866}
867
868func TestRFC_AlreadyRevokedCert(t *testing.T) {
869	s := newACMEServer()
870	s.handle("/acme/revoke-cert", func(w http.ResponseWriter, r *http.Request) {
871		s.error(w, &wireError{
872			Status: http.StatusBadRequest,
873			Type:   "urn:ietf:params:acme:error:alreadyRevoked",
874		})
875	})
876	s.start()
877	defer s.close()
878
879	cl := &Client{Key: testKeyEC, DirectoryURL: s.url("/")}
880	err := cl.RevokeCert(context.Background(), testKeyEC, []byte{0}, CRLReasonUnspecified)
881	if err != nil {
882		t.Fatalf("RevokeCert: %v", err)
883	}
884}
885
886func TestRFC_ListCertAlternates(t *testing.T) {
887	s := newACMEServer()
888	s.handle("/crt", func(w http.ResponseWriter, r *http.Request) {
889		w.Header().Set("Content-Type", "application/pem-certificate-chain")
890		w.Header().Add("Link", `<https://example.com/crt/2>;rel="alternate"`)
891		w.Header().Add("Link", `<https://example.com/crt/3>; rel="alternate"`)
892		w.Header().Add("Link", `<https://example.com/acme>; rel="index"`)
893	})
894	s.handle("/crt2", func(w http.ResponseWriter, r *http.Request) {
895		w.Header().Set("Content-Type", "application/pem-certificate-chain")
896	})
897	s.start()
898	defer s.close()
899
900	cl := &Client{Key: testKeyEC, DirectoryURL: s.url("/")}
901	crts, err := cl.ListCertAlternates(context.Background(), s.url("/crt"))
902	if err != nil {
903		t.Fatalf("ListCertAlternates: %v", err)
904	}
905	want := []string{"https://example.com/crt/2", "https://example.com/crt/3"}
906	if !reflect.DeepEqual(crts, want) {
907		t.Errorf("ListCertAlternates(/crt): %v; want %v", crts, want)
908	}
909	crts, err = cl.ListCertAlternates(context.Background(), s.url("/crt2"))
910	if err != nil {
911		t.Fatalf("ListCertAlternates: %v", err)
912	}
913	if crts != nil {
914		t.Errorf("ListCertAlternates(/crt2): %v; want nil", crts)
915	}
916}
917