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