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