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("non-retriable error", func(t *testing.T) { 573 const code = http.StatusBadRequest 574 _, err := runWaitAuthorization(context.Background(), t, func(w http.ResponseWriter, r *http.Request) { 575 w.WriteHeader(code) 576 }) 577 res, ok := err.(*Error) 578 if !ok { 579 t.Fatalf("err is %v (%T); want a non-nil *Error", err, err) 580 } 581 if res.StatusCode != code { 582 t.Errorf("res.StatusCode = %d; want %d", res.StatusCode, code) 583 } 584 }) 585 for _, code := range []int{http.StatusTooManyRequests, http.StatusInternalServerError} { 586 t.Run(fmt.Sprintf("retriable %d error", code), func(t *testing.T) { 587 var count int 588 authz, err := runWaitAuthorization(context.Background(), t, func(w http.ResponseWriter, r *http.Request) { 589 count++ 590 w.Header().Set("Retry-After", "0") 591 if count > 1 { 592 fmt.Fprintf(w, `{"status":"valid"}`) 593 return 594 } 595 w.WriteHeader(code) 596 }) 597 if err != nil { 598 t.Fatalf("non-nil error: %v", err) 599 } 600 if authz == nil { 601 t.Fatal("authz is nil") 602 } 603 }) 604 } 605 t.Run("context cancel", func(t *testing.T) { 606 ctx, cancel := context.WithTimeout(context.Background(), 200*time.Millisecond) 607 defer cancel() 608 _, err := runWaitAuthorization(ctx, t, func(w http.ResponseWriter, r *http.Request) { 609 w.Header().Set("Retry-After", "60") 610 fmt.Fprintf(w, `{"status":"pending"}`) 611 }) 612 if err == nil { 613 t.Error("err is nil") 614 } 615 }) 616} 617func runWaitAuthorization(ctx context.Context, t *testing.T, h http.HandlerFunc) (*Authorization, error) { 618 t.Helper() 619 ts := httptest.NewServer(h) 620 defer ts.Close() 621 type res struct { 622 authz *Authorization 623 err error 624 } 625 ch := make(chan res, 1) 626 go func() { 627 var client = Client{DirectoryURL: ts.URL} 628 a, err := client.WaitAuthorization(ctx, ts.URL) 629 ch <- res{a, err} 630 }() 631 select { 632 case <-time.After(3 * time.Second): 633 t.Fatal("WaitAuthorization took too long to return") 634 case v := <-ch: 635 return v.authz, v.err 636 } 637 panic("runWaitAuthorization: out of select") 638} 639 640func TestRevokeAuthorization(t *testing.T) { 641 ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 642 if r.Method == "HEAD" { 643 w.Header().Set("Replay-Nonce", "nonce") 644 return 645 } 646 switch r.URL.Path { 647 case "/1": 648 var req struct { 649 Resource string 650 Status string 651 Delete bool 652 } 653 decodeJWSRequest(t, &req, r.Body) 654 if req.Resource != "authz" { 655 t.Errorf("req.Resource = %q; want authz", req.Resource) 656 } 657 if req.Status != "deactivated" { 658 t.Errorf("req.Status = %q; want deactivated", req.Status) 659 } 660 if !req.Delete { 661 t.Errorf("req.Delete is false") 662 } 663 case "/2": 664 w.WriteHeader(http.StatusBadRequest) 665 } 666 })) 667 defer ts.Close() 668 client := &Client{ 669 Key: testKey, 670 DirectoryURL: ts.URL, // don't dial outside of localhost 671 dir: &Directory{}, // don't do discovery 672 } 673 ctx := context.Background() 674 if err := client.RevokeAuthorization(ctx, ts.URL+"/1"); err != nil { 675 t.Errorf("err = %v", err) 676 } 677 if client.RevokeAuthorization(ctx, ts.URL+"/2") == nil { 678 t.Error("nil error") 679 } 680} 681 682func TestPollChallenge(t *testing.T) { 683 ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 684 if r.Method != "GET" { 685 t.Errorf("r.Method = %q; want GET", r.Method) 686 } 687 688 w.WriteHeader(http.StatusOK) 689 fmt.Fprintf(w, `{ 690 "type":"http-01", 691 "status":"pending", 692 "uri":"https://ca.tld/acme/challenge/publickey/id1", 693 "token":"token1"}`) 694 })) 695 defer ts.Close() 696 697 cl := Client{Key: testKeyEC, DirectoryURL: ts.URL} 698 chall, err := cl.GetChallenge(context.Background(), ts.URL) 699 if err != nil { 700 t.Fatal(err) 701 } 702 703 if chall.Status != "pending" { 704 t.Errorf("Status = %q; want pending", chall.Status) 705 } 706 if chall.Type != "http-01" { 707 t.Errorf("c.Type = %q; want http-01", chall.Type) 708 } 709 if chall.URI != "https://ca.tld/acme/challenge/publickey/id1" { 710 t.Errorf("c.URI = %q; want https://ca.tld/acme/challenge/publickey/id1", chall.URI) 711 } 712 if chall.Token != "token1" { 713 t.Errorf("c.Token = %q; want token1", chall.Token) 714 } 715} 716 717func TestAcceptChallenge(t *testing.T) { 718 ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 719 if r.Method == "HEAD" { 720 w.Header().Set("Replay-Nonce", "test-nonce") 721 return 722 } 723 if r.Method != "POST" { 724 t.Errorf("r.Method = %q; want POST", r.Method) 725 } 726 727 var j struct { 728 Resource string 729 Type string 730 Auth string `json:"keyAuthorization"` 731 } 732 decodeJWSRequest(t, &j, r.Body) 733 734 // Test request 735 if j.Resource != "challenge" { 736 t.Errorf(`resource = %q; want "challenge"`, j.Resource) 737 } 738 if j.Type != "http-01" { 739 t.Errorf(`type = %q; want "http-01"`, j.Type) 740 } 741 keyAuth := "token1." + testKeyECThumbprint 742 if j.Auth != keyAuth { 743 t.Errorf(`keyAuthorization = %q; want %q`, j.Auth, keyAuth) 744 } 745 746 // Respond to request 747 w.WriteHeader(http.StatusAccepted) 748 fmt.Fprintf(w, `{ 749 "type":"http-01", 750 "status":"pending", 751 "uri":"https://ca.tld/acme/challenge/publickey/id1", 752 "token":"token1", 753 "keyAuthorization":%q 754 }`, keyAuth) 755 })) 756 defer ts.Close() 757 758 cl := Client{ 759 Key: testKeyEC, 760 DirectoryURL: ts.URL, // don't dial outside of localhost 761 dir: &Directory{}, // don't do discovery 762 } 763 c, err := cl.Accept(context.Background(), &Challenge{ 764 URI: ts.URL, 765 Token: "token1", 766 Type: "http-01", 767 }) 768 if err != nil { 769 t.Fatal(err) 770 } 771 772 if c.Type != "http-01" { 773 t.Errorf("c.Type = %q; want http-01", c.Type) 774 } 775 if c.URI != "https://ca.tld/acme/challenge/publickey/id1" { 776 t.Errorf("c.URI = %q; want https://ca.tld/acme/challenge/publickey/id1", c.URI) 777 } 778 if c.Token != "token1" { 779 t.Errorf("c.Token = %q; want token1", c.Token) 780 } 781} 782 783func TestNewCert(t *testing.T) { 784 notBefore := time.Now() 785 notAfter := notBefore.AddDate(0, 2, 0) 786 timeNow = func() time.Time { return notBefore } 787 788 ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 789 if r.Method == "HEAD" { 790 w.Header().Set("Replay-Nonce", "test-nonce") 791 return 792 } 793 if r.Method != "POST" { 794 t.Errorf("r.Method = %q; want POST", r.Method) 795 } 796 797 var j struct { 798 Resource string `json:"resource"` 799 CSR string `json:"csr"` 800 NotBefore string `json:"notBefore,omitempty"` 801 NotAfter string `json:"notAfter,omitempty"` 802 } 803 decodeJWSRequest(t, &j, r.Body) 804 805 // Test request 806 if j.Resource != "new-cert" { 807 t.Errorf(`resource = %q; want "new-cert"`, j.Resource) 808 } 809 if j.NotBefore != notBefore.Format(time.RFC3339) { 810 t.Errorf(`notBefore = %q; wanted %q`, j.NotBefore, notBefore.Format(time.RFC3339)) 811 } 812 if j.NotAfter != notAfter.Format(time.RFC3339) { 813 t.Errorf(`notAfter = %q; wanted %q`, j.NotAfter, notAfter.Format(time.RFC3339)) 814 } 815 816 // Respond to request 817 template := x509.Certificate{ 818 SerialNumber: big.NewInt(int64(1)), 819 Subject: pkix.Name{ 820 Organization: []string{"goacme"}, 821 }, 822 NotBefore: notBefore, 823 NotAfter: notAfter, 824 825 KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature, 826 ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, 827 BasicConstraintsValid: true, 828 } 829 830 sampleCert, err := x509.CreateCertificate(rand.Reader, &template, &template, &testKeyEC.PublicKey, testKeyEC) 831 if err != nil { 832 t.Fatalf("Error creating certificate: %v", err) 833 } 834 835 w.Header().Set("Location", "https://ca.tld/acme/cert/1") 836 w.WriteHeader(http.StatusCreated) 837 w.Write(sampleCert) 838 })) 839 defer ts.Close() 840 841 csr := x509.CertificateRequest{ 842 Version: 0, 843 Subject: pkix.Name{ 844 CommonName: "example.com", 845 Organization: []string{"goacme"}, 846 }, 847 } 848 csrb, err := x509.CreateCertificateRequest(rand.Reader, &csr, testKeyEC) 849 if err != nil { 850 t.Fatal(err) 851 } 852 853 c := Client{Key: testKeyEC, dir: &Directory{CertURL: ts.URL}} 854 cert, certURL, err := c.CreateCert(context.Background(), csrb, notAfter.Sub(notBefore), false) 855 if err != nil { 856 t.Fatal(err) 857 } 858 if cert == nil { 859 t.Errorf("cert is nil") 860 } 861 if certURL != "https://ca.tld/acme/cert/1" { 862 t.Errorf("certURL = %q; want https://ca.tld/acme/cert/1", certURL) 863 } 864} 865 866func TestFetchCert(t *testing.T) { 867 var count byte 868 var ts *httptest.Server 869 ts = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 870 count++ 871 if count < 3 { 872 up := fmt.Sprintf("<%s>;rel=up", ts.URL) 873 w.Header().Set("Link", up) 874 } 875 w.Write([]byte{count}) 876 })) 877 defer ts.Close() 878 cl := newTestClient() 879 res, err := cl.FetchCert(context.Background(), ts.URL, true) 880 if err != nil { 881 t.Fatalf("FetchCert: %v", err) 882 } 883 cert := [][]byte{{1}, {2}, {3}} 884 if !reflect.DeepEqual(res, cert) { 885 t.Errorf("res = %v; want %v", res, cert) 886 } 887} 888 889func TestFetchCertRetry(t *testing.T) { 890 var count int 891 ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 892 if count < 1 { 893 w.Header().Set("Retry-After", "0") 894 w.WriteHeader(http.StatusTooManyRequests) 895 count++ 896 return 897 } 898 w.Write([]byte{1}) 899 })) 900 defer ts.Close() 901 cl := newTestClient() 902 res, err := cl.FetchCert(context.Background(), ts.URL, false) 903 if err != nil { 904 t.Fatalf("FetchCert: %v", err) 905 } 906 cert := [][]byte{{1}} 907 if !reflect.DeepEqual(res, cert) { 908 t.Errorf("res = %v; want %v", res, cert) 909 } 910} 911 912func TestFetchCertCancel(t *testing.T) { 913 ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 914 w.Header().Set("Retry-After", "0") 915 w.WriteHeader(http.StatusBadRequest) 916 })) 917 defer ts.Close() 918 ctx, cancel := context.WithCancel(context.Background()) 919 done := make(chan struct{}) 920 var err error 921 go func() { 922 cl := newTestClient() 923 _, err = cl.FetchCert(ctx, ts.URL, false) 924 close(done) 925 }() 926 cancel() 927 <-done 928 if err != context.Canceled { 929 t.Errorf("err = %v; want %v", err, context.Canceled) 930 } 931} 932 933func TestFetchCertDepth(t *testing.T) { 934 var count byte 935 var ts *httptest.Server 936 ts = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 937 count++ 938 if count > maxChainLen+1 { 939 t.Errorf("count = %d; want at most %d", count, maxChainLen+1) 940 w.WriteHeader(http.StatusInternalServerError) 941 } 942 w.Header().Set("Link", fmt.Sprintf("<%s>;rel=up", ts.URL)) 943 w.Write([]byte{count}) 944 })) 945 defer ts.Close() 946 cl := newTestClient() 947 _, err := cl.FetchCert(context.Background(), ts.URL, true) 948 if err == nil { 949 t.Errorf("err is nil") 950 } 951} 952 953func TestFetchCertBreadth(t *testing.T) { 954 var ts *httptest.Server 955 ts = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 956 for i := 0; i < maxChainLen+1; i++ { 957 w.Header().Add("Link", fmt.Sprintf("<%s>;rel=up", ts.URL)) 958 } 959 w.Write([]byte{1}) 960 })) 961 defer ts.Close() 962 cl := newTestClient() 963 _, err := cl.FetchCert(context.Background(), ts.URL, true) 964 if err == nil { 965 t.Errorf("err is nil") 966 } 967} 968 969func TestFetchCertSize(t *testing.T) { 970 ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 971 b := bytes.Repeat([]byte{1}, maxCertSize+1) 972 w.Write(b) 973 })) 974 defer ts.Close() 975 cl := newTestClient() 976 _, err := cl.FetchCert(context.Background(), ts.URL, false) 977 if err == nil { 978 t.Errorf("err is nil") 979 } 980} 981 982func TestRevokeCert(t *testing.T) { 983 ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 984 if r.Method == "HEAD" { 985 w.Header().Set("Replay-Nonce", "nonce") 986 return 987 } 988 989 var req struct { 990 Resource string 991 Certificate string 992 Reason int 993 } 994 decodeJWSRequest(t, &req, r.Body) 995 if req.Resource != "revoke-cert" { 996 t.Errorf("req.Resource = %q; want revoke-cert", req.Resource) 997 } 998 if req.Reason != 1 { 999 t.Errorf("req.Reason = %d; want 1", req.Reason) 1000 } 1001 // echo -n cert | base64 | tr -d '=' | tr '/+' '_-' 1002 cert := "Y2VydA" 1003 if req.Certificate != cert { 1004 t.Errorf("req.Certificate = %q; want %q", req.Certificate, cert) 1005 } 1006 })) 1007 defer ts.Close() 1008 client := &Client{ 1009 Key: testKeyEC, 1010 dir: &Directory{RevokeURL: ts.URL}, 1011 } 1012 ctx := context.Background() 1013 if err := client.RevokeCert(ctx, nil, []byte("cert"), CRLReasonKeyCompromise); err != nil { 1014 t.Fatal(err) 1015 } 1016} 1017 1018func TestNonce_add(t *testing.T) { 1019 var c Client 1020 c.addNonce(http.Header{"Replay-Nonce": {"nonce"}}) 1021 c.addNonce(http.Header{"Replay-Nonce": {}}) 1022 c.addNonce(http.Header{"Replay-Nonce": {"nonce"}}) 1023 1024 nonces := map[string]struct{}{"nonce": {}} 1025 if !reflect.DeepEqual(c.nonces, nonces) { 1026 t.Errorf("c.nonces = %q; want %q", c.nonces, nonces) 1027 } 1028} 1029 1030func TestNonce_addMax(t *testing.T) { 1031 c := &Client{nonces: make(map[string]struct{})} 1032 for i := 0; i < maxNonces; i++ { 1033 c.nonces[fmt.Sprintf("%d", i)] = struct{}{} 1034 } 1035 c.addNonce(http.Header{"Replay-Nonce": {"nonce"}}) 1036 if n := len(c.nonces); n != maxNonces { 1037 t.Errorf("len(c.nonces) = %d; want %d", n, maxNonces) 1038 } 1039} 1040 1041func TestNonce_fetch(t *testing.T) { 1042 tests := []struct { 1043 code int 1044 nonce string 1045 }{ 1046 {http.StatusOK, "nonce1"}, 1047 {http.StatusBadRequest, "nonce2"}, 1048 {http.StatusOK, ""}, 1049 } 1050 var i int 1051 ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 1052 if r.Method != "HEAD" { 1053 t.Errorf("%d: r.Method = %q; want HEAD", i, r.Method) 1054 } 1055 w.Header().Set("Replay-Nonce", tests[i].nonce) 1056 w.WriteHeader(tests[i].code) 1057 })) 1058 defer ts.Close() 1059 for ; i < len(tests); i++ { 1060 test := tests[i] 1061 c := newTestClient() 1062 n, err := c.fetchNonce(context.Background(), ts.URL) 1063 if n != test.nonce { 1064 t.Errorf("%d: n=%q; want %q", i, n, test.nonce) 1065 } 1066 switch { 1067 case err == nil && test.nonce == "": 1068 t.Errorf("%d: n=%q, err=%v; want non-nil error", i, n, err) 1069 case err != nil && test.nonce != "": 1070 t.Errorf("%d: n=%q, err=%v; want %q", i, n, err, test.nonce) 1071 } 1072 } 1073} 1074 1075func TestNonce_fetchError(t *testing.T) { 1076 ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 1077 w.WriteHeader(http.StatusTooManyRequests) 1078 })) 1079 defer ts.Close() 1080 c := newTestClient() 1081 _, err := c.fetchNonce(context.Background(), ts.URL) 1082 e, ok := err.(*Error) 1083 if !ok { 1084 t.Fatalf("err is %T; want *Error", err) 1085 } 1086 if e.StatusCode != http.StatusTooManyRequests { 1087 t.Errorf("e.StatusCode = %d; want %d", e.StatusCode, http.StatusTooManyRequests) 1088 } 1089} 1090 1091func TestNonce_popWhenEmpty(t *testing.T) { 1092 ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 1093 if r.Method != "HEAD" { 1094 t.Errorf("r.Method = %q; want HEAD", r.Method) 1095 } 1096 switch r.URL.Path { 1097 case "/dir-with-nonce": 1098 w.Header().Set("Replay-Nonce", "dirnonce") 1099 case "/new-nonce": 1100 w.Header().Set("Replay-Nonce", "newnonce") 1101 case "/dir-no-nonce", "/empty": 1102 // No nonce in the header. 1103 default: 1104 t.Errorf("Unknown URL: %s", r.URL) 1105 } 1106 })) 1107 defer ts.Close() 1108 ctx := context.Background() 1109 1110 tt := []struct { 1111 dirURL, popURL, nonce string 1112 wantOK bool 1113 }{ 1114 {ts.URL + "/dir-with-nonce", ts.URL + "/new-nonce", "dirnonce", true}, 1115 {ts.URL + "/dir-no-nonce", ts.URL + "/new-nonce", "newnonce", true}, 1116 {ts.URL + "/dir-no-nonce", ts.URL + "/empty", "", false}, 1117 } 1118 for _, test := range tt { 1119 t.Run(fmt.Sprintf("nonce:%s wantOK:%v", test.nonce, test.wantOK), func(t *testing.T) { 1120 c := Client{DirectoryURL: test.dirURL} 1121 v, err := c.popNonce(ctx, test.popURL) 1122 if !test.wantOK { 1123 if err == nil { 1124 t.Fatalf("c.popNonce(%q) returned nil error", test.popURL) 1125 } 1126 return 1127 } 1128 if err != nil { 1129 t.Fatalf("c.popNonce(%q): %v", test.popURL, err) 1130 } 1131 if v != test.nonce { 1132 t.Errorf("c.popNonce(%q) = %q; want %q", test.popURL, v, test.nonce) 1133 } 1134 }) 1135 } 1136} 1137 1138func TestNonce_postJWS(t *testing.T) { 1139 var count int 1140 seen := make(map[string]bool) 1141 ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 1142 count++ 1143 w.Header().Set("Replay-Nonce", fmt.Sprintf("nonce%d", count)) 1144 if r.Method == "HEAD" { 1145 // We expect the client do a HEAD request 1146 // but only to fetch the first nonce. 1147 return 1148 } 1149 // Make client.Authorize happy; we're not testing its result. 1150 defer func() { 1151 w.WriteHeader(http.StatusCreated) 1152 w.Write([]byte(`{"status":"valid"}`)) 1153 }() 1154 1155 head, err := decodeJWSHead(r.Body) 1156 if err != nil { 1157 t.Errorf("decodeJWSHead: %v", err) 1158 return 1159 } 1160 if head.Nonce == "" { 1161 t.Error("head.Nonce is empty") 1162 return 1163 } 1164 if seen[head.Nonce] { 1165 t.Errorf("nonce is already used: %q", head.Nonce) 1166 } 1167 seen[head.Nonce] = true 1168 })) 1169 defer ts.Close() 1170 1171 client := Client{ 1172 Key: testKey, 1173 DirectoryURL: ts.URL, // nonces are fetched from here first 1174 dir: &Directory{AuthzURL: ts.URL}, 1175 } 1176 if _, err := client.Authorize(context.Background(), "example.com"); err != nil { 1177 t.Errorf("client.Authorize 1: %v", err) 1178 } 1179 // The second call should not generate another extra HEAD request. 1180 if _, err := client.Authorize(context.Background(), "example.com"); err != nil { 1181 t.Errorf("client.Authorize 2: %v", err) 1182 } 1183 1184 if count != 3 { 1185 t.Errorf("total requests count: %d; want 3", count) 1186 } 1187 if n := len(client.nonces); n != 1 { 1188 t.Errorf("len(client.nonces) = %d; want 1", n) 1189 } 1190 for k := range seen { 1191 if _, exist := client.nonces[k]; exist { 1192 t.Errorf("used nonce %q in client.nonces", k) 1193 } 1194 } 1195} 1196 1197func TestLinkHeader(t *testing.T) { 1198 h := http.Header{"Link": { 1199 `<https://example.com/acme/new-authz>;rel="next"`, 1200 `<https://example.com/acme/recover-reg>; rel=recover`, 1201 `<https://example.com/acme/terms>; foo=bar; rel="terms-of-service"`, 1202 `<dup>;rel="next"`, 1203 }} 1204 tests := []struct { 1205 rel string 1206 out []string 1207 }{ 1208 {"next", []string{"https://example.com/acme/new-authz", "dup"}}, 1209 {"recover", []string{"https://example.com/acme/recover-reg"}}, 1210 {"terms-of-service", []string{"https://example.com/acme/terms"}}, 1211 {"empty", nil}, 1212 } 1213 for i, test := range tests { 1214 if v := linkHeader(h, test.rel); !reflect.DeepEqual(v, test.out) { 1215 t.Errorf("%d: linkHeader(%q): %v; want %v", i, test.rel, v, test.out) 1216 } 1217 } 1218} 1219 1220func TestTLSSNI01ChallengeCert(t *testing.T) { 1221 const ( 1222 token = "evaGxfADs6pSRb2LAv9IZf17Dt3juxGJ-PCt92wr-oA" 1223 // echo -n <token.testKeyECThumbprint> | shasum -a 256 1224 san = "dbbd5eefe7b4d06eb9d1d9f5acb4c7cd.a27d320e4b30332f0b6cb441734ad7b0.acme.invalid" 1225 ) 1226 1227 tlscert, name, err := newTestClient().TLSSNI01ChallengeCert(token) 1228 if err != nil { 1229 t.Fatal(err) 1230 } 1231 1232 if n := len(tlscert.Certificate); n != 1 { 1233 t.Fatalf("len(tlscert.Certificate) = %d; want 1", n) 1234 } 1235 cert, err := x509.ParseCertificate(tlscert.Certificate[0]) 1236 if err != nil { 1237 t.Fatal(err) 1238 } 1239 if len(cert.DNSNames) != 1 || cert.DNSNames[0] != san { 1240 t.Fatalf("cert.DNSNames = %v; want %q", cert.DNSNames, san) 1241 } 1242 if cert.DNSNames[0] != name { 1243 t.Errorf("cert.DNSNames[0] != name: %q vs %q", cert.DNSNames[0], name) 1244 } 1245 if cn := cert.Subject.CommonName; cn != san { 1246 t.Errorf("cert.Subject.CommonName = %q; want %q", cn, san) 1247 } 1248} 1249 1250func TestTLSSNI02ChallengeCert(t *testing.T) { 1251 const ( 1252 token = "evaGxfADs6pSRb2LAv9IZf17Dt3juxGJ-PCt92wr-oA" 1253 // echo -n evaGxfADs6pSRb2LAv9IZf17Dt3juxGJ-PCt92wr-oA | shasum -a 256 1254 sanA = "7ea0aaa69214e71e02cebb18bb867736.09b730209baabf60e43d4999979ff139.token.acme.invalid" 1255 // echo -n <token.testKeyECThumbprint> | shasum -a 256 1256 sanB = "dbbd5eefe7b4d06eb9d1d9f5acb4c7cd.a27d320e4b30332f0b6cb441734ad7b0.ka.acme.invalid" 1257 ) 1258 1259 tlscert, name, err := newTestClient().TLSSNI02ChallengeCert(token) 1260 if err != nil { 1261 t.Fatal(err) 1262 } 1263 1264 if n := len(tlscert.Certificate); n != 1 { 1265 t.Fatalf("len(tlscert.Certificate) = %d; want 1", n) 1266 } 1267 cert, err := x509.ParseCertificate(tlscert.Certificate[0]) 1268 if err != nil { 1269 t.Fatal(err) 1270 } 1271 names := []string{sanA, sanB} 1272 if !reflect.DeepEqual(cert.DNSNames, names) { 1273 t.Fatalf("cert.DNSNames = %v;\nwant %v", cert.DNSNames, names) 1274 } 1275 sort.Strings(cert.DNSNames) 1276 i := sort.SearchStrings(cert.DNSNames, name) 1277 if i >= len(cert.DNSNames) || cert.DNSNames[i] != name { 1278 t.Errorf("%v doesn't have %q", cert.DNSNames, name) 1279 } 1280 if cn := cert.Subject.CommonName; cn != sanA { 1281 t.Errorf("CommonName = %q; want %q", cn, sanA) 1282 } 1283} 1284 1285func TestTLSALPN01ChallengeCert(t *testing.T) { 1286 const ( 1287 token = "evaGxfADs6pSRb2LAv9IZf17Dt3juxGJ-PCt92wr-oA" 1288 keyAuth = "evaGxfADs6pSRb2LAv9IZf17Dt3juxGJ-PCt92wr-oA." + testKeyECThumbprint 1289 // echo -n <token.testKeyECThumbprint> | shasum -a 256 1290 h = "0420dbbd5eefe7b4d06eb9d1d9f5acb4c7cda27d320e4b30332f0b6cb441734ad7b0" 1291 domain = "example.com" 1292 ) 1293 1294 extValue, err := hex.DecodeString(h) 1295 if err != nil { 1296 t.Fatal(err) 1297 } 1298 1299 tlscert, err := newTestClient().TLSALPN01ChallengeCert(token, domain) 1300 if err != nil { 1301 t.Fatal(err) 1302 } 1303 1304 if n := len(tlscert.Certificate); n != 1 { 1305 t.Fatalf("len(tlscert.Certificate) = %d; want 1", n) 1306 } 1307 cert, err := x509.ParseCertificate(tlscert.Certificate[0]) 1308 if err != nil { 1309 t.Fatal(err) 1310 } 1311 names := []string{domain} 1312 if !reflect.DeepEqual(cert.DNSNames, names) { 1313 t.Fatalf("cert.DNSNames = %v;\nwant %v", cert.DNSNames, names) 1314 } 1315 if cn := cert.Subject.CommonName; cn != domain { 1316 t.Errorf("CommonName = %q; want %q", cn, domain) 1317 } 1318 acmeExts := []pkix.Extension{} 1319 for _, ext := range cert.Extensions { 1320 if idPeACMEIdentifierV1.Equal(ext.Id) { 1321 acmeExts = append(acmeExts, ext) 1322 } 1323 } 1324 if len(acmeExts) != 1 { 1325 t.Errorf("acmeExts = %v; want exactly one", acmeExts) 1326 } 1327 if !acmeExts[0].Critical { 1328 t.Errorf("acmeExt.Critical = %v; want true", acmeExts[0].Critical) 1329 } 1330 if bytes.Compare(acmeExts[0].Value, extValue) != 0 { 1331 t.Errorf("acmeExt.Value = %v; want %v", acmeExts[0].Value, extValue) 1332 } 1333 1334} 1335 1336func TestTLSChallengeCertOpt(t *testing.T) { 1337 key, err := rsa.GenerateKey(rand.Reader, 512) 1338 if err != nil { 1339 t.Fatal(err) 1340 } 1341 tmpl := &x509.Certificate{ 1342 SerialNumber: big.NewInt(2), 1343 Subject: pkix.Name{Organization: []string{"Test"}}, 1344 DNSNames: []string{"should-be-overwritten"}, 1345 } 1346 opts := []CertOption{WithKey(key), WithTemplate(tmpl)} 1347 1348 client := newTestClient() 1349 cert1, _, err := client.TLSSNI01ChallengeCert("token", opts...) 1350 if err != nil { 1351 t.Fatal(err) 1352 } 1353 cert2, _, err := client.TLSSNI02ChallengeCert("token", opts...) 1354 if err != nil { 1355 t.Fatal(err) 1356 } 1357 1358 for i, tlscert := range []tls.Certificate{cert1, cert2} { 1359 // verify generated cert private key 1360 tlskey, ok := tlscert.PrivateKey.(*rsa.PrivateKey) 1361 if !ok { 1362 t.Errorf("%d: tlscert.PrivateKey is %T; want *rsa.PrivateKey", i, tlscert.PrivateKey) 1363 continue 1364 } 1365 if tlskey.D.Cmp(key.D) != 0 { 1366 t.Errorf("%d: tlskey.D = %v; want %v", i, tlskey.D, key.D) 1367 } 1368 // verify generated cert public key 1369 x509Cert, err := x509.ParseCertificate(tlscert.Certificate[0]) 1370 if err != nil { 1371 t.Errorf("%d: %v", i, err) 1372 continue 1373 } 1374 tlspub, ok := x509Cert.PublicKey.(*rsa.PublicKey) 1375 if !ok { 1376 t.Errorf("%d: x509Cert.PublicKey is %T; want *rsa.PublicKey", i, x509Cert.PublicKey) 1377 continue 1378 } 1379 if tlspub.N.Cmp(key.N) != 0 { 1380 t.Errorf("%d: tlspub.N = %v; want %v", i, tlspub.N, key.N) 1381 } 1382 // verify template option 1383 sn := big.NewInt(2) 1384 if x509Cert.SerialNumber.Cmp(sn) != 0 { 1385 t.Errorf("%d: SerialNumber = %v; want %v", i, x509Cert.SerialNumber, sn) 1386 } 1387 org := []string{"Test"} 1388 if !reflect.DeepEqual(x509Cert.Subject.Organization, org) { 1389 t.Errorf("%d: Subject.Organization = %+v; want %+v", i, x509Cert.Subject.Organization, org) 1390 } 1391 for _, v := range x509Cert.DNSNames { 1392 if !strings.HasSuffix(v, ".acme.invalid") { 1393 t.Errorf("%d: invalid DNSNames element: %q", i, v) 1394 } 1395 } 1396 } 1397} 1398 1399func TestHTTP01Challenge(t *testing.T) { 1400 const ( 1401 token = "xxx" 1402 // thumbprint is precomputed for testKeyEC in jws_test.go 1403 value = token + "." + testKeyECThumbprint 1404 urlpath = "/.well-known/acme-challenge/" + token 1405 ) 1406 client := newTestClient() 1407 val, err := client.HTTP01ChallengeResponse(token) 1408 if err != nil { 1409 t.Fatal(err) 1410 } 1411 if val != value { 1412 t.Errorf("val = %q; want %q", val, value) 1413 } 1414 if path := client.HTTP01ChallengePath(token); path != urlpath { 1415 t.Errorf("path = %q; want %q", path, urlpath) 1416 } 1417} 1418 1419func TestDNS01ChallengeRecord(t *testing.T) { 1420 // echo -n xxx.<testKeyECThumbprint> | \ 1421 // openssl dgst -binary -sha256 | \ 1422 // base64 | tr -d '=' | tr '/+' '_-' 1423 const value = "8DERMexQ5VcdJ_prpPiA0mVdp7imgbCgjsG4SqqNMIo" 1424 1425 val, err := newTestClient().DNS01ChallengeRecord("xxx") 1426 if err != nil { 1427 t.Fatal(err) 1428 } 1429 if val != value { 1430 t.Errorf("val = %q; want %q", val, value) 1431 } 1432} 1433