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