blob: 4e618f2929d52773e2c5d9bae534b258f4ba1ded [file] [log] [blame]
Alex Vaghin1777f3b2016-03-18 12:12:46 +00001// 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.
4
5package acme
6
7import (
Alex Vaghin6575f7e2016-08-10 16:19:56 +02008 "bytes"
Alex Vaghin1777f3b2016-03-18 12:12:46 +00009 "crypto/rand"
Alex Vaghinb13fc1f2016-08-25 09:06:35 +020010 "crypto/rsa"
11 "crypto/tls"
Alex Vaghin1777f3b2016-03-18 12:12:46 +000012 "crypto/x509"
13 "crypto/x509/pkix"
14 "encoding/base64"
15 "encoding/json"
16 "fmt"
17 "io/ioutil"
18 "math/big"
19 "net/http"
20 "net/http/httptest"
21 "reflect"
Alex Vaghin595bbbd2016-08-11 17:03:39 +020022 "sort"
Alex Vaghin1777f3b2016-03-18 12:12:46 +000023 "strings"
24 "testing"
25 "time"
26
27 "golang.org/x/net/context"
28)
29
30// Decodes a JWS-encoded request and unmarshals the decoded JSON into a provided
31// interface.
32func decodeJWSRequest(t *testing.T, v interface{}, r *http.Request) {
33 // Decode request
34 var req struct{ Payload string }
35 if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
36 t.Fatal(err)
37 }
38 payload, err := base64.RawURLEncoding.DecodeString(req.Payload)
39 if err != nil {
40 t.Fatal(err)
41 }
42 err = json.Unmarshal(payload, v)
43 if err != nil {
44 t.Fatal(err)
45 }
46}
47
48func TestDiscover(t *testing.T) {
49 const (
50 reg = "https://example.com/acme/new-reg"
51 authz = "https://example.com/acme/new-authz"
52 cert = "https://example.com/acme/new-cert"
53 revoke = "https://example.com/acme/revoke-cert"
54 )
55 ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
56 w.Header().Set("content-type", "application/json")
57 fmt.Fprintf(w, `{
58 "new-reg": %q,
59 "new-authz": %q,
60 "new-cert": %q,
61 "revoke-cert": %q
62 }`, reg, authz, cert, revoke)
63 }))
64 defer ts.Close()
Alex Vaghinc2f49472016-06-10 12:13:04 +010065 c := Client{DirectoryURL: ts.URL}
Alex Vaghin807ffea2016-08-16 15:36:38 +020066 dir, err := c.Discover(context.Background())
Alex Vaghin1777f3b2016-03-18 12:12:46 +000067 if err != nil {
68 t.Fatal(err)
69 }
Alex Vaghinc2f49472016-06-10 12:13:04 +010070 if dir.RegURL != reg {
71 t.Errorf("dir.RegURL = %q; want %q", dir.RegURL, reg)
Alex Vaghin1777f3b2016-03-18 12:12:46 +000072 }
Alex Vaghinc2f49472016-06-10 12:13:04 +010073 if dir.AuthzURL != authz {
74 t.Errorf("dir.AuthzURL = %q; want %q", dir.AuthzURL, authz)
Alex Vaghin1777f3b2016-03-18 12:12:46 +000075 }
Alex Vaghinc2f49472016-06-10 12:13:04 +010076 if dir.CertURL != cert {
77 t.Errorf("dir.CertURL = %q; want %q", dir.CertURL, cert)
Alex Vaghin1777f3b2016-03-18 12:12:46 +000078 }
Alex Vaghinc2f49472016-06-10 12:13:04 +010079 if dir.RevokeURL != revoke {
80 t.Errorf("dir.RevokeURL = %q; want %q", dir.RevokeURL, revoke)
Alex Vaghin1777f3b2016-03-18 12:12:46 +000081 }
82}
83
84func TestRegister(t *testing.T) {
85 contacts := []string{"mailto:admin@example.com"}
86
87 ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
88 if r.Method == "HEAD" {
89 w.Header().Set("replay-nonce", "test-nonce")
90 return
91 }
92 if r.Method != "POST" {
93 t.Errorf("r.Method = %q; want POST", r.Method)
94 }
95
96 var j struct {
97 Resource string
98 Contact []string
99 Agreement string
100 }
101 decodeJWSRequest(t, &j, r)
102
103 // Test request
104 if j.Resource != "new-reg" {
105 t.Errorf("j.Resource = %q; want new-reg", j.Resource)
106 }
107 if !reflect.DeepEqual(j.Contact, contacts) {
108 t.Errorf("j.Contact = %v; want %v", j.Contact, contacts)
109 }
110
111 w.Header().Set("Location", "https://ca.tld/acme/reg/1")
112 w.Header().Set("Link", `<https://ca.tld/acme/new-authz>;rel="next"`)
113 w.Header().Add("Link", `<https://ca.tld/acme/recover-reg>;rel="recover"`)
114 w.Header().Add("Link", `<https://ca.tld/acme/terms>;rel="terms-of-service"`)
115 w.WriteHeader(http.StatusCreated)
116 b, _ := json.Marshal(contacts)
Alex Vaghin7e016f12016-08-21 18:20:10 +0200117 fmt.Fprintf(w, `{"contact": %s}`, b)
Alex Vaghin1777f3b2016-03-18 12:12:46 +0000118 }))
119 defer ts.Close()
120
Alex Vaghin1da79bd2016-06-10 12:38:58 +0100121 prompt := func(url string) bool {
122 const terms = "https://ca.tld/acme/terms"
123 if url != terms {
124 t.Errorf("prompt url = %q; want %q", url, terms)
125 }
126 return false
127 }
128
Alex Vaghin7e016f12016-08-21 18:20:10 +0200129 c := Client{Key: testKeyEC, dir: &Directory{RegURL: ts.URL}}
Alex Vaghin1777f3b2016-03-18 12:12:46 +0000130 a := &Account{Contact: contacts}
131 var err error
Alex Vaghin807ffea2016-08-16 15:36:38 +0200132 if a, err = c.Register(context.Background(), a, prompt); err != nil {
Alex Vaghin1777f3b2016-03-18 12:12:46 +0000133 t.Fatal(err)
134 }
135 if a.URI != "https://ca.tld/acme/reg/1" {
136 t.Errorf("a.URI = %q; want https://ca.tld/acme/reg/1", a.URI)
137 }
138 if a.Authz != "https://ca.tld/acme/new-authz" {
139 t.Errorf("a.Authz = %q; want https://ca.tld/acme/new-authz", a.Authz)
140 }
141 if a.CurrentTerms != "https://ca.tld/acme/terms" {
142 t.Errorf("a.CurrentTerms = %q; want https://ca.tld/acme/terms", a.CurrentTerms)
143 }
144 if !reflect.DeepEqual(a.Contact, contacts) {
145 t.Errorf("a.Contact = %v; want %v", a.Contact, contacts)
146 }
147}
148
149func TestUpdateReg(t *testing.T) {
150 const terms = "https://ca.tld/acme/terms"
151 contacts := []string{"mailto:admin@example.com"}
152
153 ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
154 if r.Method == "HEAD" {
155 w.Header().Set("replay-nonce", "test-nonce")
156 return
157 }
158 if r.Method != "POST" {
159 t.Errorf("r.Method = %q; want POST", r.Method)
160 }
161
162 var j struct {
163 Resource string
164 Contact []string
165 Agreement string
166 }
167 decodeJWSRequest(t, &j, r)
168
169 // Test request
170 if j.Resource != "reg" {
171 t.Errorf("j.Resource = %q; want reg", j.Resource)
172 }
173 if j.Agreement != terms {
174 t.Errorf("j.Agreement = %q; want %q", j.Agreement, terms)
175 }
176 if !reflect.DeepEqual(j.Contact, contacts) {
177 t.Errorf("j.Contact = %v; want %v", j.Contact, contacts)
178 }
179
180 w.Header().Set("Link", `<https://ca.tld/acme/new-authz>;rel="next"`)
181 w.Header().Add("Link", `<https://ca.tld/acme/recover-reg>;rel="recover"`)
182 w.Header().Add("Link", fmt.Sprintf(`<%s>;rel="terms-of-service"`, terms))
183 w.WriteHeader(http.StatusOK)
184 b, _ := json.Marshal(contacts)
Alex Vaghin7e016f12016-08-21 18:20:10 +0200185 fmt.Fprintf(w, `{"contact":%s, "agreement":%q}`, b, terms)
Alex Vaghin1777f3b2016-03-18 12:12:46 +0000186 }))
187 defer ts.Close()
188
Alex Vaghin7e016f12016-08-21 18:20:10 +0200189 c := Client{Key: testKeyEC}
Alex Vaghinc2f49472016-06-10 12:13:04 +0100190 a := &Account{URI: ts.URL, Contact: contacts, AgreedTerms: terms}
Alex Vaghin1777f3b2016-03-18 12:12:46 +0000191 var err error
Alex Vaghin807ffea2016-08-16 15:36:38 +0200192 if a, err = c.UpdateReg(context.Background(), a); err != nil {
Alex Vaghin1777f3b2016-03-18 12:12:46 +0000193 t.Fatal(err)
194 }
195 if a.Authz != "https://ca.tld/acme/new-authz" {
196 t.Errorf("a.Authz = %q; want https://ca.tld/acme/new-authz", a.Authz)
197 }
198 if a.AgreedTerms != terms {
199 t.Errorf("a.AgreedTerms = %q; want %q", a.AgreedTerms, terms)
200 }
201 if a.CurrentTerms != terms {
202 t.Errorf("a.CurrentTerms = %q; want %q", a.CurrentTerms, terms)
203 }
Alex Vaghin6c9322972016-08-12 17:09:37 +0200204 if a.URI != ts.URL {
205 t.Errorf("a.URI = %q; want %q", a.URI, ts.URL)
206 }
Alex Vaghin1777f3b2016-03-18 12:12:46 +0000207}
208
209func TestGetReg(t *testing.T) {
210 const terms = "https://ca.tld/acme/terms"
211 const newTerms = "https://ca.tld/acme/new-terms"
212 contacts := []string{"mailto:admin@example.com"}
213
214 ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
215 if r.Method == "HEAD" {
216 w.Header().Set("replay-nonce", "test-nonce")
217 return
218 }
219 if r.Method != "POST" {
220 t.Errorf("r.Method = %q; want POST", r.Method)
221 }
222
223 var j struct {
224 Resource string
225 Contact []string
226 Agreement string
227 }
228 decodeJWSRequest(t, &j, r)
229
230 // Test request
231 if j.Resource != "reg" {
232 t.Errorf("j.Resource = %q; want reg", j.Resource)
233 }
234 if len(j.Contact) != 0 {
235 t.Errorf("j.Contact = %v", j.Contact)
236 }
237 if j.Agreement != "" {
238 t.Errorf("j.Agreement = %q", j.Agreement)
239 }
240
241 w.Header().Set("Link", `<https://ca.tld/acme/new-authz>;rel="next"`)
242 w.Header().Add("Link", `<https://ca.tld/acme/recover-reg>;rel="recover"`)
243 w.Header().Add("Link", fmt.Sprintf(`<%s>;rel="terms-of-service"`, newTerms))
244 w.WriteHeader(http.StatusOK)
245 b, _ := json.Marshal(contacts)
Alex Vaghin7e016f12016-08-21 18:20:10 +0200246 fmt.Fprintf(w, `{"contact":%s, "agreement":%q}`, b, terms)
Alex Vaghin1777f3b2016-03-18 12:12:46 +0000247 }))
248 defer ts.Close()
249
Alex Vaghin7e016f12016-08-21 18:20:10 +0200250 c := Client{Key: testKeyEC}
Alex Vaghin807ffea2016-08-16 15:36:38 +0200251 a, err := c.GetReg(context.Background(), ts.URL)
Alex Vaghin1777f3b2016-03-18 12:12:46 +0000252 if err != nil {
253 t.Fatal(err)
254 }
255 if a.Authz != "https://ca.tld/acme/new-authz" {
256 t.Errorf("a.AuthzURL = %q; want https://ca.tld/acme/new-authz", a.Authz)
257 }
258 if a.AgreedTerms != terms {
259 t.Errorf("a.AgreedTerms = %q; want %q", a.AgreedTerms, terms)
260 }
261 if a.CurrentTerms != newTerms {
262 t.Errorf("a.CurrentTerms = %q; want %q", a.CurrentTerms, newTerms)
263 }
Alex Vaghin6c9322972016-08-12 17:09:37 +0200264 if a.URI != ts.URL {
265 t.Errorf("a.URI = %q; want %q", a.URI, ts.URL)
266 }
Alex Vaghin1777f3b2016-03-18 12:12:46 +0000267}
268
269func TestAuthorize(t *testing.T) {
270 ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
271 if r.Method == "HEAD" {
272 w.Header().Set("replay-nonce", "test-nonce")
273 return
274 }
275 if r.Method != "POST" {
276 t.Errorf("r.Method = %q; want POST", r.Method)
277 }
278
279 var j struct {
280 Resource string
281 Identifier struct {
282 Type string
283 Value string
284 }
285 }
286 decodeJWSRequest(t, &j, r)
287
288 // Test request
289 if j.Resource != "new-authz" {
290 t.Errorf("j.Resource = %q; want new-authz", j.Resource)
291 }
292 if j.Identifier.Type != "dns" {
293 t.Errorf("j.Identifier.Type = %q; want dns", j.Identifier.Type)
294 }
295 if j.Identifier.Value != "example.com" {
296 t.Errorf("j.Identifier.Value = %q; want example.com", j.Identifier.Value)
297 }
298
299 w.Header().Set("Location", "https://ca.tld/acme/auth/1")
300 w.WriteHeader(http.StatusCreated)
301 fmt.Fprintf(w, `{
302 "identifier": {"type":"dns","value":"example.com"},
303 "status":"pending",
304 "challenges":[
305 {
306 "type":"http-01",
307 "status":"pending",
308 "uri":"https://ca.tld/acme/challenge/publickey/id1",
309 "token":"token1"
310 },
311 {
312 "type":"tls-sni-01",
313 "status":"pending",
314 "uri":"https://ca.tld/acme/challenge/publickey/id2",
315 "token":"token2"
316 }
317 ],
318 "combinations":[[0],[1]]}`)
319 }))
320 defer ts.Close()
321
Alex Vaghin7e016f12016-08-21 18:20:10 +0200322 cl := Client{Key: testKeyEC, dir: &Directory{AuthzURL: ts.URL}}
Alex Vaghin807ffea2016-08-16 15:36:38 +0200323 auth, err := cl.Authorize(context.Background(), "example.com")
Alex Vaghin1777f3b2016-03-18 12:12:46 +0000324 if err != nil {
325 t.Fatal(err)
326 }
327
328 if auth.URI != "https://ca.tld/acme/auth/1" {
329 t.Errorf("URI = %q; want https://ca.tld/acme/auth/1", auth.URI)
330 }
331 if auth.Status != "pending" {
332 t.Errorf("Status = %q; want pending", auth.Status)
333 }
334 if auth.Identifier.Type != "dns" {
335 t.Errorf("Identifier.Type = %q; want dns", auth.Identifier.Type)
336 }
337 if auth.Identifier.Value != "example.com" {
338 t.Errorf("Identifier.Value = %q; want example.com", auth.Identifier.Value)
339 }
340
341 if n := len(auth.Challenges); n != 2 {
342 t.Fatalf("len(auth.Challenges) = %d; want 2", n)
343 }
344
345 c := auth.Challenges[0]
346 if c.Type != "http-01" {
347 t.Errorf("c.Type = %q; want http-01", c.Type)
348 }
349 if c.URI != "https://ca.tld/acme/challenge/publickey/id1" {
350 t.Errorf("c.URI = %q; want https://ca.tld/acme/challenge/publickey/id1", c.URI)
351 }
352 if c.Token != "token1" {
353 t.Errorf("c.Token = %q; want token1", c.Type)
354 }
355
356 c = auth.Challenges[1]
357 if c.Type != "tls-sni-01" {
358 t.Errorf("c.Type = %q; want tls-sni-01", c.Type)
359 }
360 if c.URI != "https://ca.tld/acme/challenge/publickey/id2" {
361 t.Errorf("c.URI = %q; want https://ca.tld/acme/challenge/publickey/id2", c.URI)
362 }
363 if c.Token != "token2" {
364 t.Errorf("c.Token = %q; want token2", c.Type)
365 }
366
367 combs := [][]int{{0}, {1}}
368 if !reflect.DeepEqual(auth.Combinations, combs) {
369 t.Errorf("auth.Combinations: %+v\nwant: %+v\n", auth.Combinations, combs)
370 }
371}
372
Alex Vaghin1ba5ec02016-08-19 14:32:09 +0200373func TestAuthorizeValid(t *testing.T) {
374 ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
375 if r.Method == "HEAD" {
376 w.Header().Set("replay-nonce", "nonce")
377 return
378 }
379 w.WriteHeader(http.StatusCreated)
380 w.Write([]byte(`{"status":"valid"}`))
381 }))
382 defer ts.Close()
383 client := Client{Key: testKey, dir: &Directory{AuthzURL: ts.URL}}
384 _, err := client.Authorize(context.Background(), "example.com")
385 if err != nil {
386 t.Errorf("err = %v", err)
387 }
388}
389
Alex Vaghinb35ccbc2016-08-19 11:32:22 +0200390func TestGetAuthorization(t *testing.T) {
Alex Vaghin1777f3b2016-03-18 12:12:46 +0000391 ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
392 if r.Method != "GET" {
393 t.Errorf("r.Method = %q; want GET", r.Method)
394 }
395
396 w.WriteHeader(http.StatusOK)
397 fmt.Fprintf(w, `{
398 "identifier": {"type":"dns","value":"example.com"},
399 "status":"pending",
400 "challenges":[
401 {
402 "type":"http-01",
403 "status":"pending",
404 "uri":"https://ca.tld/acme/challenge/publickey/id1",
405 "token":"token1"
406 },
407 {
408 "type":"tls-sni-01",
409 "status":"pending",
410 "uri":"https://ca.tld/acme/challenge/publickey/id2",
411 "token":"token2"
412 }
413 ],
414 "combinations":[[0],[1]]}`)
415 }))
416 defer ts.Close()
417
Alex Vaghin7e016f12016-08-21 18:20:10 +0200418 cl := Client{Key: testKeyEC}
Alex Vaghinb35ccbc2016-08-19 11:32:22 +0200419 auth, err := cl.GetAuthorization(context.Background(), ts.URL)
Alex Vaghin1777f3b2016-03-18 12:12:46 +0000420 if err != nil {
421 t.Fatal(err)
422 }
423
424 if auth.Status != "pending" {
425 t.Errorf("Status = %q; want pending", auth.Status)
426 }
427 if auth.Identifier.Type != "dns" {
428 t.Errorf("Identifier.Type = %q; want dns", auth.Identifier.Type)
429 }
430 if auth.Identifier.Value != "example.com" {
431 t.Errorf("Identifier.Value = %q; want example.com", auth.Identifier.Value)
432 }
433
434 if n := len(auth.Challenges); n != 2 {
435 t.Fatalf("len(set.Challenges) = %d; want 2", n)
436 }
437
438 c := auth.Challenges[0]
439 if c.Type != "http-01" {
440 t.Errorf("c.Type = %q; want http-01", c.Type)
441 }
442 if c.URI != "https://ca.tld/acme/challenge/publickey/id1" {
443 t.Errorf("c.URI = %q; want https://ca.tld/acme/challenge/publickey/id1", c.URI)
444 }
445 if c.Token != "token1" {
446 t.Errorf("c.Token = %q; want token1", c.Type)
447 }
448
449 c = auth.Challenges[1]
450 if c.Type != "tls-sni-01" {
451 t.Errorf("c.Type = %q; want tls-sni-01", c.Type)
452 }
453 if c.URI != "https://ca.tld/acme/challenge/publickey/id2" {
454 t.Errorf("c.URI = %q; want https://ca.tld/acme/challenge/publickey/id2", c.URI)
455 }
456 if c.Token != "token2" {
457 t.Errorf("c.Token = %q; want token2", c.Type)
458 }
459
460 combs := [][]int{{0}, {1}}
461 if !reflect.DeepEqual(auth.Combinations, combs) {
462 t.Errorf("auth.Combinations: %+v\nwant: %+v\n", auth.Combinations, combs)
463 }
464}
465
Alex Vaghinb35ccbc2016-08-19 11:32:22 +0200466func TestWaitAuthorization(t *testing.T) {
467 var count int
468 ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
469 count++
470 w.Header().Set("retry-after", "0")
471 if count > 1 {
472 fmt.Fprintf(w, `{"status":"valid"}`)
473 return
474 }
475 fmt.Fprintf(w, `{"status":"pending"}`)
476 }))
477 defer ts.Close()
478
479 type res struct {
480 authz *Authorization
481 err error
482 }
483 done := make(chan res)
484 defer close(done)
485 go func() {
486 var client Client
487 a, err := client.WaitAuthorization(context.Background(), ts.URL)
488 done <- res{a, err}
489 }()
490
491 select {
492 case <-time.After(5 * time.Second):
493 t.Fatal("WaitAuthz took too long to return")
494 case res := <-done:
495 if res.err != nil {
496 t.Fatalf("res.err = %v", res.err)
497 }
498 if res.authz == nil {
499 t.Fatal("res.authz is nil")
500 }
501 }
502}
503
504func TestWaitAuthorizationInvalid(t *testing.T) {
505 ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
506 fmt.Fprintf(w, `{"status":"invalid"}`)
507 }))
508 defer ts.Close()
509
510 res := make(chan error)
511 defer close(res)
512 go func() {
513 var client Client
514 _, err := client.WaitAuthorization(context.Background(), ts.URL)
515 res <- err
516 }()
517
518 select {
519 case <-time.After(3 * time.Second):
520 t.Fatal("WaitAuthz took too long to return")
521 case err := <-res:
522 if err == nil {
523 t.Error("err is nil")
524 }
525 }
526}
527
528func TestWaitAuthorizationCancel(t *testing.T) {
529 ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
530 w.Header().Set("retry-after", "60")
531 fmt.Fprintf(w, `{"status":"pending"}`)
532 }))
533 defer ts.Close()
534
535 res := make(chan error)
536 defer close(res)
537 go func() {
538 var client Client
539 ctx, cancel := context.WithTimeout(context.Background(), 200*time.Millisecond)
540 defer cancel()
541 _, err := client.WaitAuthorization(ctx, ts.URL)
542 res <- err
543 }()
544
545 select {
546 case <-time.After(time.Second):
547 t.Fatal("WaitAuthz took too long to return")
548 case err := <-res:
549 if err == nil {
550 t.Error("err is nil")
551 }
552 }
553}
554
Alex Vaghinf9440962016-09-07 16:50:43 +0200555func TestRevokeAuthorization(t *testing.T) {
556 ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
557 if r.Method == "HEAD" {
558 w.Header().Set("replay-nonce", "nonce")
559 return
560 }
561 switch r.URL.Path {
562 case "/1":
563 var req struct {
564 Resource string
Alex Vaghinca7e7f12016-10-25 14:52:55 +0200565 Status string
Alex Vaghinf9440962016-09-07 16:50:43 +0200566 Delete bool
567 }
568 decodeJWSRequest(t, &req, r)
569 if req.Resource != "authz" {
570 t.Errorf("req.Resource = %q; want authz", req.Resource)
571 }
Alex Vaghinca7e7f12016-10-25 14:52:55 +0200572 if req.Status != "deactivated" {
573 t.Errorf("req.Status = %q; want deactivated", req.Status)
574 }
Alex Vaghinf9440962016-09-07 16:50:43 +0200575 if !req.Delete {
576 t.Errorf("req.Delete is false")
577 }
578 case "/2":
579 w.WriteHeader(http.StatusInternalServerError)
580 }
581 }))
582 defer ts.Close()
583 client := &Client{Key: testKey}
584 ctx := context.Background()
585 if err := client.RevokeAuthorization(ctx, ts.URL+"/1"); err != nil {
586 t.Errorf("err = %v", err)
587 }
588 if client.RevokeAuthorization(ctx, ts.URL+"/2") == nil {
589 t.Error("nil error")
590 }
591}
592
Alex Vaghin1777f3b2016-03-18 12:12:46 +0000593func TestPollChallenge(t *testing.T) {
594 ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
595 if r.Method != "GET" {
596 t.Errorf("r.Method = %q; want GET", r.Method)
597 }
598
599 w.WriteHeader(http.StatusOK)
600 fmt.Fprintf(w, `{
601 "type":"http-01",
602 "status":"pending",
603 "uri":"https://ca.tld/acme/challenge/publickey/id1",
604 "token":"token1"}`)
605 }))
606 defer ts.Close()
607
Alex Vaghin7e016f12016-08-21 18:20:10 +0200608 cl := Client{Key: testKeyEC}
Alex Vaghin807ffea2016-08-16 15:36:38 +0200609 chall, err := cl.GetChallenge(context.Background(), ts.URL)
Alex Vaghin1777f3b2016-03-18 12:12:46 +0000610 if err != nil {
611 t.Fatal(err)
612 }
613
614 if chall.Status != "pending" {
615 t.Errorf("Status = %q; want pending", chall.Status)
616 }
617 if chall.Type != "http-01" {
618 t.Errorf("c.Type = %q; want http-01", chall.Type)
619 }
620 if chall.URI != "https://ca.tld/acme/challenge/publickey/id1" {
621 t.Errorf("c.URI = %q; want https://ca.tld/acme/challenge/publickey/id1", chall.URI)
622 }
623 if chall.Token != "token1" {
624 t.Errorf("c.Token = %q; want token1", chall.Type)
625 }
626}
627
628func TestAcceptChallenge(t *testing.T) {
629 ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
630 if r.Method == "HEAD" {
631 w.Header().Set("replay-nonce", "test-nonce")
632 return
633 }
634 if r.Method != "POST" {
635 t.Errorf("r.Method = %q; want POST", r.Method)
636 }
637
638 var j struct {
639 Resource string
640 Type string
641 Auth string `json:"keyAuthorization"`
642 }
643 decodeJWSRequest(t, &j, r)
644
645 // Test request
646 if j.Resource != "challenge" {
647 t.Errorf(`resource = %q; want "challenge"`, j.Resource)
648 }
649 if j.Type != "http-01" {
650 t.Errorf(`type = %q; want "http-01"`, j.Type)
651 }
Alex Vaghin7e016f12016-08-21 18:20:10 +0200652 keyAuth := "token1." + testKeyECThumbprint
Alex Vaghin1777f3b2016-03-18 12:12:46 +0000653 if j.Auth != keyAuth {
654 t.Errorf(`keyAuthorization = %q; want %q`, j.Auth, keyAuth)
655 }
656
657 // Respond to request
658 w.WriteHeader(http.StatusAccepted)
659 fmt.Fprintf(w, `{
660 "type":"http-01",
661 "status":"pending",
662 "uri":"https://ca.tld/acme/challenge/publickey/id1",
663 "token":"token1",
664 "keyAuthorization":%q
665 }`, keyAuth)
666 }))
667 defer ts.Close()
668
Alex Vaghin7e016f12016-08-21 18:20:10 +0200669 cl := Client{Key: testKeyEC}
Alex Vaghin807ffea2016-08-16 15:36:38 +0200670 c, err := cl.Accept(context.Background(), &Challenge{
Alex Vaghin1777f3b2016-03-18 12:12:46 +0000671 URI: ts.URL,
672 Token: "token1",
673 Type: "http-01",
674 })
675 if err != nil {
676 t.Fatal(err)
677 }
678
679 if c.Type != "http-01" {
680 t.Errorf("c.Type = %q; want http-01", c.Type)
681 }
682 if c.URI != "https://ca.tld/acme/challenge/publickey/id1" {
683 t.Errorf("c.URI = %q; want https://ca.tld/acme/challenge/publickey/id1", c.URI)
684 }
685 if c.Token != "token1" {
686 t.Errorf("c.Token = %q; want token1", c.Type)
687 }
688}
689
690func TestNewCert(t *testing.T) {
691 notBefore := time.Now()
692 notAfter := notBefore.AddDate(0, 2, 0)
693 timeNow = func() time.Time { return notBefore }
694
695 ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
696 if r.Method == "HEAD" {
697 w.Header().Set("replay-nonce", "test-nonce")
698 return
699 }
700 if r.Method != "POST" {
701 t.Errorf("r.Method = %q; want POST", r.Method)
702 }
703
704 var j struct {
705 Resource string `json:"resource"`
706 CSR string `json:"csr"`
707 NotBefore string `json:"notBefore,omitempty"`
708 NotAfter string `json:"notAfter,omitempty"`
709 }
710 decodeJWSRequest(t, &j, r)
711
712 // Test request
713 if j.Resource != "new-cert" {
714 t.Errorf(`resource = %q; want "new-cert"`, j.Resource)
715 }
716 if j.NotBefore != notBefore.Format(time.RFC3339) {
717 t.Errorf(`notBefore = %q; wanted %q`, j.NotBefore, notBefore.Format(time.RFC3339))
718 }
719 if j.NotAfter != notAfter.Format(time.RFC3339) {
720 t.Errorf(`notAfter = %q; wanted %q`, j.NotAfter, notAfter.Format(time.RFC3339))
721 }
722
723 // Respond to request
724 template := x509.Certificate{
725 SerialNumber: big.NewInt(int64(1)),
726 Subject: pkix.Name{
727 Organization: []string{"goacme"},
728 },
729 NotBefore: notBefore,
730 NotAfter: notAfter,
731
732 KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
733 ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
734 BasicConstraintsValid: true,
735 }
736
Alex Vaghin7e016f12016-08-21 18:20:10 +0200737 sampleCert, err := x509.CreateCertificate(rand.Reader, &template, &template, &testKeyEC.PublicKey, testKeyEC)
Alex Vaghin1777f3b2016-03-18 12:12:46 +0000738 if err != nil {
739 t.Fatalf("Error creating certificate: %v", err)
740 }
741
742 w.Header().Set("Location", "https://ca.tld/acme/cert/1")
743 w.WriteHeader(http.StatusCreated)
744 w.Write(sampleCert)
745 }))
746 defer ts.Close()
747
748 csr := x509.CertificateRequest{
749 Version: 0,
750 Subject: pkix.Name{
751 CommonName: "example.com",
752 Organization: []string{"goacme"},
753 },
754 }
Alex Vaghin7e016f12016-08-21 18:20:10 +0200755 csrb, err := x509.CreateCertificateRequest(rand.Reader, &csr, testKeyEC)
Alex Vaghin1777f3b2016-03-18 12:12:46 +0000756 if err != nil {
757 t.Fatal(err)
758 }
759
Alex Vaghin7e016f12016-08-21 18:20:10 +0200760 c := Client{Key: testKeyEC, dir: &Directory{CertURL: ts.URL}}
Alex Vaghinc2f49472016-06-10 12:13:04 +0100761 cert, certURL, err := c.CreateCert(context.Background(), csrb, notAfter.Sub(notBefore), false)
Alex Vaghin1777f3b2016-03-18 12:12:46 +0000762 if err != nil {
763 t.Fatal(err)
764 }
765 if cert == nil {
766 t.Errorf("cert is nil")
767 }
768 if certURL != "https://ca.tld/acme/cert/1" {
769 t.Errorf("certURL = %q; want https://ca.tld/acme/cert/1", certURL)
770 }
771}
772
773func TestFetchCert(t *testing.T) {
Alex Vaghin6575f7e2016-08-10 16:19:56 +0200774 var count byte
775 var ts *httptest.Server
776 ts = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
777 count++
778 if count < 3 {
779 up := fmt.Sprintf("<%s>;rel=up", ts.URL)
780 w.Header().Set("link", up)
781 }
782 w.Write([]byte{count})
Alex Vaghin1777f3b2016-03-18 12:12:46 +0000783 }))
784 defer ts.Close()
Alex Vaghin6575f7e2016-08-10 16:19:56 +0200785 res, err := (&Client{}).FetchCert(context.Background(), ts.URL, true)
Alex Vaghin1777f3b2016-03-18 12:12:46 +0000786 if err != nil {
787 t.Fatalf("FetchCert: %v", err)
788 }
Alex Vaghin6575f7e2016-08-10 16:19:56 +0200789 cert := [][]byte{{1}, {2}, {3}}
Alex Vaghin1777f3b2016-03-18 12:12:46 +0000790 if !reflect.DeepEqual(res, cert) {
791 t.Errorf("res = %v; want %v", res, cert)
792 }
793}
794
795func TestFetchCertRetry(t *testing.T) {
796 var count int
797 ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
798 if count < 1 {
799 w.Header().Set("retry-after", "0")
800 w.WriteHeader(http.StatusAccepted)
801 count++
802 return
803 }
804 w.Write([]byte{1})
805 }))
806 defer ts.Close()
807 res, err := (&Client{}).FetchCert(context.Background(), ts.URL, false)
808 if err != nil {
809 t.Fatalf("FetchCert: %v", err)
810 }
811 cert := [][]byte{{1}}
812 if !reflect.DeepEqual(res, cert) {
813 t.Errorf("res = %v; want %v", res, cert)
814 }
815}
816
817func TestFetchCertCancel(t *testing.T) {
818 ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
819 w.Header().Set("retry-after", "0")
820 w.WriteHeader(http.StatusAccepted)
821 }))
822 defer ts.Close()
823 ctx, cancel := context.WithCancel(context.Background())
824 done := make(chan struct{})
825 var err error
826 go func() {
827 _, err = (&Client{}).FetchCert(ctx, ts.URL, false)
828 close(done)
829 }()
830 cancel()
831 <-done
832 if err != context.Canceled {
833 t.Errorf("err = %v; want %v", err, context.Canceled)
834 }
835}
836
Alex Vaghin6575f7e2016-08-10 16:19:56 +0200837func TestFetchCertDepth(t *testing.T) {
838 var count byte
839 var ts *httptest.Server
840 ts = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
841 count++
842 if count > maxChainLen+1 {
843 t.Errorf("count = %d; want at most %d", count, maxChainLen+1)
844 w.WriteHeader(http.StatusInternalServerError)
845 }
846 w.Header().Set("link", fmt.Sprintf("<%s>;rel=up", ts.URL))
847 w.Write([]byte{count})
848 }))
849 defer ts.Close()
850 _, err := (&Client{}).FetchCert(context.Background(), ts.URL, true)
851 if err == nil {
852 t.Errorf("err is nil")
853 }
854}
855
856func TestFetchCertBreadth(t *testing.T) {
857 var ts *httptest.Server
858 ts = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
859 for i := 0; i < maxChainLen+1; i++ {
860 w.Header().Add("link", fmt.Sprintf("<%s>;rel=up", ts.URL))
861 }
862 w.Write([]byte{1})
863 }))
864 defer ts.Close()
865 _, err := (&Client{}).FetchCert(context.Background(), ts.URL, true)
866 if err == nil {
867 t.Errorf("err is nil")
868 }
869}
870
871func TestFetchCertSize(t *testing.T) {
872 ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
873 b := bytes.Repeat([]byte{1}, maxCertSize+1)
874 w.Write(b)
875 }))
876 defer ts.Close()
877 _, err := (&Client{}).FetchCert(context.Background(), ts.URL, false)
878 if err == nil {
879 t.Errorf("err is nil")
880 }
881}
882
Alex Vaghin9fbab142016-08-16 16:28:56 +0200883func TestRevokeCert(t *testing.T) {
884 ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
885 if r.Method == "HEAD" {
886 w.Header().Set("replay-nonce", "nonce")
887 return
888 }
889
890 var req struct {
891 Resource string
892 Certificate string
893 Reason int
894 }
895 decodeJWSRequest(t, &req, r)
896 if req.Resource != "revoke-cert" {
897 t.Errorf("req.Resource = %q; want revoke-cert", req.Resource)
898 }
899 if req.Reason != 1 {
900 t.Errorf("req.Reason = %d; want 1", req.Reason)
901 }
902 // echo -n cert | base64 | tr -d '=' | tr '/+' '_-'
903 cert := "Y2VydA"
904 if req.Certificate != cert {
905 t.Errorf("req.Certificate = %q; want %q", req.Certificate, cert)
906 }
907 }))
908 defer ts.Close()
909 client := &Client{
Alex Vaghin7e016f12016-08-21 18:20:10 +0200910 Key: testKeyEC,
Alex Vaghin9fbab142016-08-16 16:28:56 +0200911 dir: &Directory{RevokeURL: ts.URL},
912 }
913 ctx := context.Background()
914 if err := client.RevokeCert(ctx, nil, []byte("cert"), CRLReasonKeyCompromise); err != nil {
915 t.Fatal(err)
916 }
917}
918
Alex Vaghin1777f3b2016-03-18 12:12:46 +0000919func TestFetchNonce(t *testing.T) {
920 tests := []struct {
921 code int
922 nonce string
923 }{
924 {http.StatusOK, "nonce1"},
925 {http.StatusBadRequest, "nonce2"},
926 {http.StatusOK, ""},
927 }
928 var i int
929 ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
930 if r.Method != "HEAD" {
931 t.Errorf("%d: r.Method = %q; want HEAD", i, r.Method)
932 }
933 w.Header().Set("replay-nonce", tests[i].nonce)
934 w.WriteHeader(tests[i].code)
935 }))
936 defer ts.Close()
937 for ; i < len(tests); i++ {
938 test := tests[i]
Alex Vaghin807ffea2016-08-16 15:36:38 +0200939 n, err := fetchNonce(context.Background(), http.DefaultClient, ts.URL)
Alex Vaghin1777f3b2016-03-18 12:12:46 +0000940 if n != test.nonce {
941 t.Errorf("%d: n=%q; want %q", i, n, test.nonce)
942 }
943 switch {
944 case err == nil && test.nonce == "":
945 t.Errorf("%d: n=%q, err=%v; want non-nil error", i, n, err)
946 case err != nil && test.nonce != "":
947 t.Errorf("%d: n=%q, err=%v; want %q", i, n, err, test.nonce)
948 }
949 }
950}
951
952func TestLinkHeader(t *testing.T) {
953 h := http.Header{"Link": {
954 `<https://example.com/acme/new-authz>;rel="next"`,
955 `<https://example.com/acme/recover-reg>; rel=recover`,
956 `<https://example.com/acme/terms>; foo=bar; rel="terms-of-service"`,
Alex Vaghin6575f7e2016-08-10 16:19:56 +0200957 `<dup>;rel="next"`,
Alex Vaghin1777f3b2016-03-18 12:12:46 +0000958 }}
Alex Vaghin6575f7e2016-08-10 16:19:56 +0200959 tests := []struct {
960 rel string
961 out []string
962 }{
963 {"next", []string{"https://example.com/acme/new-authz", "dup"}},
964 {"recover", []string{"https://example.com/acme/recover-reg"}},
965 {"terms-of-service", []string{"https://example.com/acme/terms"}},
966 {"empty", nil},
Alex Vaghin1777f3b2016-03-18 12:12:46 +0000967 }
968 for i, test := range tests {
Alex Vaghin6575f7e2016-08-10 16:19:56 +0200969 if v := linkHeader(h, test.rel); !reflect.DeepEqual(v, test.out) {
970 t.Errorf("%d: linkHeader(%q): %v; want %v", i, test.rel, v, test.out)
Alex Vaghin1777f3b2016-03-18 12:12:46 +0000971 }
972 }
973}
974
975func TestErrorResponse(t *testing.T) {
976 s := `{
977 "status": 400,
978 "type": "urn:acme:error:xxx",
979 "detail": "text"
980 }`
981 res := &http.Response{
982 StatusCode: 400,
983 Status: "400 Bad Request",
984 Body: ioutil.NopCloser(strings.NewReader(s)),
985 Header: http.Header{"X-Foo": {"bar"}},
986 }
987 err := responseError(res)
988 v, ok := err.(*Error)
989 if !ok {
990 t.Fatalf("err = %+v (%T); want *Error type", err, err)
991 }
992 if v.StatusCode != 400 {
993 t.Errorf("v.StatusCode = %v; want 400", v.StatusCode)
994 }
995 if v.ProblemType != "urn:acme:error:xxx" {
996 t.Errorf("v.ProblemType = %q; want urn:acme:error:xxx", v.ProblemType)
997 }
998 if v.Detail != "text" {
999 t.Errorf("v.Detail = %q; want text", v.Detail)
1000 }
1001 if !reflect.DeepEqual(v.Header, res.Header) {
1002 t.Errorf("v.Header = %+v; want %+v", v.Header, res.Header)
1003 }
1004}
Alex Vaghin7a1054f2016-08-03 21:58:22 +02001005
1006func TestTLSSNI01ChallengeCert(t *testing.T) {
1007 const (
1008 token = "evaGxfADs6pSRb2LAv9IZf17Dt3juxGJ-PCt92wr-oA"
Alex Vaghin7e016f12016-08-21 18:20:10 +02001009 // echo -n <token.testKeyECThumbprint> | shasum -a 256
1010 san = "dbbd5eefe7b4d06eb9d1d9f5acb4c7cd.a27d320e4b30332f0b6cb441734ad7b0.acme.invalid"
Alex Vaghin7a1054f2016-08-03 21:58:22 +02001011 )
1012
Alex Vaghin7e016f12016-08-21 18:20:10 +02001013 client := &Client{Key: testKeyEC}
Alex Vaghin595bbbd2016-08-11 17:03:39 +02001014 tlscert, name, err := client.TLSSNI01ChallengeCert(token)
Alex Vaghin7a1054f2016-08-03 21:58:22 +02001015 if err != nil {
1016 t.Fatal(err)
1017 }
1018
1019 if n := len(tlscert.Certificate); n != 1 {
1020 t.Fatalf("len(tlscert.Certificate) = %d; want 1", n)
1021 }
1022 cert, err := x509.ParseCertificate(tlscert.Certificate[0])
1023 if err != nil {
1024 t.Fatal(err)
1025 }
1026 if len(cert.DNSNames) != 1 || cert.DNSNames[0] != san {
Alex Vaghin595bbbd2016-08-11 17:03:39 +02001027 t.Fatalf("cert.DNSNames = %v; want %q", cert.DNSNames, san)
1028 }
1029 if cert.DNSNames[0] != name {
1030 t.Errorf("cert.DNSNames[0] != name: %q vs %q", cert.DNSNames[0], name)
Alex Vaghin7a1054f2016-08-03 21:58:22 +02001031 }
1032}
Alex Vaghina548aac2016-08-13 22:38:22 +02001033
Alex Vaghin7a1054f2016-08-03 21:58:22 +02001034func TestTLSSNI02ChallengeCert(t *testing.T) {
1035 const (
1036 token = "evaGxfADs6pSRb2LAv9IZf17Dt3juxGJ-PCt92wr-oA"
1037 // echo -n evaGxfADs6pSRb2LAv9IZf17Dt3juxGJ-PCt92wr-oA | shasum -a 256
1038 sanA = "7ea0aaa69214e71e02cebb18bb867736.09b730209baabf60e43d4999979ff139.token.acme.invalid"
Alex Vaghin7e016f12016-08-21 18:20:10 +02001039 // echo -n <token.testKeyECThumbprint> | shasum -a 256
1040 sanB = "dbbd5eefe7b4d06eb9d1d9f5acb4c7cd.a27d320e4b30332f0b6cb441734ad7b0.ka.acme.invalid"
Alex Vaghin7a1054f2016-08-03 21:58:22 +02001041 )
1042
Alex Vaghin7e016f12016-08-21 18:20:10 +02001043 client := &Client{Key: testKeyEC}
Alex Vaghin595bbbd2016-08-11 17:03:39 +02001044 tlscert, name, err := client.TLSSNI02ChallengeCert(token)
Alex Vaghin7a1054f2016-08-03 21:58:22 +02001045 if err != nil {
1046 t.Fatal(err)
1047 }
1048
1049 if n := len(tlscert.Certificate); n != 1 {
1050 t.Fatalf("len(tlscert.Certificate) = %d; want 1", n)
1051 }
1052 cert, err := x509.ParseCertificate(tlscert.Certificate[0])
1053 if err != nil {
1054 t.Fatal(err)
1055 }
1056 names := []string{sanA, sanB}
1057 if !reflect.DeepEqual(cert.DNSNames, names) {
Alex Vaghin595bbbd2016-08-11 17:03:39 +02001058 t.Fatalf("cert.DNSNames = %v;\nwant %v", cert.DNSNames, names)
1059 }
1060 sort.Strings(cert.DNSNames)
1061 i := sort.SearchStrings(cert.DNSNames, name)
1062 if i >= len(cert.DNSNames) || cert.DNSNames[i] != name {
1063 t.Errorf("%v doesn't have %q", cert.DNSNames, name)
Alex Vaghin7a1054f2016-08-03 21:58:22 +02001064 }
1065}
Alex Vaghin1f83de12016-08-14 22:48:00 +02001066
Alex Vaghine3112312016-09-12 12:18:32 +02001067func TestTLSChallengeCertOpt(t *testing.T) {
Alex Vaghinb13fc1f2016-08-25 09:06:35 +02001068 key, err := rsa.GenerateKey(rand.Reader, 512)
1069 if err != nil {
1070 t.Fatal(err)
1071 }
Alex Vaghine3112312016-09-12 12:18:32 +02001072 tmpl := &x509.Certificate{
1073 SerialNumber: big.NewInt(2),
1074 Subject: pkix.Name{Organization: []string{"Test"}},
1075 DNSNames: []string{"should-be-overwritten"},
1076 }
1077 opts := []CertOption{WithKey(key), WithTemplate(tmpl)}
1078
Alex Vaghinb13fc1f2016-08-25 09:06:35 +02001079 client := &Client{Key: testKeyEC}
Alex Vaghine3112312016-09-12 12:18:32 +02001080 cert1, _, err := client.TLSSNI01ChallengeCert("token", opts...)
Alex Vaghinb13fc1f2016-08-25 09:06:35 +02001081 if err != nil {
1082 t.Fatal(err)
1083 }
Alex Vaghine3112312016-09-12 12:18:32 +02001084 cert2, _, err := client.TLSSNI02ChallengeCert("token", opts...)
Alex Vaghinb13fc1f2016-08-25 09:06:35 +02001085 if err != nil {
1086 t.Fatal(err)
1087 }
Alex Vaghine3112312016-09-12 12:18:32 +02001088
Alex Vaghinb13fc1f2016-08-25 09:06:35 +02001089 for i, tlscert := range []tls.Certificate{cert1, cert2} {
1090 // verify generated cert private key
1091 tlskey, ok := tlscert.PrivateKey.(*rsa.PrivateKey)
1092 if !ok {
1093 t.Errorf("%d: tlscert.PrivateKey is %T; want *rsa.PrivateKey", i, tlscert.PrivateKey)
1094 continue
1095 }
1096 if tlskey.D.Cmp(key.D) != 0 {
1097 t.Errorf("%d: tlskey.D = %v; want %v", i, tlskey.D, key.D)
1098 }
1099 // verify generated cert public key
1100 x509Cert, err := x509.ParseCertificate(tlscert.Certificate[0])
1101 if err != nil {
1102 t.Errorf("%d: %v", i, err)
1103 continue
1104 }
1105 tlspub, ok := x509Cert.PublicKey.(*rsa.PublicKey)
1106 if !ok {
1107 t.Errorf("%d: x509Cert.PublicKey is %T; want *rsa.PublicKey", i, x509Cert.PublicKey)
1108 continue
1109 }
1110 if tlspub.N.Cmp(key.N) != 0 {
1111 t.Errorf("%d: tlspub.N = %v; want %v", i, tlspub.N, key.N)
1112 }
Alex Vaghine3112312016-09-12 12:18:32 +02001113 // verify template option
1114 sn := big.NewInt(2)
1115 if x509Cert.SerialNumber.Cmp(sn) != 0 {
1116 t.Errorf("%d: SerialNumber = %v; want %v", i, x509Cert.SerialNumber, sn)
1117 }
1118 org := []string{"Test"}
1119 if !reflect.DeepEqual(x509Cert.Subject.Organization, org) {
1120 t.Errorf("%d: Subject.Organization = %+v; want %+v", i, x509Cert.Subject.Organization, org)
1121 }
1122 for _, v := range x509Cert.DNSNames {
1123 if !strings.HasSuffix(v, ".acme.invalid") {
1124 t.Errorf("%d: invalid DNSNames element: %q", i, v)
1125 }
1126 }
Alex Vaghinb13fc1f2016-08-25 09:06:35 +02001127 }
1128}
1129
Alex Vaghin1f83de12016-08-14 22:48:00 +02001130func TestHTTP01Challenge(t *testing.T) {
1131 const (
1132 token = "xxx"
Alex Vaghin7e016f12016-08-21 18:20:10 +02001133 // thumbprint is precomputed for testKeyEC in jws_test.go
1134 value = token + "." + testKeyECThumbprint
Alex Vaghin1f83de12016-08-14 22:48:00 +02001135 urlpath = "/.well-known/acme-challenge/" + token
1136 )
Alex Vaghin7e016f12016-08-21 18:20:10 +02001137 client := &Client{Key: testKeyEC}
Alex Vaghin1f83de12016-08-14 22:48:00 +02001138 val, err := client.HTTP01ChallengeResponse(token)
1139 if err != nil {
1140 t.Fatal(err)
1141 }
1142 if val != value {
1143 t.Errorf("val = %q; want %q", val, value)
1144 }
1145 if path := client.HTTP01ChallengePath(token); path != urlpath {
1146 t.Errorf("path = %q; want %q", path, urlpath)
1147 }
1148}
Alex Vaghinb35ccbc2016-08-19 11:32:22 +02001149
Alex Vaghin351dc6a2016-08-24 15:50:57 +02001150func TestDNS01ChallengeRecord(t *testing.T) {
1151 // echo -n xxx.<testKeyECThumbprint> | \
1152 // openssl dgst -binary -sha256 | \
1153 // base64 | tr -d '=' | tr '/+' '_-'
1154 const value = "8DERMexQ5VcdJ_prpPiA0mVdp7imgbCgjsG4SqqNMIo"
1155
1156 client := &Client{Key: testKeyEC}
1157 val, err := client.DNS01ChallengeRecord("xxx")
1158 if err != nil {
1159 t.Fatal(err)
1160 }
1161 if val != value {
1162 t.Errorf("val = %q; want %q", val, value)
1163 }
1164}
1165
Alex Vaghinb35ccbc2016-08-19 11:32:22 +02001166func TestBackoff(t *testing.T) {
1167 tt := []struct{ min, max time.Duration }{
1168 {time.Second, 2 * time.Second},
1169 {2 * time.Second, 3 * time.Second},
1170 {4 * time.Second, 5 * time.Second},
1171 {8 * time.Second, 9 * time.Second},
1172 }
1173 for i, test := range tt {
1174 d := backoff(i, time.Minute)
1175 if d < test.min || test.max < d {
1176 t.Errorf("%d: d = %v; want between %v and %v", i, d, test.min, test.max)
1177 }
1178 }
1179
1180 min, max := time.Second, 2*time.Second
1181 if d := backoff(-1, time.Minute); d < min || max < d {
1182 t.Errorf("d = %v; want between %v and %v", d, min, max)
1183 }
1184
1185 bound := 10 * time.Second
1186 if d := backoff(100, bound); d != bound {
1187 t.Errorf("d = %v; want %v", d, bound)
1188 }
1189}