1 // Copyright 2015 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.
29 // Decodes a JWS-encoded request and unmarshals the decoded JSON into a provided
31 func decodeJWSRequest(t *testing.T, v interface{}, r *http.Request) {
33 var req struct{ Payload string }
34 if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
37 payload, err := base64.RawURLEncoding.DecodeString(req.Payload)
41 err = json.Unmarshal(payload, v)
50 JWK map[string]string `json:"jwk"`
53 func decodeJWSHead(r *http.Request) (*jwsHead, error) {
54 var req struct{ Protected string }
55 if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
58 b, err := base64.RawURLEncoding.DecodeString(req.Protected)
63 if err := json.Unmarshal(b, &head); err != nil {
69 func TestDiscover(t *testing.T) {
71 reg = "https://example.com/acme/new-reg"
72 authz = "https://example.com/acme/new-authz"
73 cert = "https://example.com/acme/new-cert"
74 revoke = "https://example.com/acme/revoke-cert"
76 ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
77 w.Header().Set("Content-Type", "application/json")
83 }`, reg, authz, cert, revoke)
86 c := Client{DirectoryURL: ts.URL}
87 dir, err := c.Discover(context.Background())
91 if dir.RegURL != reg {
92 t.Errorf("dir.RegURL = %q; want %q", dir.RegURL, reg)
94 if dir.AuthzURL != authz {
95 t.Errorf("dir.AuthzURL = %q; want %q", dir.AuthzURL, authz)
97 if dir.CertURL != cert {
98 t.Errorf("dir.CertURL = %q; want %q", dir.CertURL, cert)
100 if dir.RevokeURL != revoke {
101 t.Errorf("dir.RevokeURL = %q; want %q", dir.RevokeURL, revoke)
105 func TestRegister(t *testing.T) {
106 contacts := []string{"mailto:admin@example.com"}
108 ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
109 if r.Method == "HEAD" {
110 w.Header().Set("Replay-Nonce", "test-nonce")
113 if r.Method != "POST" {
114 t.Errorf("r.Method = %q; want POST", r.Method)
122 decodeJWSRequest(t, &j, r)
125 if j.Resource != "new-reg" {
126 t.Errorf("j.Resource = %q; want new-reg", j.Resource)
128 if !reflect.DeepEqual(j.Contact, contacts) {
129 t.Errorf("j.Contact = %v; want %v", j.Contact, contacts)
132 w.Header().Set("Location", "https://ca.tld/acme/reg/1")
133 w.Header().Set("Link", `<https://ca.tld/acme/new-authz>;rel="next"`)
134 w.Header().Add("Link", `<https://ca.tld/acme/recover-reg>;rel="recover"`)
135 w.Header().Add("Link", `<https://ca.tld/acme/terms>;rel="terms-of-service"`)
136 w.WriteHeader(http.StatusCreated)
137 b, _ := json.Marshal(contacts)
138 fmt.Fprintf(w, `{"contact": %s}`, b)
142 prompt := func(url string) bool {
143 const terms = "https://ca.tld/acme/terms"
145 t.Errorf("prompt url = %q; want %q", url, terms)
150 c := Client{Key: testKeyEC, dir: &Directory{RegURL: ts.URL}}
151 a := &Account{Contact: contacts}
153 if a, err = c.Register(context.Background(), a, prompt); err != nil {
156 if a.URI != "https://ca.tld/acme/reg/1" {
157 t.Errorf("a.URI = %q; want https://ca.tld/acme/reg/1", a.URI)
159 if a.Authz != "https://ca.tld/acme/new-authz" {
160 t.Errorf("a.Authz = %q; want https://ca.tld/acme/new-authz", a.Authz)
162 if a.CurrentTerms != "https://ca.tld/acme/terms" {
163 t.Errorf("a.CurrentTerms = %q; want https://ca.tld/acme/terms", a.CurrentTerms)
165 if !reflect.DeepEqual(a.Contact, contacts) {
166 t.Errorf("a.Contact = %v; want %v", a.Contact, contacts)
170 func TestUpdateReg(t *testing.T) {
171 const terms = "https://ca.tld/acme/terms"
172 contacts := []string{"mailto:admin@example.com"}
174 ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
175 if r.Method == "HEAD" {
176 w.Header().Set("Replay-Nonce", "test-nonce")
179 if r.Method != "POST" {
180 t.Errorf("r.Method = %q; want POST", r.Method)
188 decodeJWSRequest(t, &j, r)
191 if j.Resource != "reg" {
192 t.Errorf("j.Resource = %q; want reg", j.Resource)
194 if j.Agreement != terms {
195 t.Errorf("j.Agreement = %q; want %q", j.Agreement, terms)
197 if !reflect.DeepEqual(j.Contact, contacts) {
198 t.Errorf("j.Contact = %v; want %v", j.Contact, contacts)
201 w.Header().Set("Link", `<https://ca.tld/acme/new-authz>;rel="next"`)
202 w.Header().Add("Link", `<https://ca.tld/acme/recover-reg>;rel="recover"`)
203 w.Header().Add("Link", fmt.Sprintf(`<%s>;rel="terms-of-service"`, terms))
204 w.WriteHeader(http.StatusOK)
205 b, _ := json.Marshal(contacts)
206 fmt.Fprintf(w, `{"contact":%s, "agreement":%q}`, b, terms)
210 c := Client{Key: testKeyEC}
211 a := &Account{URI: ts.URL, Contact: contacts, AgreedTerms: terms}
213 if a, err = c.UpdateReg(context.Background(), a); err != nil {
216 if a.Authz != "https://ca.tld/acme/new-authz" {
217 t.Errorf("a.Authz = %q; want https://ca.tld/acme/new-authz", a.Authz)
219 if a.AgreedTerms != terms {
220 t.Errorf("a.AgreedTerms = %q; want %q", a.AgreedTerms, terms)
222 if a.CurrentTerms != terms {
223 t.Errorf("a.CurrentTerms = %q; want %q", a.CurrentTerms, terms)
226 t.Errorf("a.URI = %q; want %q", a.URI, ts.URL)
230 func TestGetReg(t *testing.T) {
231 const terms = "https://ca.tld/acme/terms"
232 const newTerms = "https://ca.tld/acme/new-terms"
233 contacts := []string{"mailto:admin@example.com"}
235 ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
236 if r.Method == "HEAD" {
237 w.Header().Set("Replay-Nonce", "test-nonce")
240 if r.Method != "POST" {
241 t.Errorf("r.Method = %q; want POST", r.Method)
249 decodeJWSRequest(t, &j, r)
252 if j.Resource != "reg" {
253 t.Errorf("j.Resource = %q; want reg", j.Resource)
255 if len(j.Contact) != 0 {
256 t.Errorf("j.Contact = %v", j.Contact)
258 if j.Agreement != "" {
259 t.Errorf("j.Agreement = %q", j.Agreement)
262 w.Header().Set("Link", `<https://ca.tld/acme/new-authz>;rel="next"`)
263 w.Header().Add("Link", `<https://ca.tld/acme/recover-reg>;rel="recover"`)
264 w.Header().Add("Link", fmt.Sprintf(`<%s>;rel="terms-of-service"`, newTerms))
265 w.WriteHeader(http.StatusOK)
266 b, _ := json.Marshal(contacts)
267 fmt.Fprintf(w, `{"contact":%s, "agreement":%q}`, b, terms)
271 c := Client{Key: testKeyEC}
272 a, err := c.GetReg(context.Background(), ts.URL)
276 if a.Authz != "https://ca.tld/acme/new-authz" {
277 t.Errorf("a.AuthzURL = %q; want https://ca.tld/acme/new-authz", a.Authz)
279 if a.AgreedTerms != terms {
280 t.Errorf("a.AgreedTerms = %q; want %q", a.AgreedTerms, terms)
282 if a.CurrentTerms != newTerms {
283 t.Errorf("a.CurrentTerms = %q; want %q", a.CurrentTerms, newTerms)
286 t.Errorf("a.URI = %q; want %q", a.URI, ts.URL)
290 func TestAuthorize(t *testing.T) {
291 ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
292 if r.Method == "HEAD" {
293 w.Header().Set("Replay-Nonce", "test-nonce")
296 if r.Method != "POST" {
297 t.Errorf("r.Method = %q; want POST", r.Method)
307 decodeJWSRequest(t, &j, r)
310 if j.Resource != "new-authz" {
311 t.Errorf("j.Resource = %q; want new-authz", j.Resource)
313 if j.Identifier.Type != "dns" {
314 t.Errorf("j.Identifier.Type = %q; want dns", j.Identifier.Type)
316 if j.Identifier.Value != "example.com" {
317 t.Errorf("j.Identifier.Value = %q; want example.com", j.Identifier.Value)
320 w.Header().Set("Location", "https://ca.tld/acme/auth/1")
321 w.WriteHeader(http.StatusCreated)
323 "identifier": {"type":"dns","value":"example.com"},
329 "uri":"https://ca.tld/acme/challenge/publickey/id1",
335 "uri":"https://ca.tld/acme/challenge/publickey/id2",
339 "combinations":[[0],[1]]}`)
343 cl := Client{Key: testKeyEC, dir: &Directory{AuthzURL: ts.URL}}
344 auth, err := cl.Authorize(context.Background(), "example.com")
349 if auth.URI != "https://ca.tld/acme/auth/1" {
350 t.Errorf("URI = %q; want https://ca.tld/acme/auth/1", auth.URI)
352 if auth.Status != "pending" {
353 t.Errorf("Status = %q; want pending", auth.Status)
355 if auth.Identifier.Type != "dns" {
356 t.Errorf("Identifier.Type = %q; want dns", auth.Identifier.Type)
358 if auth.Identifier.Value != "example.com" {
359 t.Errorf("Identifier.Value = %q; want example.com", auth.Identifier.Value)
362 if n := len(auth.Challenges); n != 2 {
363 t.Fatalf("len(auth.Challenges) = %d; want 2", n)
366 c := auth.Challenges[0]
367 if c.Type != "http-01" {
368 t.Errorf("c.Type = %q; want http-01", c.Type)
370 if c.URI != "https://ca.tld/acme/challenge/publickey/id1" {
371 t.Errorf("c.URI = %q; want https://ca.tld/acme/challenge/publickey/id1", c.URI)
373 if c.Token != "token1" {
374 t.Errorf("c.Token = %q; want token1", c.Token)
377 c = auth.Challenges[1]
378 if c.Type != "tls-sni-01" {
379 t.Errorf("c.Type = %q; want tls-sni-01", c.Type)
381 if c.URI != "https://ca.tld/acme/challenge/publickey/id2" {
382 t.Errorf("c.URI = %q; want https://ca.tld/acme/challenge/publickey/id2", c.URI)
384 if c.Token != "token2" {
385 t.Errorf("c.Token = %q; want token2", c.Token)
388 combs := [][]int{{0}, {1}}
389 if !reflect.DeepEqual(auth.Combinations, combs) {
390 t.Errorf("auth.Combinations: %+v\nwant: %+v\n", auth.Combinations, combs)
394 func TestAuthorizeValid(t *testing.T) {
395 ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
396 if r.Method == "HEAD" {
397 w.Header().Set("Replay-Nonce", "nonce")
400 w.WriteHeader(http.StatusCreated)
401 w.Write([]byte(`{"status":"valid"}`))
404 client := Client{Key: testKey, dir: &Directory{AuthzURL: ts.URL}}
405 _, err := client.Authorize(context.Background(), "example.com")
407 t.Errorf("err = %v", err)
411 func TestGetAuthorization(t *testing.T) {
412 ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
413 if r.Method != "GET" {
414 t.Errorf("r.Method = %q; want GET", r.Method)
417 w.WriteHeader(http.StatusOK)
419 "identifier": {"type":"dns","value":"example.com"},
425 "uri":"https://ca.tld/acme/challenge/publickey/id1",
431 "uri":"https://ca.tld/acme/challenge/publickey/id2",
435 "combinations":[[0],[1]]}`)
439 cl := Client{Key: testKeyEC}
440 auth, err := cl.GetAuthorization(context.Background(), ts.URL)
445 if auth.Status != "pending" {
446 t.Errorf("Status = %q; want pending", auth.Status)
448 if auth.Identifier.Type != "dns" {
449 t.Errorf("Identifier.Type = %q; want dns", auth.Identifier.Type)
451 if auth.Identifier.Value != "example.com" {
452 t.Errorf("Identifier.Value = %q; want example.com", auth.Identifier.Value)
455 if n := len(auth.Challenges); n != 2 {
456 t.Fatalf("len(set.Challenges) = %d; want 2", n)
459 c := auth.Challenges[0]
460 if c.Type != "http-01" {
461 t.Errorf("c.Type = %q; want http-01", c.Type)
463 if c.URI != "https://ca.tld/acme/challenge/publickey/id1" {
464 t.Errorf("c.URI = %q; want https://ca.tld/acme/challenge/publickey/id1", c.URI)
466 if c.Token != "token1" {
467 t.Errorf("c.Token = %q; want token1", c.Token)
470 c = auth.Challenges[1]
471 if c.Type != "tls-sni-01" {
472 t.Errorf("c.Type = %q; want tls-sni-01", c.Type)
474 if c.URI != "https://ca.tld/acme/challenge/publickey/id2" {
475 t.Errorf("c.URI = %q; want https://ca.tld/acme/challenge/publickey/id2", c.URI)
477 if c.Token != "token2" {
478 t.Errorf("c.Token = %q; want token2", c.Token)
481 combs := [][]int{{0}, {1}}
482 if !reflect.DeepEqual(auth.Combinations, combs) {
483 t.Errorf("auth.Combinations: %+v\nwant: %+v\n", auth.Combinations, combs)
487 func TestWaitAuthorization(t *testing.T) {
489 ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
491 w.Header().Set("Retry-After", "0")
493 fmt.Fprintf(w, `{"status":"valid"}`)
496 fmt.Fprintf(w, `{"status":"pending"}`)
504 done := make(chan res)
508 a, err := client.WaitAuthorization(context.Background(), ts.URL)
513 case <-time.After(5 * time.Second):
514 t.Fatal("WaitAuthz took too long to return")
517 t.Fatalf("res.err = %v", res.err)
519 if res.authz == nil {
520 t.Fatal("res.authz is nil")
525 func TestWaitAuthorizationInvalid(t *testing.T) {
526 ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
527 fmt.Fprintf(w, `{"status":"invalid"}`)
531 res := make(chan error)
535 _, err := client.WaitAuthorization(context.Background(), ts.URL)
540 case <-time.After(3 * time.Second):
541 t.Fatal("WaitAuthz took too long to return")
544 t.Error("err is nil")
546 if _, ok := err.(*AuthorizationError); !ok {
547 t.Errorf("err is %T; want *AuthorizationError", err)
552 func TestWaitAuthorizationCancel(t *testing.T) {
553 ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
554 w.Header().Set("Retry-After", "60")
555 fmt.Fprintf(w, `{"status":"pending"}`)
559 res := make(chan error)
563 ctx, cancel := context.WithTimeout(context.Background(), 200*time.Millisecond)
565 _, err := client.WaitAuthorization(ctx, ts.URL)
570 case <-time.After(time.Second):
571 t.Fatal("WaitAuthz took too long to return")
574 t.Error("err is nil")
579 func TestRevokeAuthorization(t *testing.T) {
580 ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
581 if r.Method == "HEAD" {
582 w.Header().Set("Replay-Nonce", "nonce")
592 decodeJWSRequest(t, &req, r)
593 if req.Resource != "authz" {
594 t.Errorf("req.Resource = %q; want authz", req.Resource)
596 if req.Status != "deactivated" {
597 t.Errorf("req.Status = %q; want deactivated", req.Status)
600 t.Errorf("req.Delete is false")
603 w.WriteHeader(http.StatusInternalServerError)
607 client := &Client{Key: testKey}
608 ctx := context.Background()
609 if err := client.RevokeAuthorization(ctx, ts.URL+"/1"); err != nil {
610 t.Errorf("err = %v", err)
612 if client.RevokeAuthorization(ctx, ts.URL+"/2") == nil {
617 func TestPollChallenge(t *testing.T) {
618 ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
619 if r.Method != "GET" {
620 t.Errorf("r.Method = %q; want GET", r.Method)
623 w.WriteHeader(http.StatusOK)
627 "uri":"https://ca.tld/acme/challenge/publickey/id1",
632 cl := Client{Key: testKeyEC}
633 chall, err := cl.GetChallenge(context.Background(), ts.URL)
638 if chall.Status != "pending" {
639 t.Errorf("Status = %q; want pending", chall.Status)
641 if chall.Type != "http-01" {
642 t.Errorf("c.Type = %q; want http-01", chall.Type)
644 if chall.URI != "https://ca.tld/acme/challenge/publickey/id1" {
645 t.Errorf("c.URI = %q; want https://ca.tld/acme/challenge/publickey/id1", chall.URI)
647 if chall.Token != "token1" {
648 t.Errorf("c.Token = %q; want token1", chall.Token)
652 func TestAcceptChallenge(t *testing.T) {
653 ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
654 if r.Method == "HEAD" {
655 w.Header().Set("Replay-Nonce", "test-nonce")
658 if r.Method != "POST" {
659 t.Errorf("r.Method = %q; want POST", r.Method)
665 Auth string `json:"keyAuthorization"`
667 decodeJWSRequest(t, &j, r)
670 if j.Resource != "challenge" {
671 t.Errorf(`resource = %q; want "challenge"`, j.Resource)
673 if j.Type != "http-01" {
674 t.Errorf(`type = %q; want "http-01"`, j.Type)
676 keyAuth := "token1." + testKeyECThumbprint
677 if j.Auth != keyAuth {
678 t.Errorf(`keyAuthorization = %q; want %q`, j.Auth, keyAuth)
681 // Respond to request
682 w.WriteHeader(http.StatusAccepted)
686 "uri":"https://ca.tld/acme/challenge/publickey/id1",
688 "keyAuthorization":%q
693 cl := Client{Key: testKeyEC}
694 c, err := cl.Accept(context.Background(), &Challenge{
703 if c.Type != "http-01" {
704 t.Errorf("c.Type = %q; want http-01", c.Type)
706 if c.URI != "https://ca.tld/acme/challenge/publickey/id1" {
707 t.Errorf("c.URI = %q; want https://ca.tld/acme/challenge/publickey/id1", c.URI)
709 if c.Token != "token1" {
710 t.Errorf("c.Token = %q; want token1", c.Token)
714 func TestNewCert(t *testing.T) {
715 notBefore := time.Now()
716 notAfter := notBefore.AddDate(0, 2, 0)
717 timeNow = func() time.Time { return notBefore }
719 ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
720 if r.Method == "HEAD" {
721 w.Header().Set("Replay-Nonce", "test-nonce")
724 if r.Method != "POST" {
725 t.Errorf("r.Method = %q; want POST", r.Method)
729 Resource string `json:"resource"`
730 CSR string `json:"csr"`
731 NotBefore string `json:"notBefore,omitempty"`
732 NotAfter string `json:"notAfter,omitempty"`
734 decodeJWSRequest(t, &j, r)
737 if j.Resource != "new-cert" {
738 t.Errorf(`resource = %q; want "new-cert"`, j.Resource)
740 if j.NotBefore != notBefore.Format(time.RFC3339) {
741 t.Errorf(`notBefore = %q; wanted %q`, j.NotBefore, notBefore.Format(time.RFC3339))
743 if j.NotAfter != notAfter.Format(time.RFC3339) {
744 t.Errorf(`notAfter = %q; wanted %q`, j.NotAfter, notAfter.Format(time.RFC3339))
747 // Respond to request
748 template := x509.Certificate{
749 SerialNumber: big.NewInt(int64(1)),
751 Organization: []string{"goacme"},
753 NotBefore: notBefore,
756 KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
757 ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
758 BasicConstraintsValid: true,
761 sampleCert, err := x509.CreateCertificate(rand.Reader, &template, &template, &testKeyEC.PublicKey, testKeyEC)
763 t.Fatalf("Error creating certificate: %v", err)
766 w.Header().Set("Location", "https://ca.tld/acme/cert/1")
767 w.WriteHeader(http.StatusCreated)
772 csr := x509.CertificateRequest{
775 CommonName: "example.com",
776 Organization: []string{"goacme"},
779 csrb, err := x509.CreateCertificateRequest(rand.Reader, &csr, testKeyEC)
784 c := Client{Key: testKeyEC, dir: &Directory{CertURL: ts.URL}}
785 cert, certURL, err := c.CreateCert(context.Background(), csrb, notAfter.Sub(notBefore), false)
790 t.Errorf("cert is nil")
792 if certURL != "https://ca.tld/acme/cert/1" {
793 t.Errorf("certURL = %q; want https://ca.tld/acme/cert/1", certURL)
797 func TestFetchCert(t *testing.T) {
799 var ts *httptest.Server
800 ts = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
803 up := fmt.Sprintf("<%s>;rel=up", ts.URL)
804 w.Header().Set("Link", up)
806 w.Write([]byte{count})
809 res, err := (&Client{}).FetchCert(context.Background(), ts.URL, true)
811 t.Fatalf("FetchCert: %v", err)
813 cert := [][]byte{{1}, {2}, {3}}
814 if !reflect.DeepEqual(res, cert) {
815 t.Errorf("res = %v; want %v", res, cert)
819 func TestFetchCertRetry(t *testing.T) {
821 ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
823 w.Header().Set("Retry-After", "0")
824 w.WriteHeader(http.StatusAccepted)
831 res, err := (&Client{}).FetchCert(context.Background(), ts.URL, false)
833 t.Fatalf("FetchCert: %v", err)
835 cert := [][]byte{{1}}
836 if !reflect.DeepEqual(res, cert) {
837 t.Errorf("res = %v; want %v", res, cert)
841 func TestFetchCertCancel(t *testing.T) {
842 ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
843 w.Header().Set("Retry-After", "0")
844 w.WriteHeader(http.StatusAccepted)
847 ctx, cancel := context.WithCancel(context.Background())
848 done := make(chan struct{})
851 _, err = (&Client{}).FetchCert(ctx, ts.URL, false)
856 if err != context.Canceled {
857 t.Errorf("err = %v; want %v", err, context.Canceled)
861 func TestFetchCertDepth(t *testing.T) {
863 var ts *httptest.Server
864 ts = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
866 if count > maxChainLen+1 {
867 t.Errorf("count = %d; want at most %d", count, maxChainLen+1)
868 w.WriteHeader(http.StatusInternalServerError)
870 w.Header().Set("Link", fmt.Sprintf("<%s>;rel=up", ts.URL))
871 w.Write([]byte{count})
874 _, err := (&Client{}).FetchCert(context.Background(), ts.URL, true)
876 t.Errorf("err is nil")
880 func TestFetchCertBreadth(t *testing.T) {
881 var ts *httptest.Server
882 ts = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
883 for i := 0; i < maxChainLen+1; i++ {
884 w.Header().Add("Link", fmt.Sprintf("<%s>;rel=up", ts.URL))
889 _, err := (&Client{}).FetchCert(context.Background(), ts.URL, true)
891 t.Errorf("err is nil")
895 func TestFetchCertSize(t *testing.T) {
896 ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
897 b := bytes.Repeat([]byte{1}, maxCertSize+1)
901 _, err := (&Client{}).FetchCert(context.Background(), ts.URL, false)
903 t.Errorf("err is nil")
907 func TestRevokeCert(t *testing.T) {
908 ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
909 if r.Method == "HEAD" {
910 w.Header().Set("Replay-Nonce", "nonce")
919 decodeJWSRequest(t, &req, r)
920 if req.Resource != "revoke-cert" {
921 t.Errorf("req.Resource = %q; want revoke-cert", req.Resource)
924 t.Errorf("req.Reason = %d; want 1", req.Reason)
926 // echo -n cert | base64 | tr -d '=' | tr '/+' '_-'
928 if req.Certificate != cert {
929 t.Errorf("req.Certificate = %q; want %q", req.Certificate, cert)
935 dir: &Directory{RevokeURL: ts.URL},
937 ctx := context.Background()
938 if err := client.RevokeCert(ctx, nil, []byte("cert"), CRLReasonKeyCompromise); err != nil {
943 func TestNonce_add(t *testing.T) {
945 c.addNonce(http.Header{"Replay-Nonce": {"nonce"}})
946 c.addNonce(http.Header{"Replay-Nonce": {}})
947 c.addNonce(http.Header{"Replay-Nonce": {"nonce"}})
949 nonces := map[string]struct{}{"nonce": struct{}{}}
950 if !reflect.DeepEqual(c.nonces, nonces) {
951 t.Errorf("c.nonces = %q; want %q", c.nonces, nonces)
955 func TestNonce_addMax(t *testing.T) {
956 c := &Client{nonces: make(map[string]struct{})}
957 for i := 0; i < maxNonces; i++ {
958 c.nonces[fmt.Sprintf("%d", i)] = struct{}{}
960 c.addNonce(http.Header{"Replay-Nonce": {"nonce"}})
961 if n := len(c.nonces); n != maxNonces {
962 t.Errorf("len(c.nonces) = %d; want %d", n, maxNonces)
966 func TestNonce_fetch(t *testing.T) {
971 {http.StatusOK, "nonce1"},
972 {http.StatusBadRequest, "nonce2"},
976 ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
977 if r.Method != "HEAD" {
978 t.Errorf("%d: r.Method = %q; want HEAD", i, r.Method)
980 w.Header().Set("Replay-Nonce", tests[i].nonce)
981 w.WriteHeader(tests[i].code)
984 for ; i < len(tests); i++ {
987 n, err := c.fetchNonce(context.Background(), ts.URL)
989 t.Errorf("%d: n=%q; want %q", i, n, test.nonce)
992 case err == nil && test.nonce == "":
993 t.Errorf("%d: n=%q, err=%v; want non-nil error", i, n, err)
994 case err != nil && test.nonce != "":
995 t.Errorf("%d: n=%q, err=%v; want %q", i, n, err, test.nonce)
1000 func TestNonce_fetchError(t *testing.T) {
1001 ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
1002 w.WriteHeader(http.StatusTooManyRequests)
1006 _, err := c.fetchNonce(context.Background(), ts.URL)
1007 e, ok := err.(*Error)
1009 t.Fatalf("err is %T; want *Error", err)
1011 if e.StatusCode != http.StatusTooManyRequests {
1012 t.Errorf("e.StatusCode = %d; want %d", e.StatusCode, http.StatusTooManyRequests)
1016 func TestNonce_postJWS(t *testing.T) {
1018 seen := make(map[string]bool)
1019 ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
1021 w.Header().Set("Replay-Nonce", fmt.Sprintf("nonce%d", count))
1022 if r.Method == "HEAD" {
1023 // We expect the client do a HEAD request
1024 // but only to fetch the first nonce.
1027 // Make client.Authorize happy; we're not testing its result.
1029 w.WriteHeader(http.StatusCreated)
1030 w.Write([]byte(`{"status":"valid"}`))
1033 head, err := decodeJWSHead(r)
1035 t.Errorf("decodeJWSHead: %v", err)
1038 if head.Nonce == "" {
1039 t.Error("head.Nonce is empty")
1042 if seen[head.Nonce] {
1043 t.Errorf("nonce is already used: %q", head.Nonce)
1045 seen[head.Nonce] = true
1049 client := Client{Key: testKey, dir: &Directory{AuthzURL: ts.URL}}
1050 if _, err := client.Authorize(context.Background(), "example.com"); err != nil {
1051 t.Errorf("client.Authorize 1: %v", err)
1053 // The second call should not generate another extra HEAD request.
1054 if _, err := client.Authorize(context.Background(), "example.com"); err != nil {
1055 t.Errorf("client.Authorize 2: %v", err)
1059 t.Errorf("total requests count: %d; want 3", count)
1061 if n := len(client.nonces); n != 1 {
1062 t.Errorf("len(client.nonces) = %d; want 1", n)
1064 for k := range seen {
1065 if _, exist := client.nonces[k]; exist {
1066 t.Errorf("used nonce %q in client.nonces", k)
1071 func TestRetryPostJWS(t *testing.T) {
1073 ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
1075 w.Header().Set("Replay-Nonce", fmt.Sprintf("nonce%d", count))
1076 if r.Method == "HEAD" {
1077 // We expect the client to do 2 head requests to fetch
1078 // nonces, one to start and another after getting badNonce
1082 head, err := decodeJWSHead(r)
1084 t.Errorf("decodeJWSHead: %v", err)
1085 } else if head.Nonce == "" {
1086 t.Error("head.Nonce is empty")
1087 } else if head.Nonce == "nonce1" {
1088 // return a badNonce error to force the call to retry
1089 w.WriteHeader(http.StatusBadRequest)
1090 w.Write([]byte(`{"type":"urn:ietf:params:acme:error:badNonce"}`))
1093 // Make client.Authorize happy; we're not testing its result.
1094 w.WriteHeader(http.StatusCreated)
1095 w.Write([]byte(`{"status":"valid"}`))
1099 client := Client{Key: testKey, dir: &Directory{AuthzURL: ts.URL}}
1100 // This call will fail with badNonce, causing a retry
1101 if _, err := client.Authorize(context.Background(), "example.com"); err != nil {
1102 t.Errorf("client.Authorize 1: %v", err)
1105 t.Errorf("total requests count: %d; want 4", count)
1109 func TestLinkHeader(t *testing.T) {
1110 h := http.Header{"Link": {
1111 `<https://example.com/acme/new-authz>;rel="next"`,
1112 `<https://example.com/acme/recover-reg>; rel=recover`,
1113 `<https://example.com/acme/terms>; foo=bar; rel="terms-of-service"`,
1120 {"next", []string{"https://example.com/acme/new-authz", "dup"}},
1121 {"recover", []string{"https://example.com/acme/recover-reg"}},
1122 {"terms-of-service", []string{"https://example.com/acme/terms"}},
1125 for i, test := range tests {
1126 if v := linkHeader(h, test.rel); !reflect.DeepEqual(v, test.out) {
1127 t.Errorf("%d: linkHeader(%q): %v; want %v", i, test.rel, v, test.out)
1132 func TestErrorResponse(t *testing.T) {
1135 "type": "urn:acme:error:xxx",
1138 res := &http.Response{
1140 Status: "400 Bad Request",
1141 Body: ioutil.NopCloser(strings.NewReader(s)),
1142 Header: http.Header{"X-Foo": {"bar"}},
1144 err := responseError(res)
1145 v, ok := err.(*Error)
1147 t.Fatalf("err = %+v (%T); want *Error type", err, err)
1149 if v.StatusCode != 400 {
1150 t.Errorf("v.StatusCode = %v; want 400", v.StatusCode)
1152 if v.ProblemType != "urn:acme:error:xxx" {
1153 t.Errorf("v.ProblemType = %q; want urn:acme:error:xxx", v.ProblemType)
1155 if v.Detail != "text" {
1156 t.Errorf("v.Detail = %q; want text", v.Detail)
1158 if !reflect.DeepEqual(v.Header, res.Header) {
1159 t.Errorf("v.Header = %+v; want %+v", v.Header, res.Header)
1163 func TestTLSSNI01ChallengeCert(t *testing.T) {
1165 token = "evaGxfADs6pSRb2LAv9IZf17Dt3juxGJ-PCt92wr-oA"
1166 // echo -n <token.testKeyECThumbprint> | shasum -a 256
1167 san = "dbbd5eefe7b4d06eb9d1d9f5acb4c7cd.a27d320e4b30332f0b6cb441734ad7b0.acme.invalid"
1170 client := &Client{Key: testKeyEC}
1171 tlscert, name, err := client.TLSSNI01ChallengeCert(token)
1176 if n := len(tlscert.Certificate); n != 1 {
1177 t.Fatalf("len(tlscert.Certificate) = %d; want 1", n)
1179 cert, err := x509.ParseCertificate(tlscert.Certificate[0])
1183 if len(cert.DNSNames) != 1 || cert.DNSNames[0] != san {
1184 t.Fatalf("cert.DNSNames = %v; want %q", cert.DNSNames, san)
1186 if cert.DNSNames[0] != name {
1187 t.Errorf("cert.DNSNames[0] != name: %q vs %q", cert.DNSNames[0], name)
1191 func TestTLSSNI02ChallengeCert(t *testing.T) {
1193 token = "evaGxfADs6pSRb2LAv9IZf17Dt3juxGJ-PCt92wr-oA"
1194 // echo -n evaGxfADs6pSRb2LAv9IZf17Dt3juxGJ-PCt92wr-oA | shasum -a 256
1195 sanA = "7ea0aaa69214e71e02cebb18bb867736.09b730209baabf60e43d4999979ff139.token.acme.invalid"
1196 // echo -n <token.testKeyECThumbprint> | shasum -a 256
1197 sanB = "dbbd5eefe7b4d06eb9d1d9f5acb4c7cd.a27d320e4b30332f0b6cb441734ad7b0.ka.acme.invalid"
1200 client := &Client{Key: testKeyEC}
1201 tlscert, name, err := client.TLSSNI02ChallengeCert(token)
1206 if n := len(tlscert.Certificate); n != 1 {
1207 t.Fatalf("len(tlscert.Certificate) = %d; want 1", n)
1209 cert, err := x509.ParseCertificate(tlscert.Certificate[0])
1213 names := []string{sanA, sanB}
1214 if !reflect.DeepEqual(cert.DNSNames, names) {
1215 t.Fatalf("cert.DNSNames = %v;\nwant %v", cert.DNSNames, names)
1217 sort.Strings(cert.DNSNames)
1218 i := sort.SearchStrings(cert.DNSNames, name)
1219 if i >= len(cert.DNSNames) || cert.DNSNames[i] != name {
1220 t.Errorf("%v doesn't have %q", cert.DNSNames, name)
1224 func TestTLSChallengeCertOpt(t *testing.T) {
1225 key, err := rsa.GenerateKey(rand.Reader, 512)
1229 tmpl := &x509.Certificate{
1230 SerialNumber: big.NewInt(2),
1231 Subject: pkix.Name{Organization: []string{"Test"}},
1232 DNSNames: []string{"should-be-overwritten"},
1234 opts := []CertOption{WithKey(key), WithTemplate(tmpl)}
1236 client := &Client{Key: testKeyEC}
1237 cert1, _, err := client.TLSSNI01ChallengeCert("token", opts...)
1241 cert2, _, err := client.TLSSNI02ChallengeCert("token", opts...)
1246 for i, tlscert := range []tls.Certificate{cert1, cert2} {
1247 // verify generated cert private key
1248 tlskey, ok := tlscert.PrivateKey.(*rsa.PrivateKey)
1250 t.Errorf("%d: tlscert.PrivateKey is %T; want *rsa.PrivateKey", i, tlscert.PrivateKey)
1253 if tlskey.D.Cmp(key.D) != 0 {
1254 t.Errorf("%d: tlskey.D = %v; want %v", i, tlskey.D, key.D)
1256 // verify generated cert public key
1257 x509Cert, err := x509.ParseCertificate(tlscert.Certificate[0])
1259 t.Errorf("%d: %v", i, err)
1262 tlspub, ok := x509Cert.PublicKey.(*rsa.PublicKey)
1264 t.Errorf("%d: x509Cert.PublicKey is %T; want *rsa.PublicKey", i, x509Cert.PublicKey)
1267 if tlspub.N.Cmp(key.N) != 0 {
1268 t.Errorf("%d: tlspub.N = %v; want %v", i, tlspub.N, key.N)
1270 // verify template option
1272 if x509Cert.SerialNumber.Cmp(sn) != 0 {
1273 t.Errorf("%d: SerialNumber = %v; want %v", i, x509Cert.SerialNumber, sn)
1275 org := []string{"Test"}
1276 if !reflect.DeepEqual(x509Cert.Subject.Organization, org) {
1277 t.Errorf("%d: Subject.Organization = %+v; want %+v", i, x509Cert.Subject.Organization, org)
1279 for _, v := range x509Cert.DNSNames {
1280 if !strings.HasSuffix(v, ".acme.invalid") {
1281 t.Errorf("%d: invalid DNSNames element: %q", i, v)
1287 func TestHTTP01Challenge(t *testing.T) {
1290 // thumbprint is precomputed for testKeyEC in jws_test.go
1291 value = token + "." + testKeyECThumbprint
1292 urlpath = "/.well-known/acme-challenge/" + token
1294 client := &Client{Key: testKeyEC}
1295 val, err := client.HTTP01ChallengeResponse(token)
1300 t.Errorf("val = %q; want %q", val, value)
1302 if path := client.HTTP01ChallengePath(token); path != urlpath {
1303 t.Errorf("path = %q; want %q", path, urlpath)
1307 func TestDNS01ChallengeRecord(t *testing.T) {
1308 // echo -n xxx.<testKeyECThumbprint> | \
1309 // openssl dgst -binary -sha256 | \
1310 // base64 | tr -d '=' | tr '/+' '_-'
1311 const value = "8DERMexQ5VcdJ_prpPiA0mVdp7imgbCgjsG4SqqNMIo"
1313 client := &Client{Key: testKeyEC}
1314 val, err := client.DNS01ChallengeRecord("xxx")
1319 t.Errorf("val = %q; want %q", val, value)
1323 func TestBackoff(t *testing.T) {
1324 tt := []struct{ min, max time.Duration }{
1325 {time.Second, 2 * time.Second},
1326 {2 * time.Second, 3 * time.Second},
1327 {4 * time.Second, 5 * time.Second},
1328 {8 * time.Second, 9 * time.Second},
1330 for i, test := range tt {
1331 d := backoff(i, time.Minute)
1332 if d < test.min || test.max < d {
1333 t.Errorf("%d: d = %v; want between %v and %v", i, d, test.min, test.max)
1337 min, max := time.Second, 2*time.Second
1338 if d := backoff(-1, time.Minute); d < min || max < d {
1339 t.Errorf("d = %v; want between %v and %v", d, min, max)
1342 bound := 10 * time.Second
1343 if d := backoff(100, bound); d != bound {
1344 t.Errorf("d = %v; want %v", d, bound)