rfc8555_test.go 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476
  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. package acme
  5. import (
  6. "bytes"
  7. "context"
  8. "encoding/json"
  9. "fmt"
  10. "io/ioutil"
  11. "net/http"
  12. "net/http/httptest"
  13. "reflect"
  14. "sync"
  15. "testing"
  16. "time"
  17. )
  18. // While contents of this file is pertinent only to RFC8555,
  19. // it is complementary to the tests in the other _test.go files
  20. // many of which are valid for both pre- and RFC8555.
  21. // This will make it easier to clean up the tests once non-RFC compliant
  22. // code is removed.
  23. func TestRFC_Discover(t *testing.T) {
  24. const (
  25. nonce = "https://example.com/acme/new-nonce"
  26. reg = "https://example.com/acme/new-acct"
  27. order = "https://example.com/acme/new-order"
  28. authz = "https://example.com/acme/new-authz"
  29. revoke = "https://example.com/acme/revoke-cert"
  30. keychange = "https://example.com/acme/key-change"
  31. metaTerms = "https://example.com/acme/terms/2017-5-30"
  32. metaWebsite = "https://www.example.com/"
  33. metaCAA = "example.com"
  34. )
  35. ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  36. w.Header().Set("Content-Type", "application/json")
  37. fmt.Fprintf(w, `{
  38. "newNonce": %q,
  39. "newAccount": %q,
  40. "newOrder": %q,
  41. "newAuthz": %q,
  42. "revokeCert": %q,
  43. "keyChange": %q,
  44. "meta": {
  45. "termsOfService": %q,
  46. "website": %q,
  47. "caaIdentities": [%q],
  48. "externalAccountRequired": true
  49. }
  50. }`, nonce, reg, order, authz, revoke, keychange, metaTerms, metaWebsite, metaCAA)
  51. }))
  52. defer ts.Close()
  53. c := Client{DirectoryURL: ts.URL}
  54. dir, err := c.Discover(context.Background())
  55. if err != nil {
  56. t.Fatal(err)
  57. }
  58. if dir.NonceURL != nonce {
  59. t.Errorf("dir.NonceURL = %q; want %q", dir.NonceURL, nonce)
  60. }
  61. if dir.RegURL != reg {
  62. t.Errorf("dir.RegURL = %q; want %q", dir.RegURL, reg)
  63. }
  64. if dir.OrderURL != order {
  65. t.Errorf("dir.OrderURL = %q; want %q", dir.OrderURL, order)
  66. }
  67. if dir.AuthzURL != authz {
  68. t.Errorf("dir.AuthzURL = %q; want %q", dir.AuthzURL, authz)
  69. }
  70. if dir.RevokeURL != revoke {
  71. t.Errorf("dir.RevokeURL = %q; want %q", dir.RevokeURL, revoke)
  72. }
  73. if dir.KeyChangeURL != keychange {
  74. t.Errorf("dir.KeyChangeURL = %q; want %q", dir.KeyChangeURL, keychange)
  75. }
  76. if dir.Terms != metaTerms {
  77. t.Errorf("dir.Terms = %q; want %q", dir.Terms, metaTerms)
  78. }
  79. if dir.Website != metaWebsite {
  80. t.Errorf("dir.Website = %q; want %q", dir.Website, metaWebsite)
  81. }
  82. if len(dir.CAA) == 0 || dir.CAA[0] != metaCAA {
  83. t.Errorf("dir.CAA = %q; want [%q]", dir.CAA, metaCAA)
  84. }
  85. if !dir.ExternalAccountRequired {
  86. t.Error("dir.Meta.ExternalAccountRequired is false")
  87. }
  88. }
  89. func TestRFC_popNonce(t *testing.T) {
  90. var count int
  91. ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  92. // The Client uses only Directory.NonceURL when specified.
  93. // Expect no other URL paths.
  94. if r.URL.Path != "/new-nonce" {
  95. t.Errorf("r.URL.Path = %q; want /new-nonce", r.URL.Path)
  96. }
  97. if count > 0 {
  98. w.WriteHeader(http.StatusTooManyRequests)
  99. return
  100. }
  101. count++
  102. w.Header().Set("Replay-Nonce", "second")
  103. }))
  104. cl := &Client{
  105. DirectoryURL: ts.URL,
  106. dir: &Directory{NonceURL: ts.URL + "/new-nonce"},
  107. }
  108. cl.addNonce(http.Header{"Replay-Nonce": {"first"}})
  109. for i, nonce := range []string{"first", "second"} {
  110. v, err := cl.popNonce(context.Background(), "")
  111. if err != nil {
  112. t.Errorf("%d: cl.popNonce: %v", i, err)
  113. }
  114. if v != nonce {
  115. t.Errorf("%d: cl.popNonce = %q; want %q", i, v, nonce)
  116. }
  117. }
  118. // No more nonces and server replies with an error past first nonce fetch.
  119. // Expected to fail.
  120. if _, err := cl.popNonce(context.Background(), ""); err == nil {
  121. t.Error("last cl.popNonce returned nil error")
  122. }
  123. }
  124. func TestRFC_postKID(t *testing.T) {
  125. var ts *httptest.Server
  126. ts = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  127. switch r.URL.Path {
  128. case "/new-nonce":
  129. w.Header().Set("Replay-Nonce", "nonce")
  130. case "/new-account":
  131. w.Header().Set("Location", "/account-1")
  132. w.Write([]byte(`{"status":"valid"}`))
  133. case "/post":
  134. b, _ := ioutil.ReadAll(r.Body) // check err later in decodeJWSxxx
  135. head, err := decodeJWSHead(bytes.NewReader(b))
  136. if err != nil {
  137. t.Errorf("decodeJWSHead: %v", err)
  138. return
  139. }
  140. if head.KID != "/account-1" {
  141. t.Errorf("head.KID = %q; want /account-1", head.KID)
  142. }
  143. if len(head.JWK) != 0 {
  144. t.Errorf("head.JWK = %q; want zero map", head.JWK)
  145. }
  146. if v := ts.URL + "/post"; head.URL != v {
  147. t.Errorf("head.URL = %q; want %q", head.URL, v)
  148. }
  149. var payload struct{ Msg string }
  150. decodeJWSRequest(t, &payload, bytes.NewReader(b))
  151. if payload.Msg != "ping" {
  152. t.Errorf("payload.Msg = %q; want ping", payload.Msg)
  153. }
  154. w.Write([]byte("pong"))
  155. default:
  156. t.Errorf("unhandled %s %s", r.Method, r.URL)
  157. w.WriteHeader(http.StatusBadRequest)
  158. }
  159. }))
  160. defer ts.Close()
  161. ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
  162. defer cancel()
  163. cl := &Client{
  164. Key: testKey,
  165. DirectoryURL: ts.URL,
  166. dir: &Directory{
  167. NonceURL: ts.URL + "/new-nonce",
  168. RegURL: ts.URL + "/new-account",
  169. OrderURL: "/force-rfc-mode",
  170. },
  171. }
  172. req := json.RawMessage(`{"msg":"ping"}`)
  173. res, err := cl.post(ctx, nil /* use kid */, ts.URL+"/post", req, wantStatus(http.StatusOK))
  174. if err != nil {
  175. t.Fatal(err)
  176. }
  177. defer res.Body.Close()
  178. b, _ := ioutil.ReadAll(res.Body) // don't care about err - just checking b
  179. if string(b) != "pong" {
  180. t.Errorf("res.Body = %q; want pong", b)
  181. }
  182. }
  183. // acmeServer simulates a subset of RFC8555 compliant CA.
  184. //
  185. // TODO: We also have x/crypto/acme/autocert/acmetest and startACMEServerStub in autocert_test.go.
  186. // It feels like this acmeServer is a sweet spot between usefulness and added complexity.
  187. // Also, acmetest and startACMEServerStub were both written for draft-02, no RFC support.
  188. // The goal is to consolidate all into one ACME test server.
  189. type acmeServer struct {
  190. ts *httptest.Server
  191. handler map[string]http.HandlerFunc // keyed by r.URL.Path
  192. mu sync.Mutex
  193. nnonce int
  194. }
  195. func newACMEServer() *acmeServer {
  196. return &acmeServer{handler: make(map[string]http.HandlerFunc)}
  197. }
  198. func (s *acmeServer) handle(path string, f func(http.ResponseWriter, *http.Request)) {
  199. s.handler[path] = http.HandlerFunc(f)
  200. }
  201. func (s *acmeServer) start() {
  202. s.ts = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  203. w.Header().Set("Content-Type", "application/json")
  204. // Directory request.
  205. if r.URL.Path == "/" {
  206. fmt.Fprintf(w, `{
  207. "newNonce": %q,
  208. "newAccount": %q,
  209. "newOrder": %q,
  210. "newAuthz": %q,
  211. "revokeCert": %q,
  212. "meta": {"termsOfService": %q}
  213. }`,
  214. s.url("/acme/new-nonce"),
  215. s.url("/acme/new-account"),
  216. s.url("/acme/new-order"),
  217. s.url("/acme/new-authz"),
  218. s.url("/acme/revoke-cert"),
  219. s.url("/terms"),
  220. )
  221. return
  222. }
  223. // All other responses contain a nonce value unconditionally.
  224. w.Header().Set("Replay-Nonce", s.nonce())
  225. if r.URL.Path == "/acme/new-nonce" {
  226. return
  227. }
  228. h := s.handler[r.URL.Path]
  229. if h == nil {
  230. w.WriteHeader(http.StatusBadRequest)
  231. fmt.Fprintf(w, "Unhandled %s", r.URL.Path)
  232. return
  233. }
  234. h.ServeHTTP(w, r)
  235. }))
  236. }
  237. func (s *acmeServer) close() {
  238. s.ts.Close()
  239. }
  240. func (s *acmeServer) url(path string) string {
  241. return s.ts.URL + path
  242. }
  243. func (s *acmeServer) nonce() string {
  244. s.mu.Lock()
  245. defer s.mu.Unlock()
  246. s.nnonce++
  247. return fmt.Sprintf("nonce%d", s.nnonce)
  248. }
  249. func (s *acmeServer) error(w http.ResponseWriter, e *wireError) {
  250. w.WriteHeader(e.Status)
  251. json.NewEncoder(w).Encode(e)
  252. }
  253. func TestRFC_Register(t *testing.T) {
  254. const email = "mailto:user@example.org"
  255. s := newACMEServer()
  256. s.handle("/acme/new-account", func(w http.ResponseWriter, r *http.Request) {
  257. w.Header().Set("Location", s.url("/accounts/1"))
  258. w.WriteHeader(http.StatusCreated) // 201 means new account created
  259. fmt.Fprintf(w, `{
  260. "status": "valid",
  261. "contact": [%q],
  262. "orders": %q
  263. }`, email, s.url("/accounts/1/orders"))
  264. b, _ := ioutil.ReadAll(r.Body) // check err later in decodeJWSxxx
  265. head, err := decodeJWSHead(bytes.NewReader(b))
  266. if err != nil {
  267. t.Errorf("decodeJWSHead: %v", err)
  268. return
  269. }
  270. if len(head.JWK) == 0 {
  271. t.Error("head.JWK is empty")
  272. }
  273. var req struct{ Contact []string }
  274. decodeJWSRequest(t, &req, bytes.NewReader(b))
  275. if len(req.Contact) != 1 || req.Contact[0] != email {
  276. t.Errorf("req.Contact = %q; want [%q]", req.Contact, email)
  277. }
  278. })
  279. s.start()
  280. defer s.close()
  281. ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
  282. defer cancel()
  283. cl := &Client{
  284. Key: testKeyEC,
  285. DirectoryURL: s.url("/"),
  286. }
  287. var didPrompt bool
  288. a := &Account{Contact: []string{email}}
  289. acct, err := cl.Register(ctx, a, func(tos string) bool {
  290. didPrompt = true
  291. terms := s.url("/terms")
  292. if tos != terms {
  293. t.Errorf("tos = %q; want %q", tos, terms)
  294. }
  295. return true
  296. })
  297. if err != nil {
  298. t.Fatal(err)
  299. }
  300. okAccount := &Account{
  301. URI: s.url("/accounts/1"),
  302. Status: StatusValid,
  303. Contact: []string{email},
  304. OrdersURL: s.url("/accounts/1/orders"),
  305. }
  306. if !reflect.DeepEqual(acct, okAccount) {
  307. t.Errorf("acct = %+v; want %+v", acct, okAccount)
  308. }
  309. if !didPrompt {
  310. t.Error("tos prompt wasn't called")
  311. }
  312. if v := cl.accountKID(ctx); v != keyID(okAccount.URI) {
  313. t.Errorf("account kid = %q; want %q", v, okAccount.URI)
  314. }
  315. }
  316. func TestRFC_RegisterExisting(t *testing.T) {
  317. s := newACMEServer()
  318. s.handle("/acme/new-account", func(w http.ResponseWriter, r *http.Request) {
  319. w.Header().Set("Location", s.url("/accounts/1"))
  320. w.WriteHeader(http.StatusOK) // 200 means account already exists
  321. w.Write([]byte(`{"status": "valid"}`))
  322. })
  323. s.start()
  324. defer s.close()
  325. cl := &Client{Key: testKeyEC, DirectoryURL: s.url("/")}
  326. _, err := cl.Register(context.Background(), &Account{}, AcceptTOS)
  327. if err != ErrAccountAlreadyExists {
  328. t.Errorf("err = %v; want %v", err, ErrAccountAlreadyExists)
  329. }
  330. kid := keyID(s.url("/accounts/1"))
  331. if v := cl.accountKID(context.Background()); v != kid {
  332. t.Errorf("account kid = %q; want %q", v, kid)
  333. }
  334. }
  335. func TestRFC_UpdateReg(t *testing.T) {
  336. const email = "mailto:user@example.org"
  337. s := newACMEServer()
  338. s.handle("/acme/new-account", func(w http.ResponseWriter, r *http.Request) {
  339. w.Header().Set("Location", s.url("/accounts/1"))
  340. w.WriteHeader(http.StatusOK)
  341. w.Write([]byte(`{"status": "valid"}`))
  342. })
  343. var didUpdate bool
  344. s.handle("/accounts/1", func(w http.ResponseWriter, r *http.Request) {
  345. didUpdate = true
  346. w.Header().Set("Location", s.url("/accounts/1"))
  347. w.WriteHeader(http.StatusOK)
  348. w.Write([]byte(`{"status": "valid"}`))
  349. b, _ := ioutil.ReadAll(r.Body) // check err later in decodeJWSxxx
  350. head, err := decodeJWSHead(bytes.NewReader(b))
  351. if err != nil {
  352. t.Errorf("decodeJWSHead: %v", err)
  353. return
  354. }
  355. if len(head.JWK) != 0 {
  356. t.Error("head.JWK is non-zero")
  357. }
  358. kid := s.url("/accounts/1")
  359. if head.KID != kid {
  360. t.Errorf("head.KID = %q; want %q", head.KID, kid)
  361. }
  362. var req struct{ Contact []string }
  363. decodeJWSRequest(t, &req, bytes.NewReader(b))
  364. if len(req.Contact) != 1 || req.Contact[0] != email {
  365. t.Errorf("req.Contact = %q; want [%q]", req.Contact, email)
  366. }
  367. })
  368. s.start()
  369. defer s.close()
  370. cl := &Client{Key: testKeyEC, DirectoryURL: s.url("/")}
  371. _, err := cl.UpdateReg(context.Background(), &Account{Contact: []string{email}})
  372. if err != nil {
  373. t.Error(err)
  374. }
  375. if !didUpdate {
  376. t.Error("UpdateReg didn't update the account")
  377. }
  378. }
  379. func TestRFC_GetReg(t *testing.T) {
  380. s := newACMEServer()
  381. s.handle("/acme/new-account", func(w http.ResponseWriter, r *http.Request) {
  382. w.Header().Set("Location", s.url("/accounts/1"))
  383. w.WriteHeader(http.StatusOK)
  384. w.Write([]byte(`{"status": "valid"}`))
  385. head, err := decodeJWSHead(r.Body)
  386. if err != nil {
  387. t.Errorf("decodeJWSHead: %v", err)
  388. return
  389. }
  390. if len(head.JWK) == 0 {
  391. t.Error("head.JWK is empty")
  392. }
  393. })
  394. s.start()
  395. defer s.close()
  396. cl := &Client{Key: testKeyEC, DirectoryURL: s.url("/")}
  397. acct, err := cl.GetReg(context.Background(), "")
  398. if err != nil {
  399. t.Fatal(err)
  400. }
  401. okAccount := &Account{
  402. URI: s.url("/accounts/1"),
  403. Status: StatusValid,
  404. }
  405. if !reflect.DeepEqual(acct, okAccount) {
  406. t.Errorf("acct = %+v; want %+v", acct, okAccount)
  407. }
  408. }
  409. func TestRFC_GetRegNoAccount(t *testing.T) {
  410. s := newACMEServer()
  411. s.handle("/acme/new-account", func(w http.ResponseWriter, r *http.Request) {
  412. s.error(w, &wireError{
  413. Status: http.StatusBadRequest,
  414. Type: "urn:ietf:params:acme:error:accountDoesNotExist",
  415. })
  416. })
  417. s.start()
  418. defer s.close()
  419. cl := &Client{Key: testKeyEC, DirectoryURL: s.url("/")}
  420. if _, err := cl.GetReg(context.Background(), ""); err != ErrNoAccount {
  421. t.Errorf("err = %v; want %v", err, ErrNoAccount)
  422. }
  423. }
  424. func TestRFC_GetRegOtherError(t *testing.T) {
  425. s := newACMEServer()
  426. s.handle("/acme/new-account", func(w http.ResponseWriter, r *http.Request) {
  427. w.WriteHeader(http.StatusBadRequest)
  428. })
  429. s.start()
  430. defer s.close()
  431. cl := &Client{Key: testKeyEC, DirectoryURL: s.url("/")}
  432. if _, err := cl.GetReg(context.Background(), ""); err == nil || err == ErrNoAccount {
  433. t.Errorf("GetReg: %v; want any other non-nil err", err)
  434. }
  435. }