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. 4 5package acme 6 7import ( 8 "bytes" 9 "context" 10 "crypto/rand" 11 "crypto/rsa" 12 "crypto/tls" 13 "crypto/x509" 14 "crypto/x509/pkix" 15 "encoding/base64" 16 "encoding/hex" 17 "encoding/json" 18 "fmt" 19 "io" 20 "math/big" 21 "net/http" 22 "net/http/httptest" 23 "reflect" 24 "sort" 25 "strings" 26 "testing" 27 "time" 28) 29 30// newTestClient creates a client with a non-nil Directory so that it skips 31// the discovery which is otherwise done on the first call of almost every 32// exported method. 33func newTestClient() *Client { 34 return &Client{ 35 Key: testKeyEC, 36 dir: &Directory{}, // skip discovery 37 } 38} 39 40// Decodes a JWS-encoded request and unmarshals the decoded JSON into a provided 41// interface. 42func decodeJWSRequest(t *testing.T, v interface{}, r io.Reader) { 43 // Decode request 44 var req struct{ Payload string } 45 if err := json.NewDecoder(r).Decode(&req); err != nil { 46 t.Fatal(err) 47 } 48 payload, err := base64.RawURLEncoding.DecodeString(req.Payload) 49 if err != nil { 50 t.Fatal(err) 51 } 52 err = json.Unmarshal(payload, v) 53 if err != nil { 54 t.Fatal(err) 55 } 56} 57 58type jwsHead struct { 59 Alg string 60 Nonce string 61 URL string `json:"url"` 62 KID string `json:"kid"` 63 JWK map[string]string `json:"jwk"` 64} 65 66func decodeJWSHead(r io.Reader) (*jwsHead, error) { 67 var req struct{ Protected string } 68 if err := json.NewDecoder(r).Decode(&req); err != nil { 69 return nil, err 70 } 71 b, err := base64.RawURLEncoding.DecodeString(req.Protected) 72 if err != nil { 73 return nil, err 74 } 75 var head jwsHead 76 if err := json.Unmarshal(b, &head); err != nil { 77 return nil, err 78 } 79 return &head, nil 80} 81 82func TestDiscover(t *testing.T) { 83 const ( 84 reg = "https://example.com/acme/new-reg" 85 authz = "https://example.com/acme/new-authz" 86 cert = "https://example.com/acme/new-cert" 87 revoke = "https://example.com/acme/revoke-cert" 88 ) 89 ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 90 w.Header().Set("Content-Type", "application/json") 91 w.Header().Set("Replay-Nonce", "testnonce") 92 fmt.Fprintf(w, `{ 93 "new-reg": %q, 94 "new-authz": %q, 95 "new-cert": %q, 96 "revoke-cert": %q 97 }`, reg, authz, cert, revoke) 98 })) 99 defer ts.Close() 100 c := Client{DirectoryURL: ts.URL} 101 dir, err := c.Discover(context.Background()) 102 if err != nil { 103 t.Fatal(err) 104 } 105 if dir.RegURL != reg { 106 t.Errorf("dir.RegURL = %q; want %q", dir.RegURL, reg) 107 } 108 if dir.AuthzURL != authz { 109 t.Errorf("dir.AuthzURL = %q; want %q", dir.AuthzURL, authz) 110 } 111 if dir.CertURL != cert { 112 t.Errorf("dir.CertURL = %q; want %q", dir.CertURL, cert) 113 } 114 if dir.RevokeURL != revoke { 115 t.Errorf("dir.RevokeURL = %q; want %q", dir.RevokeURL, revoke) 116 } 117 if _, exist := c.nonces["testnonce"]; !exist { 118 t.Errorf("c.nonces = %q; want 'testnonce' in the map", c.nonces) 119 } 120} 121 122func TestRegister(t *testing.T) { 123 contacts := []string{"mailto:admin@example.com"} 124 125 ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 126 if r.Method == "HEAD" { 127 w.Header().Set("Replay-Nonce", "test-nonce") 128 return 129 } 130 if r.Method != "POST" { 131 t.Errorf("r.Method = %q; want POST", r.Method) 132 } 133 134 var j struct { 135 Resource string 136 Contact []string 137 Agreement string 138 } 139 decodeJWSRequest(t, &j, r.Body) 140 141 // Test request 142 if j.Resource != "new-reg" { 143 t.Errorf("j.Resource = %q; want new-reg", j.Resource) 144 } 145 if !reflect.DeepEqual(j.Contact, contacts) { 146 t.Errorf("j.Contact = %v; want %v", j.Contact, contacts) 147 } 148 149 w.Header().Set("Location", "https://ca.tld/acme/reg/1") 150 w.Header().Set("Link", `<https://ca.tld/acme/new-authz>;rel="next"`) 151 w.Header().Add("Link", `<https://ca.tld/acme/recover-reg>;rel="recover"`) 152 w.Header().Add("Link", `<https://ca.tld/acme/terms>;rel="terms-of-service"`) 153 w.WriteHeader(http.StatusCreated) 154 b, _ := json.Marshal(contacts) 155 fmt.Fprintf(w, `{"contact": %s}`, b) 156 })) 157 defer ts.Close() 158 159 prompt := func(url string) bool { 160 const terms = "https://ca.tld/acme/terms" 161 if url != terms { 162 t.Errorf("prompt url = %q; want %q", url, terms) 163 } 164 return false 165 } 166 167 c := Client{ 168 Key: testKeyEC, 169 DirectoryURL: ts.URL, 170 dir: &Directory{RegURL: ts.URL}, 171 } 172 a := &Account{Contact: contacts} 173 var err error 174 if a, err = c.Register(context.Background(), a, prompt); err != nil { 175 t.Fatal(err) 176 } 177 if a.URI != "https://ca.tld/acme/reg/1" { 178 t.Errorf("a.URI = %q; want https://ca.tld/acme/reg/1", a.URI) 179 } 180 if a.Authz != "https://ca.tld/acme/new-authz" { 181 t.Errorf("a.Authz = %q; want https://ca.tld/acme/new-authz", a.Authz) 182 } 183 if a.CurrentTerms != "https://ca.tld/acme/terms" { 184 t.Errorf("a.CurrentTerms = %q; want https://ca.tld/acme/terms", a.CurrentTerms) 185 } 186 if !reflect.DeepEqual(a.Contact, contacts) { 187 t.Errorf("a.Contact = %v; want %v", a.Contact, contacts) 188 } 189} 190 191func TestUpdateReg(t *testing.T) { 192 const terms = "https://ca.tld/acme/terms" 193 contacts := []string{"mailto:admin@example.com"} 194 195 ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 196 if r.Method == "HEAD" { 197 w.Header().Set("Replay-Nonce", "test-nonce") 198 return 199 } 200 if r.Method != "POST" { 201 t.Errorf("r.Method = %q; want POST", r.Method) 202 } 203 204 var j struct { 205 Resource string 206 Contact []string 207 Agreement string 208 } 209 decodeJWSRequest(t, &j, r.Body) 210 211 // Test request 212 if j.Resource != "reg" { 213 t.Errorf("j.Resource = %q; want reg", j.Resource) 214 } 215 if j.Agreement != terms { 216 t.Errorf("j.Agreement = %q; want %q", j.Agreement, terms) 217 } 218 if !reflect.DeepEqual(j.Contact, contacts) { 219 t.Errorf("j.Contact = %v; want %v", j.Contact, contacts) 220 } 221 222 w.Header().Set("Link", `<https://ca.tld/acme/new-authz>;rel="next"`) 223 w.Header().Add("Link", `<https://ca.tld/acme/recover-reg>;rel="recover"`) 224 w.Header().Add("Link", fmt.Sprintf(`<%s>;rel="terms-of-service"`, terms)) 225 w.WriteHeader(http.StatusOK) 226 b, _ := json.Marshal(contacts) 227 fmt.Fprintf(w, `{"contact":%s, "agreement":%q}`, b, terms) 228 })) 229 defer ts.Close() 230 231 c := Client{ 232 Key: testKeyEC, 233 DirectoryURL: ts.URL, // don't dial outside of localhost 234 dir: &Directory{}, // don't do discovery 235 } 236 a := &Account{URI: ts.URL, Contact: contacts, AgreedTerms: terms} 237 var err error 238 if a, err = c.UpdateReg(context.Background(), a); err != nil { 239 t.Fatal(err) 240 } 241 if a.Authz != "https://ca.tld/acme/new-authz" { 242 t.Errorf("a.Authz = %q; want https://ca.tld/acme/new-authz", a.Authz) 243 } 244 if a.AgreedTerms != terms { 245 t.Errorf("a.AgreedTerms = %q; want %q", a.AgreedTerms, terms) 246 } 247 if a.CurrentTerms != terms { 248 t.Errorf("a.CurrentTerms = %q; want %q", a.CurrentTerms, terms) 249 } 250 if a.URI != ts.URL { 251 t.Errorf("a.URI = %q; want %q", a.URI, ts.URL) 252 } 253} 254 255func TestGetReg(t *testing.T) { 256 const terms = "https://ca.tld/acme/terms" 257 const newTerms = "https://ca.tld/acme/new-terms" 258 contacts := []string{"mailto:admin@example.com"} 259 260 ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 261 if r.Method == "HEAD" { 262 w.Header().Set("Replay-Nonce", "test-nonce") 263 return 264 } 265 if r.Method != "POST" { 266 t.Errorf("r.Method = %q; want POST", r.Method) 267 } 268 269 var j struct { 270 Resource string 271 Contact []string 272 Agreement string 273 } 274 decodeJWSRequest(t, &j, r.Body) 275 276 // Test request 277 if j.Resource != "reg" { 278 t.Errorf("j.Resource = %q; want reg", j.Resource) 279 } 280 if len(j.Contact) != 0 { 281 t.Errorf("j.Contact = %v", j.Contact) 282 } 283 if j.Agreement != "" { 284 t.Errorf("j.Agreement = %q", j.Agreement) 285 } 286 287 w.Header().Set("Link", `<https://ca.tld/acme/new-authz>;rel="next"`) 288 w.Header().Add("Link", `<https://ca.tld/acme/recover-reg>;rel="recover"`) 289 w.Header().Add("Link", fmt.Sprintf(`<%s>;rel="terms-of-service"`, newTerms)) 290 w.WriteHeader(http.StatusOK) 291 b, _ := json.Marshal(contacts) 292 fmt.Fprintf(w, `{"contact":%s, "agreement":%q}`, b, terms) 293 })) 294 defer ts.Close() 295 296 c := Client{ 297 Key: testKeyEC, 298 DirectoryURL: ts.URL, // don't dial outside of localhost 299 dir: &Directory{}, // don't do discovery 300 } 301 a, err := c.GetReg(context.Background(), ts.URL) 302 if err != nil { 303 t.Fatal(err) 304 } 305 if a.Authz != "https://ca.tld/acme/new-authz" { 306 t.Errorf("a.AuthzURL = %q; want https://ca.tld/acme/new-authz", a.Authz) 307 } 308 if a.AgreedTerms != terms { 309 t.Errorf("a.AgreedTerms = %q; want %q", a.AgreedTerms, terms) 310 } 311 if a.CurrentTerms != newTerms { 312 t.Errorf("a.CurrentTerms = %q; want %q", a.CurrentTerms, newTerms) 313 } 314 if a.URI != ts.URL { 315 t.Errorf("a.URI = %q; want %q", a.URI, ts.URL) 316 } 317} 318 319func TestAuthorize(t *testing.T) { 320 tt := []struct{ typ, value string }{ 321 {"dns", "example.com"}, 322 {"ip", "1.2.3.4"}, 323 } 324 for _, test := range tt { 325 t.Run(test.typ, func(t *testing.T) { 326 ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 327 if r.Method == "HEAD" { 328 w.Header().Set("Replay-Nonce", "test-nonce") 329 return 330 } 331 if r.Method != "POST" { 332 t.Errorf("r.Method = %q; want POST", r.Method) 333 } 334 335 var j struct { 336 Resource string 337 Identifier struct { 338 Type string 339 Value string 340 } 341 } 342 decodeJWSRequest(t, &j, r.Body) 343 344 // Test request 345 if j.Resource != "new-authz" { 346 t.Errorf("j.Resource = %q; want new-authz", j.Resource) 347 } 348 if j.Identifier.Type != test.typ { 349 t.Errorf("j.Identifier.Type = %q; want %q", j.Identifier.Type, test.typ) 350 } 351 if j.Identifier.Value != test.value { 352 t.Errorf("j.Identifier.Value = %q; want %q", j.Identifier.Value, test.value) 353 } 354 355 w.Header().Set("Location", "https://ca.tld/acme/auth/1") 356 w.WriteHeader(http.StatusCreated) 357 fmt.Fprintf(w, `{ 358 "identifier": {"type":%q,"value":%q}, 359 "status":"pending", 360 "challenges":[ 361 { 362 "type":"http-01", 363 "status":"pending", 364 "uri":"https://ca.tld/acme/challenge/publickey/id1", 365 "token":"token1" 366 }, 367 { 368 "type":"tls-sni-01", 369 "status":"pending", 370 "uri":"https://ca.tld/acme/challenge/publickey/id2", 371 "token":"token2" 372 } 373 ], 374 "combinations":[[0],[1]] 375 }`, test.typ, test.value) 376 })) 377 defer ts.Close() 378 379 var ( 380 auth *Authorization 381 err error 382 ) 383 cl := Client{ 384 Key: testKeyEC, 385 DirectoryURL: ts.URL, 386 dir: &Directory{AuthzURL: ts.URL}, 387 } 388 switch test.typ { 389 case "dns": 390 auth, err = cl.Authorize(context.Background(), test.value) 391 case "ip": 392 auth, err = cl.AuthorizeIP(context.Background(), test.value) 393 default: 394 t.Fatalf("unknown identifier type: %q", test.typ) 395 } 396 if err != nil { 397 t.Fatal(err) 398 } 399 400 if auth.URI != "https://ca.tld/acme/auth/1" { 401 t.Errorf("URI = %q; want https://ca.tld/acme/auth/1", auth.URI) 402 } 403 if auth.Status != "pending" { 404 t.Errorf("Status = %q; want pending", auth.Status) 405 } 406 if auth.Identifier.Type != test.typ { 407 t.Errorf("Identifier.Type = %q; want %q", auth.Identifier.Type, test.typ) 408 } 409 if auth.Identifier.Value != test.value { 410 t.Errorf("Identifier.Value = %q; want %q", auth.Identifier.Value, test.value) 411 } 412 413 if n := len(auth.Challenges); n != 2 { 414 t.Fatalf("len(auth.Challenges) = %d; want 2", n) 415 } 416 417 c := auth.Challenges[0] 418 if c.Type != "http-01" { 419 t.Errorf("c.Type = %q; want http-01", c.Type) 420 } 421 if c.URI != "https://ca.tld/acme/challenge/publickey/id1" { 422 t.Errorf("c.URI = %q; want https://ca.tld/acme/challenge/publickey/id1", c.URI) 423 } 424 if c.Token != "token1" { 425 t.Errorf("c.Token = %q; want token1", c.Token) 426 } 427 428 c = auth.Challenges[1] 429 if c.Type != "tls-sni-01" { 430 t.Errorf("c.Type = %q; want tls-sni-01", c.Type) 431 } 432 if c.URI != "https://ca.tld/acme/challenge/publickey/id2" { 433 t.Errorf("c.URI = %q; want https://ca.tld/acme/challenge/publickey/id2", c.URI) 434 } 435 if c.Token != "token2" { 436 t.Errorf("c.Token = %q; want token2", c.Token) 437 } 438 439 combs := [][]int{{0}, {1}} 440 if !reflect.DeepEqual(auth.Combinations, combs) { 441 t.Errorf("auth.Combinations: %+v\nwant: %+v\n", auth.Combinations, combs) 442 } 443 444 }) 445 } 446} 447 448func TestAuthorizeValid(t *testing.T) { 449 ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 450 if r.Method == "HEAD" { 451 w.Header().Set("Replay-Nonce", "nonce") 452 return 453 } 454 w.WriteHeader(http.StatusCreated) 455 w.Write([]byte(`{"status":"valid"}`)) 456 })) 457 defer ts.Close() 458 client := Client{ 459 Key: testKey, 460 DirectoryURL: ts.URL, 461 dir: &Directory{AuthzURL: ts.URL}, 462 } 463 _, err := client.Authorize(context.Background(), "example.com") 464 if err != nil { 465 t.Errorf("err = %v", err) 466 } 467} 468 469func TestGetAuthorization(t *testing.T) { 470 ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 471 if r.Method != "GET" { 472 t.Errorf("r.Method = %q; want GET", r.Method) 473 } 474 475 w.WriteHeader(http.StatusOK) 476 fmt.Fprintf(w, `{ 477 "identifier": {"type":"dns","value":"example.com"}, 478 "status":"pending", 479 "challenges":[ 480 { 481 "type":"http-01", 482 "status":"pending", 483 "uri":"https://ca.tld/acme/challenge/publickey/id1", 484 "token":"token1" 485 }, 486 { 487 "type":"tls-sni-01", 488 "status":"pending", 489 "uri":"https://ca.tld/acme/challenge/publickey/id2", 490 "token":"token2" 491 } 492 ], 493 "combinations":[[0],[1]]}`) 494 })) 495 defer ts.Close() 496 497 cl := Client{Key: testKeyEC, DirectoryURL: ts.URL} 498 auth, err := cl.GetAuthorization(context.Background(), ts.URL) 499 if err != nil { 500 t.Fatal(err) 501 } 502 503 if auth.Status != "pending" { 504 t.Errorf("Status = %q; want pending", auth.Status) 505 } 506 if auth.Identifier.Type != "dns" { 507 t.Errorf("Identifier.Type = %q; want dns", auth.Identifier.Type) 508 } 509 if auth.Identifier.Value != "example.com" { 510 t.Errorf("Identifier.Value = %q; want example.com", auth.Identifier.Value) 511 } 512 513 if n := len(auth.Challenges); n != 2 { 514 t.Fatalf("len(set.Challenges) = %d; want 2", n) 515 } 516 517 c := auth.Challenges[0] 518 if c.Type != "http-01" { 519 t.Errorf("c.Type = %q; want http-01", c.Type) 520 } 521 if c.URI != "https://ca.tld/acme/challenge/publickey/id1" { 522 t.Errorf("c.URI = %q; want https://ca.tld/acme/challenge/publickey/id1", c.URI) 523 } 524 if c.Token != "token1" { 525 t.Errorf("c.Token = %q; want token1", c.Token) 526 } 527 528 c = auth.Challenges[1] 529 if c.Type != "tls-sni-01" { 530 t.Errorf("c.Type = %q; want tls-sni-01", c.Type) 531 } 532 if c.URI != "https://ca.tld/acme/challenge/publickey/id2" { 533 t.Errorf("c.URI = %q; want https://ca.tld/acme/challenge/publickey/id2", c.URI) 534 } 535 if c.Token != "token2" { 536 t.Errorf("c.Token = %q; want token2", c.Token) 537 } 538 539 combs := [][]int{{0}, {1}} 540 if !reflect.DeepEqual(auth.Combinations, combs) { 541 t.Errorf("auth.Combinations: %+v\nwant: %+v\n", auth.Combinations, combs) 542 } 543} 544 545func TestWaitAuthorization(t *testing.T) { 546 t.Run("wait loop", func(t *testing.T) { 547 var count int 548 authz, err := runWaitAuthorization(context.Background(), t, func(w http.ResponseWriter, r *http.Request) { 549 count++ 550 w.Header().Set("Retry-After", "0") 551 if count > 1 { 552 fmt.Fprintf(w, `{"status":"valid"}`) 553 return 554 } 555 fmt.Fprintf(w, `{"status":"pending"}`) 556 }) 557 if err != nil { 558 t.Fatalf("non-nil error: %v", err) 559 } 560 if authz == nil { 561 t.Fatal("authz is nil") 562 } 563 }) 564 t.Run("invalid status", func(t *testing.T) { 565 _, err := runWaitAuthorization(context.Background(), t, func(w http.ResponseWriter, r *http.Request) { 566 fmt.Fprintf(w, `{"status":"invalid"}`) 567 }) 568 if _, ok := err.(*AuthorizationError); !ok { 569 t.Errorf("err is %v (%T); want non-nil *AuthorizationError", err, err) 570 } 571 }) 572 t.Run("invalid status with error returns the authorization error", func(t *testing.T) { 573 _, err := runWaitAuthorization(context.Background(), t, func(w http.ResponseWriter, r *http.Request) { 574 fmt.Fprintf(w, `{ 575 "type": "dns-01", 576 "status": "invalid", 577 "error": { 578 "type": "urn:ietf:params:acme:error:caa", 579 "detail": "CAA record for <domain> prevents issuance", 580 "status": 403 581 }, 582 "url": "https://acme-v02.api.letsencrypt.org/acme/chall-v3/xxx/xxx", 583 "token": "xxx", 584 "validationRecord": [ 585 { 586 "hostname": "<domain>" 587 } 588 ] 589 }`) 590 }) 591 592 want := &AuthorizationError{ 593 Errors: []error{ 594 (&wireError{ 595 Status: 403, 596 Type: "urn:ietf:params:acme:error:caa", 597 Detail: "CAA record for <domain> prevents issuance", 598 }).error(nil), 599 }, 600 } 601 602 _, ok := err.(*AuthorizationError) 603 if !ok { 604 t.Errorf("err is %T; want non-nil *AuthorizationError", err) 605 } 606 607 if err.Error() != want.Error() { 608 t.Errorf("err is %v; want %v", err, want) 609 } 610 }) 611 t.Run("non-retriable error", func(t *testing.T) { 612 const code = http.StatusBadRequest 613 _, err := runWaitAuthorization(context.Background(), t, func(w http.ResponseWriter, r *http.Request) { 614 w.WriteHeader(code) 615 }) 616 res, ok := err.(*Error) 617 if !ok { 618 t.Fatalf("err is %v (%T); want a non-nil *Error", err, err) 619 } 620 if res.StatusCode != code { 621 t.Errorf("res.StatusCode = %d; want %d", res.StatusCode, code) 622 } 623 }) 624 for _, code := range []int{http.StatusTooManyRequests, http.StatusInternalServerError} { 625 t.Run(fmt.Sprintf("retriable %d error", code), func(t *testing.T) { 626 var count int 627 authz, err := runWaitAuthorization(context.Background(), t, func(w http.ResponseWriter, r *http.Request) { 628 count++ 629 w.Header().Set("Retry-After", "0") 630 if count > 1 { 631 fmt.Fprintf(w, `{"status":"valid"}`) 632 return 633 } 634 w.WriteHeader(code) 635 }) 636 if err != nil { 637 t.Fatalf("non-nil error: %v", err) 638 } 639 if authz == nil { 640 t.Fatal("authz is nil") 641 } 642 }) 643 } 644 t.Run("context cancel", func(t *testing.T) { 645 ctx, cancel := context.WithTimeout(context.Background(), 200*time.Millisecond) 646 defer cancel() 647 _, err := runWaitAuthorization(ctx, t, func(w http.ResponseWriter, r *http.Request) { 648 w.Header().Set("Retry-After", "60") 649 fmt.Fprintf(w, `{"status":"pending"}`) 650 }) 651 if err == nil { 652 t.Error("err is nil") 653 } 654 }) 655} 656func runWaitAuthorization(ctx context.Context, t *testing.T, h http.HandlerFunc) (*Authorization, error) { 657 t.Helper() 658 ts := httptest.NewServer(h) 659 defer ts.Close() 660 type res struct { 661 authz *Authorization 662 err error 663 } 664 ch := make(chan res, 1) 665 go func() { 666 var client = Client{DirectoryURL: ts.URL} 667 a, err := client.WaitAuthorization(ctx, ts.URL) 668 ch <- res{a, err} 669 }() 670 select { 671 case <-time.After(3 * time.Second): 672 t.Fatal("WaitAuthorization took too long to return") 673 case v := <-ch: 674 return v.authz, v.err 675 } 676 panic("runWaitAuthorization: out of select") 677} 678 679func TestRevokeAuthorization(t *testing.T) { 680 ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 681 if r.Method == "HEAD" { 682 w.Header().Set("Replay-Nonce", "nonce") 683 return 684 } 685 switch r.URL.Path { 686 case "/1": 687 var req struct { 688 Resource string 689 Status string 690 Delete bool 691 } 692 decodeJWSRequest(t, &req, r.Body) 693 if req.Resource != "authz" { 694 t.Errorf("req.Resource = %q; want authz", req.Resource) 695 } 696 if req.Status != "deactivated" { 697 t.Errorf("req.Status = %q; want deactivated", req.Status) 698 } 699 if !req.Delete { 700 t.Errorf("req.Delete is false") 701 } 702 case "/2": 703 w.WriteHeader(http.StatusBadRequest) 704 } 705 })) 706 defer ts.Close() 707 client := &Client{ 708 Key: testKey, 709 DirectoryURL: ts.URL, // don't dial outside of localhost 710 dir: &Directory{}, // don't do discovery 711 } 712 ctx := context.Background() 713 if err := client.RevokeAuthorization(ctx, ts.URL+"/1"); err != nil { 714 t.Errorf("err = %v", err) 715 } 716 if client.RevokeAuthorization(ctx, ts.URL+"/2") == nil { 717 t.Error("nil error") 718 } 719} 720 721func TestPollChallenge(t *testing.T) { 722 ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 723 if r.Method != "GET" { 724 t.Errorf("r.Method = %q; want GET", r.Method) 725 } 726 727 w.WriteHeader(http.StatusOK) 728 fmt.Fprintf(w, `{ 729 "type":"http-01", 730 "status":"pending", 731 "uri":"https://ca.tld/acme/challenge/publickey/id1", 732 "token":"token1"}`) 733 })) 734 defer ts.Close() 735 736 cl := Client{Key: testKeyEC, DirectoryURL: ts.URL} 737 chall, err := cl.GetChallenge(context.Background(), ts.URL) 738 if err != nil { 739 t.Fatal(err) 740 } 741 742 if chall.Status != "pending" { 743 t.Errorf("Status = %q; want pending", chall.Status) 744 } 745 if chall.Type != "http-01" { 746 t.Errorf("c.Type = %q; want http-01", chall.Type) 747 } 748 if chall.URI != "https://ca.tld/acme/challenge/publickey/id1" { 749 t.Errorf("c.URI = %q; want https://ca.tld/acme/challenge/publickey/id1", chall.URI) 750 } 751 if chall.Token != "token1" { 752 t.Errorf("c.Token = %q; want token1", chall.Token) 753 } 754} 755 756func TestAcceptChallenge(t *testing.T) { 757 ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 758 if r.Method == "HEAD" { 759 w.Header().Set("Replay-Nonce", "test-nonce") 760 return 761 } 762 if r.Method != "POST" { 763 t.Errorf("r.Method = %q; want POST", r.Method) 764 } 765 766 var j struct { 767 Resource string 768 Type string 769 Auth string `json:"keyAuthorization"` 770 } 771 decodeJWSRequest(t, &j, r.Body) 772 773 // Test request 774 if j.Resource != "challenge" { 775 t.Errorf(`resource = %q; want "challenge"`, j.Resource) 776 } 777 if j.Type != "http-01" { 778 t.Errorf(`type = %q; want "http-01"`, j.Type) 779 } 780 keyAuth := "token1." + testKeyECThumbprint 781 if j.Auth != keyAuth { 782 t.Errorf(`keyAuthorization = %q; want %q`, j.Auth, keyAuth) 783 } 784 785 // Respond to request 786 w.WriteHeader(http.StatusAccepted) 787 fmt.Fprintf(w, `{ 788 "type":"http-01", 789 "status":"pending", 790 "uri":"https://ca.tld/acme/challenge/publickey/id1", 791 "token":"token1", 792 "keyAuthorization":%q 793 }`, keyAuth) 794 })) 795 defer ts.Close() 796 797 cl := Client{ 798 Key: testKeyEC, 799 DirectoryURL: ts.URL, // don't dial outside of localhost 800 dir: &Directory{}, // don't do discovery 801 } 802 c, err := cl.Accept(context.Background(), &Challenge{ 803 URI: ts.URL, 804 Token: "token1", 805 Type: "http-01", 806 }) 807 if err != nil { 808 t.Fatal(err) 809 } 810 811 if c.Type != "http-01" { 812 t.Errorf("c.Type = %q; want http-01", c.Type) 813 } 814 if c.URI != "https://ca.tld/acme/challenge/publickey/id1" { 815 t.Errorf("c.URI = %q; want https://ca.tld/acme/challenge/publickey/id1", c.URI) 816 } 817 if c.Token != "token1" { 818 t.Errorf("c.Token = %q; want token1", c.Token) 819 } 820} 821 822func TestNewCert(t *testing.T) { 823 notBefore := time.Now() 824 notAfter := notBefore.AddDate(0, 2, 0) 825 timeNow = func() time.Time { return notBefore } 826 827 ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 828 if r.Method == "HEAD" { 829 w.Header().Set("Replay-Nonce", "test-nonce") 830 return 831 } 832 if r.Method != "POST" { 833 t.Errorf("r.Method = %q; want POST", r.Method) 834 } 835 836 var j struct { 837 Resource string `json:"resource"` 838 CSR string `json:"csr"` 839 NotBefore string `json:"notBefore,omitempty"` 840 NotAfter string `json:"notAfter,omitempty"` 841 } 842 decodeJWSRequest(t, &j, r.Body) 843 844 // Test request 845 if j.Resource != "new-cert" { 846 t.Errorf(`resource = %q; want "new-cert"`, j.Resource) 847 } 848 if j.NotBefore != notBefore.Format(time.RFC3339) { 849 t.Errorf(`notBefore = %q; wanted %q`, j.NotBefore, notBefore.Format(time.RFC3339)) 850 } 851 if j.NotAfter != notAfter.Format(time.RFC3339) { 852 t.Errorf(`notAfter = %q; wanted %q`, j.NotAfter, notAfter.Format(time.RFC3339)) 853 } 854 855 // Respond to request 856 template := x509.Certificate{ 857 SerialNumber: big.NewInt(int64(1)), 858 Subject: pkix.Name{ 859 Organization: []string{"goacme"}, 860 }, 861 NotBefore: notBefore, 862 NotAfter: notAfter, 863 864 KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature, 865 ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, 866 BasicConstraintsValid: true, 867 } 868 869 sampleCert, err := x509.CreateCertificate(rand.Reader, &template, &template, &testKeyEC.PublicKey, testKeyEC) 870 if err != nil { 871 t.Fatalf("Error creating certificate: %v", err) 872 } 873 874 w.Header().Set("Location", "https://ca.tld/acme/cert/1") 875 w.WriteHeader(http.StatusCreated) 876 w.Write(sampleCert) 877 })) 878 defer ts.Close() 879 880 csr := x509.CertificateRequest{ 881 Version: 0, 882 Subject: pkix.Name{ 883 CommonName: "example.com", 884 Organization: []string{"goacme"}, 885 }, 886 } 887 csrb, err := x509.CreateCertificateRequest(rand.Reader, &csr, testKeyEC) 888 if err != nil { 889 t.Fatal(err) 890 } 891 892 c := Client{Key: testKeyEC, dir: &Directory{CertURL: ts.URL}} 893 cert, certURL, err := c.CreateCert(context.Background(), csrb, notAfter.Sub(notBefore), false) 894 if err != nil { 895 t.Fatal(err) 896 } 897 if cert == nil { 898 t.Errorf("cert is nil") 899 } 900 if certURL != "https://ca.tld/acme/cert/1" { 901 t.Errorf("certURL = %q; want https://ca.tld/acme/cert/1", certURL) 902 } 903} 904 905func TestFetchCert(t *testing.T) { 906 var count byte 907 var ts *httptest.Server 908 ts = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 909 count++ 910 if count < 3 { 911 up := fmt.Sprintf("<%s>;rel=up", ts.URL) 912 w.Header().Set("Link", up) 913 } 914 w.Write([]byte{count}) 915 })) 916 defer ts.Close() 917 cl := newTestClient() 918 res, err := cl.FetchCert(context.Background(), ts.URL, true) 919 if err != nil { 920 t.Fatalf("FetchCert: %v", err) 921 } 922 cert := [][]byte{{1}, {2}, {3}} 923 if !reflect.DeepEqual(res, cert) { 924 t.Errorf("res = %v; want %v", res, cert) 925 } 926} 927 928func TestFetchCertRetry(t *testing.T) { 929 var count int 930 ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 931 if count < 1 { 932 w.Header().Set("Retry-After", "0") 933 w.WriteHeader(http.StatusTooManyRequests) 934 count++ 935 return 936 } 937 w.Write([]byte{1}) 938 })) 939 defer ts.Close() 940 cl := newTestClient() 941 res, err := cl.FetchCert(context.Background(), ts.URL, false) 942 if err != nil { 943 t.Fatalf("FetchCert: %v", err) 944 } 945 cert := [][]byte{{1}} 946 if !reflect.DeepEqual(res, cert) { 947 t.Errorf("res = %v; want %v", res, cert) 948 } 949} 950 951func TestFetchCertCancel(t *testing.T) { 952 ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 953 w.Header().Set("Retry-After", "0") 954 w.WriteHeader(http.StatusBadRequest) 955 })) 956 defer ts.Close() 957 ctx, cancel := context.WithCancel(context.Background()) 958 done := make(chan struct{}) 959 var err error 960 go func() { 961 cl := newTestClient() 962 _, err = cl.FetchCert(ctx, ts.URL, false) 963 close(done) 964 }() 965 cancel() 966 <-done 967 if err != context.Canceled { 968 t.Errorf("err = %v; want %v", err, context.Canceled) 969 } 970} 971 972func TestFetchCertDepth(t *testing.T) { 973 var count byte 974 var ts *httptest.Server 975 ts = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 976 count++ 977 if count > maxChainLen+1 { 978 t.Errorf("count = %d; want at most %d", count, maxChainLen+1) 979 w.WriteHeader(http.StatusInternalServerError) 980 } 981 w.Header().Set("Link", fmt.Sprintf("<%s>;rel=up", ts.URL)) 982 w.Write([]byte{count}) 983 })) 984 defer ts.Close() 985 cl := newTestClient() 986 _, err := cl.FetchCert(context.Background(), ts.URL, true) 987 if err == nil { 988 t.Errorf("err is nil") 989 } 990} 991 992func TestFetchCertBreadth(t *testing.T) { 993 var ts *httptest.Server 994 ts = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 995 for i := 0; i < maxChainLen+1; i++ { 996 w.Header().Add("Link", fmt.Sprintf("<%s>;rel=up", ts.URL)) 997 } 998 w.Write([]byte{1}) 999 })) 1000 defer ts.Close() 1001 cl := newTestClient() 1002 _, err := cl.FetchCert(context.Background(), ts.URL, true) 1003 if err == nil { 1004 t.Errorf("err is nil") 1005 } 1006} 1007 1008func TestFetchCertSize(t *testing.T) { 1009 ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 1010 b := bytes.Repeat([]byte{1}, maxCertSize+1) 1011 w.Write(b) 1012 })) 1013 defer ts.Close() 1014 cl := newTestClient() 1015 _, err := cl.FetchCert(context.Background(), ts.URL, false) 1016 if err == nil { 1017 t.Errorf("err is nil") 1018 } 1019} 1020 1021func TestRevokeCert(t *testing.T) { 1022 ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 1023 if r.Method == "HEAD" { 1024 w.Header().Set("Replay-Nonce", "nonce") 1025 return 1026 } 1027 1028 var req struct { 1029 Resource string 1030 Certificate string 1031 Reason int 1032 } 1033 decodeJWSRequest(t, &req, r.Body) 1034 if req.Resource != "revoke-cert" { 1035 t.Errorf("req.Resource = %q; want revoke-cert", req.Resource) 1036 } 1037 if req.Reason != 1 { 1038 t.Errorf("req.Reason = %d; want 1", req.Reason) 1039 } 1040 // echo -n cert | base64 | tr -d '=' | tr '/+' '_-' 1041 cert := "Y2VydA" 1042 if req.Certificate != cert { 1043 t.Errorf("req.Certificate = %q; want %q", req.Certificate, cert) 1044 } 1045 })) 1046 defer ts.Close() 1047 client := &Client{ 1048 Key: testKeyEC, 1049 dir: &Directory{RevokeURL: ts.URL}, 1050 } 1051 ctx := context.Background() 1052 if err := client.RevokeCert(ctx, nil, []byte("cert"), CRLReasonKeyCompromise); err != nil { 1053 t.Fatal(err) 1054 } 1055} 1056 1057func TestNonce_add(t *testing.T) { 1058 var c Client 1059 c.addNonce(http.Header{"Replay-Nonce": {"nonce"}}) 1060 c.addNonce(http.Header{"Replay-Nonce": {}}) 1061 c.addNonce(http.Header{"Replay-Nonce": {"nonce"}}) 1062 1063 nonces := map[string]struct{}{"nonce": {}} 1064 if !reflect.DeepEqual(c.nonces, nonces) { 1065 t.Errorf("c.nonces = %q; want %q", c.nonces, nonces) 1066 } 1067} 1068 1069func TestNonce_addMax(t *testing.T) { 1070 c := &Client{nonces: make(map[string]struct{})} 1071 for i := 0; i < maxNonces; i++ { 1072 c.nonces[fmt.Sprintf("%d", i)] = struct{}{} 1073 } 1074 c.addNonce(http.Header{"Replay-Nonce": {"nonce"}}) 1075 if n := len(c.nonces); n != maxNonces { 1076 t.Errorf("len(c.nonces) = %d; want %d", n, maxNonces) 1077 } 1078} 1079 1080func TestNonce_fetch(t *testing.T) { 1081 tests := []struct { 1082 code int 1083 nonce string 1084 }{ 1085 {http.StatusOK, "nonce1"}, 1086 {http.StatusBadRequest, "nonce2"}, 1087 {http.StatusOK, ""}, 1088 } 1089 var i int 1090 ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 1091 if r.Method != "HEAD" { 1092 t.Errorf("%d: r.Method = %q; want HEAD", i, r.Method) 1093 } 1094 w.Header().Set("Replay-Nonce", tests[i].nonce) 1095 w.WriteHeader(tests[i].code) 1096 })) 1097 defer ts.Close() 1098 for ; i < len(tests); i++ { 1099 test := tests[i] 1100 c := newTestClient() 1101 n, err := c.fetchNonce(context.Background(), ts.URL) 1102 if n != test.nonce { 1103 t.Errorf("%d: n=%q; want %q", i, n, test.nonce) 1104 } 1105 switch { 1106 case err == nil && test.nonce == "": 1107 t.Errorf("%d: n=%q, err=%v; want non-nil error", i, n, err) 1108 case err != nil && test.nonce != "": 1109 t.Errorf("%d: n=%q, err=%v; want %q", i, n, err, test.nonce) 1110 } 1111 } 1112} 1113 1114func TestNonce_fetchError(t *testing.T) { 1115 ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 1116 w.WriteHeader(http.StatusTooManyRequests) 1117 })) 1118 defer ts.Close() 1119 c := newTestClient() 1120 _, err := c.fetchNonce(context.Background(), ts.URL) 1121 e, ok := err.(*Error) 1122 if !ok { 1123 t.Fatalf("err is %T; want *Error", err) 1124 } 1125 if e.StatusCode != http.StatusTooManyRequests { 1126 t.Errorf("e.StatusCode = %d; want %d", e.StatusCode, http.StatusTooManyRequests) 1127 } 1128} 1129 1130func TestNonce_popWhenEmpty(t *testing.T) { 1131 ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 1132 if r.Method != "HEAD" { 1133 t.Errorf("r.Method = %q; want HEAD", r.Method) 1134 } 1135 switch r.URL.Path { 1136 case "/dir-with-nonce": 1137 w.Header().Set("Replay-Nonce", "dirnonce") 1138 case "/new-nonce": 1139 w.Header().Set("Replay-Nonce", "newnonce") 1140 case "/dir-no-nonce", "/empty": 1141 // No nonce in the header. 1142 default: 1143 t.Errorf("Unknown URL: %s", r.URL) 1144 } 1145 })) 1146 defer ts.Close() 1147 ctx := context.Background() 1148 1149 tt := []struct { 1150 dirURL, popURL, nonce string 1151 wantOK bool 1152 }{ 1153 {ts.URL + "/dir-with-nonce", ts.URL + "/new-nonce", "dirnonce", true}, 1154 {ts.URL + "/dir-no-nonce", ts.URL + "/new-nonce", "newnonce", true}, 1155 {ts.URL + "/dir-no-nonce", ts.URL + "/empty", "", false}, 1156 } 1157 for _, test := range tt { 1158 t.Run(fmt.Sprintf("nonce:%s wantOK:%v", test.nonce, test.wantOK), func(t *testing.T) { 1159 c := Client{DirectoryURL: test.dirURL} 1160 v, err := c.popNonce(ctx, test.popURL) 1161 if !test.wantOK { 1162 if err == nil { 1163 t.Fatalf("c.popNonce(%q) returned nil error", test.popURL) 1164 } 1165 return 1166 } 1167 if err != nil { 1168 t.Fatalf("c.popNonce(%q): %v", test.popURL, err) 1169 } 1170 if v != test.nonce { 1171 t.Errorf("c.popNonce(%q) = %q; want %q", test.popURL, v, test.nonce) 1172 } 1173 }) 1174 } 1175} 1176 1177func TestNonce_postJWS(t *testing.T) { 1178 var count int 1179 seen := make(map[string]bool) 1180 ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 1181 count++ 1182 w.Header().Set("Replay-Nonce", fmt.Sprintf("nonce%d", count)) 1183 if r.Method == "HEAD" { 1184 // We expect the client do a HEAD request 1185 // but only to fetch the first nonce. 1186 return 1187 } 1188 // Make client.Authorize happy; we're not testing its result. 1189 defer func() { 1190 w.WriteHeader(http.StatusCreated) 1191 w.Write([]byte(`{"status":"valid"}`)) 1192 }() 1193 1194 head, err := decodeJWSHead(r.Body) 1195 if err != nil { 1196 t.Errorf("decodeJWSHead: %v", err) 1197 return 1198 } 1199 if head.Nonce == "" { 1200 t.Error("head.Nonce is empty") 1201 return 1202 } 1203 if seen[head.Nonce] { 1204 t.Errorf("nonce is already used: %q", head.Nonce) 1205 } 1206 seen[head.Nonce] = true 1207 })) 1208 defer ts.Close() 1209 1210 client := Client{ 1211 Key: testKey, 1212 DirectoryURL: ts.URL, // nonces are fetched from here first 1213 dir: &Directory{AuthzURL: ts.URL}, 1214 } 1215 if _, err := client.Authorize(context.Background(), "example.com"); err != nil { 1216 t.Errorf("client.Authorize 1: %v", err) 1217 } 1218 // The second call should not generate another extra HEAD request. 1219 if _, err := client.Authorize(context.Background(), "example.com"); err != nil { 1220 t.Errorf("client.Authorize 2: %v", err) 1221 } 1222 1223 if count != 3 { 1224 t.Errorf("total requests count: %d; want 3", count) 1225 } 1226 if n := len(client.nonces); n != 1 { 1227 t.Errorf("len(client.nonces) = %d; want 1", n) 1228 } 1229 for k := range seen { 1230 if _, exist := client.nonces[k]; exist { 1231 t.Errorf("used nonce %q in client.nonces", k) 1232 } 1233 } 1234} 1235 1236func TestLinkHeader(t *testing.T) { 1237 h := http.Header{"Link": { 1238 `<https://example.com/acme/new-authz>;rel="next"`, 1239 `<https://example.com/acme/recover-reg>; rel=recover`, 1240 `<https://example.com/acme/terms>; foo=bar; rel="terms-of-service"`, 1241 `<dup>;rel="next"`, 1242 }} 1243 tests := []struct { 1244 rel string 1245 out []string 1246 }{ 1247 {"next", []string{"https://example.com/acme/new-authz", "dup"}}, 1248 {"recover", []string{"https://example.com/acme/recover-reg"}}, 1249 {"terms-of-service", []string{"https://example.com/acme/terms"}}, 1250 {"empty", nil}, 1251 } 1252 for i, test := range tests { 1253 if v := linkHeader(h, test.rel); !reflect.DeepEqual(v, test.out) { 1254 t.Errorf("%d: linkHeader(%q): %v; want %v", i, test.rel, v, test.out) 1255 } 1256 } 1257} 1258 1259func TestTLSSNI01ChallengeCert(t *testing.T) { 1260 const ( 1261 token = "evaGxfADs6pSRb2LAv9IZf17Dt3juxGJ-PCt92wr-oA" 1262 // echo -n <token.testKeyECThumbprint> | shasum -a 256 1263 san = "dbbd5eefe7b4d06eb9d1d9f5acb4c7cd.a27d320e4b30332f0b6cb441734ad7b0.acme.invalid" 1264 ) 1265 1266 tlscert, name, err := newTestClient().TLSSNI01ChallengeCert(token) 1267 if err != nil { 1268 t.Fatal(err) 1269 } 1270 1271 if n := len(tlscert.Certificate); n != 1 { 1272 t.Fatalf("len(tlscert.Certificate) = %d; want 1", n) 1273 } 1274 cert, err := x509.ParseCertificate(tlscert.Certificate[0]) 1275 if err != nil { 1276 t.Fatal(err) 1277 } 1278 if len(cert.DNSNames) != 1 || cert.DNSNames[0] != san { 1279 t.Fatalf("cert.DNSNames = %v; want %q", cert.DNSNames, san) 1280 } 1281 if cert.DNSNames[0] != name { 1282 t.Errorf("cert.DNSNames[0] != name: %q vs %q", cert.DNSNames[0], name) 1283 } 1284 if cn := cert.Subject.CommonName; cn != san { 1285 t.Errorf("cert.Subject.CommonName = %q; want %q", cn, san) 1286 } 1287} 1288 1289func TestTLSSNI02ChallengeCert(t *testing.T) { 1290 const ( 1291 token = "evaGxfADs6pSRb2LAv9IZf17Dt3juxGJ-PCt92wr-oA" 1292 // echo -n evaGxfADs6pSRb2LAv9IZf17Dt3juxGJ-PCt92wr-oA | shasum -a 256 1293 sanA = "7ea0aaa69214e71e02cebb18bb867736.09b730209baabf60e43d4999979ff139.token.acme.invalid" 1294 // echo -n <token.testKeyECThumbprint> | shasum -a 256 1295 sanB = "dbbd5eefe7b4d06eb9d1d9f5acb4c7cd.a27d320e4b30332f0b6cb441734ad7b0.ka.acme.invalid" 1296 ) 1297 1298 tlscert, name, err := newTestClient().TLSSNI02ChallengeCert(token) 1299 if err != nil { 1300 t.Fatal(err) 1301 } 1302 1303 if n := len(tlscert.Certificate); n != 1 { 1304 t.Fatalf("len(tlscert.Certificate) = %d; want 1", n) 1305 } 1306 cert, err := x509.ParseCertificate(tlscert.Certificate[0]) 1307 if err != nil { 1308 t.Fatal(err) 1309 } 1310 names := []string{sanA, sanB} 1311 if !reflect.DeepEqual(cert.DNSNames, names) { 1312 t.Fatalf("cert.DNSNames = %v;\nwant %v", cert.DNSNames, names) 1313 } 1314 sort.Strings(cert.DNSNames) 1315 i := sort.SearchStrings(cert.DNSNames, name) 1316 if i >= len(cert.DNSNames) || cert.DNSNames[i] != name { 1317 t.Errorf("%v doesn't have %q", cert.DNSNames, name) 1318 } 1319 if cn := cert.Subject.CommonName; cn != sanA { 1320 t.Errorf("CommonName = %q; want %q", cn, sanA) 1321 } 1322} 1323 1324func TestTLSALPN01ChallengeCert(t *testing.T) { 1325 const ( 1326 token = "evaGxfADs6pSRb2LAv9IZf17Dt3juxGJ-PCt92wr-oA" 1327 keyAuth = "evaGxfADs6pSRb2LAv9IZf17Dt3juxGJ-PCt92wr-oA." + testKeyECThumbprint 1328 // echo -n <token.testKeyECThumbprint> | shasum -a 256 1329 h = "0420dbbd5eefe7b4d06eb9d1d9f5acb4c7cda27d320e4b30332f0b6cb441734ad7b0" 1330 domain = "example.com" 1331 ) 1332 1333 extValue, err := hex.DecodeString(h) 1334 if err != nil { 1335 t.Fatal(err) 1336 } 1337 1338 tlscert, err := newTestClient().TLSALPN01ChallengeCert(token, domain) 1339 if err != nil { 1340 t.Fatal(err) 1341 } 1342 1343 if n := len(tlscert.Certificate); n != 1 { 1344 t.Fatalf("len(tlscert.Certificate) = %d; want 1", n) 1345 } 1346 cert, err := x509.ParseCertificate(tlscert.Certificate[0]) 1347 if err != nil { 1348 t.Fatal(err) 1349 } 1350 names := []string{domain} 1351 if !reflect.DeepEqual(cert.DNSNames, names) { 1352 t.Fatalf("cert.DNSNames = %v;\nwant %v", cert.DNSNames, names) 1353 } 1354 if cn := cert.Subject.CommonName; cn != domain { 1355 t.Errorf("CommonName = %q; want %q", cn, domain) 1356 } 1357 acmeExts := []pkix.Extension{} 1358 for _, ext := range cert.Extensions { 1359 if idPeACMEIdentifier.Equal(ext.Id) { 1360 acmeExts = append(acmeExts, ext) 1361 } 1362 } 1363 if len(acmeExts) != 1 { 1364 t.Errorf("acmeExts = %v; want exactly one", acmeExts) 1365 } 1366 if !acmeExts[0].Critical { 1367 t.Errorf("acmeExt.Critical = %v; want true", acmeExts[0].Critical) 1368 } 1369 if bytes.Compare(acmeExts[0].Value, extValue) != 0 { 1370 t.Errorf("acmeExt.Value = %v; want %v", acmeExts[0].Value, extValue) 1371 } 1372 1373} 1374 1375func TestTLSChallengeCertOpt(t *testing.T) { 1376 key, err := rsa.GenerateKey(rand.Reader, 512) 1377 if err != nil { 1378 t.Fatal(err) 1379 } 1380 tmpl := &x509.Certificate{ 1381 SerialNumber: big.NewInt(2), 1382 Subject: pkix.Name{Organization: []string{"Test"}}, 1383 DNSNames: []string{"should-be-overwritten"}, 1384 } 1385 opts := []CertOption{WithKey(key), WithTemplate(tmpl)} 1386 1387 client := newTestClient() 1388 cert1, _, err := client.TLSSNI01ChallengeCert("token", opts...) 1389 if err != nil { 1390 t.Fatal(err) 1391 } 1392 cert2, _, err := client.TLSSNI02ChallengeCert("token", opts...) 1393 if err != nil { 1394 t.Fatal(err) 1395 } 1396 1397 for i, tlscert := range []tls.Certificate{cert1, cert2} { 1398 // verify generated cert private key 1399 tlskey, ok := tlscert.PrivateKey.(*rsa.PrivateKey) 1400 if !ok { 1401 t.Errorf("%d: tlscert.PrivateKey is %T; want *rsa.PrivateKey", i, tlscert.PrivateKey) 1402 continue 1403 } 1404 if tlskey.D.Cmp(key.D) != 0 { 1405 t.Errorf("%d: tlskey.D = %v; want %v", i, tlskey.D, key.D) 1406 } 1407 // verify generated cert public key 1408 x509Cert, err := x509.ParseCertificate(tlscert.Certificate[0]) 1409 if err != nil { 1410 t.Errorf("%d: %v", i, err) 1411 continue 1412 } 1413 tlspub, ok := x509Cert.PublicKey.(*rsa.PublicKey) 1414 if !ok { 1415 t.Errorf("%d: x509Cert.PublicKey is %T; want *rsa.PublicKey", i, x509Cert.PublicKey) 1416 continue 1417 } 1418 if tlspub.N.Cmp(key.N) != 0 { 1419 t.Errorf("%d: tlspub.N = %v; want %v", i, tlspub.N, key.N) 1420 } 1421 // verify template option 1422 sn := big.NewInt(2) 1423 if x509Cert.SerialNumber.Cmp(sn) != 0 { 1424 t.Errorf("%d: SerialNumber = %v; want %v", i, x509Cert.SerialNumber, sn) 1425 } 1426 org := []string{"Test"} 1427 if !reflect.DeepEqual(x509Cert.Subject.Organization, org) { 1428 t.Errorf("%d: Subject.Organization = %+v; want %+v", i, x509Cert.Subject.Organization, org) 1429 } 1430 for _, v := range x509Cert.DNSNames { 1431 if !strings.HasSuffix(v, ".acme.invalid") { 1432 t.Errorf("%d: invalid DNSNames element: %q", i, v) 1433 } 1434 } 1435 } 1436} 1437 1438func TestHTTP01Challenge(t *testing.T) { 1439 const ( 1440 token = "xxx" 1441 // thumbprint is precomputed for testKeyEC in jws_test.go 1442 value = token + "." + testKeyECThumbprint 1443 urlpath = "/.well-known/acme-challenge/" + token 1444 ) 1445 client := newTestClient() 1446 val, err := client.HTTP01ChallengeResponse(token) 1447 if err != nil { 1448 t.Fatal(err) 1449 } 1450 if val != value { 1451 t.Errorf("val = %q; want %q", val, value) 1452 } 1453 if path := client.HTTP01ChallengePath(token); path != urlpath { 1454 t.Errorf("path = %q; want %q", path, urlpath) 1455 } 1456} 1457 1458func TestDNS01ChallengeRecord(t *testing.T) { 1459 // echo -n xxx.<testKeyECThumbprint> | \ 1460 // openssl dgst -binary -sha256 | \ 1461 // base64 | tr -d '=' | tr '/+' '_-' 1462 const value = "8DERMexQ5VcdJ_prpPiA0mVdp7imgbCgjsG4SqqNMIo" 1463 1464 val, err := newTestClient().DNS01ChallengeRecord("xxx") 1465 if err != nil { 1466 t.Fatal(err) 1467 } 1468 if val != value { 1469 t.Errorf("val = %q; want %q", val, value) 1470 } 1471} 1472