acme_test.go 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768
  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. package acme
  5. import (
  6. "crypto/rand"
  7. "crypto/x509"
  8. "crypto/x509/pkix"
  9. "encoding/base64"
  10. "encoding/json"
  11. "fmt"
  12. "io/ioutil"
  13. "math/big"
  14. "net/http"
  15. "net/http/httptest"
  16. "reflect"
  17. "strings"
  18. "testing"
  19. "time"
  20. "golang.org/x/net/context"
  21. )
  22. // Decodes a JWS-encoded request and unmarshals the decoded JSON into a provided
  23. // interface.
  24. func decodeJWSRequest(t *testing.T, v interface{}, r *http.Request) {
  25. // Decode request
  26. var req struct{ Payload string }
  27. if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
  28. t.Fatal(err)
  29. }
  30. payload, err := base64.RawURLEncoding.DecodeString(req.Payload)
  31. if err != nil {
  32. t.Fatal(err)
  33. }
  34. err = json.Unmarshal(payload, v)
  35. if err != nil {
  36. t.Fatal(err)
  37. }
  38. }
  39. func TestDiscover(t *testing.T) {
  40. const (
  41. reg = "https://example.com/acme/new-reg"
  42. authz = "https://example.com/acme/new-authz"
  43. cert = "https://example.com/acme/new-cert"
  44. revoke = "https://example.com/acme/revoke-cert"
  45. )
  46. ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  47. w.Header().Set("content-type", "application/json")
  48. fmt.Fprintf(w, `{
  49. "new-reg": %q,
  50. "new-authz": %q,
  51. "new-cert": %q,
  52. "revoke-cert": %q
  53. }`, reg, authz, cert, revoke)
  54. }))
  55. defer ts.Close()
  56. c := Client{DirectoryURL: ts.URL}
  57. dir, err := c.Discover()
  58. if err != nil {
  59. t.Fatal(err)
  60. }
  61. if dir.RegURL != reg {
  62. t.Errorf("dir.RegURL = %q; want %q", dir.RegURL, reg)
  63. }
  64. if dir.AuthzURL != authz {
  65. t.Errorf("dir.AuthzURL = %q; want %q", dir.AuthzURL, authz)
  66. }
  67. if dir.CertURL != cert {
  68. t.Errorf("dir.CertURL = %q; want %q", dir.CertURL, cert)
  69. }
  70. if dir.RevokeURL != revoke {
  71. t.Errorf("dir.RevokeURL = %q; want %q", dir.RevokeURL, revoke)
  72. }
  73. }
  74. func TestRegister(t *testing.T) {
  75. contacts := []string{"mailto:admin@example.com"}
  76. ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  77. if r.Method == "HEAD" {
  78. w.Header().Set("replay-nonce", "test-nonce")
  79. return
  80. }
  81. if r.Method != "POST" {
  82. t.Errorf("r.Method = %q; want POST", r.Method)
  83. }
  84. var j struct {
  85. Resource string
  86. Contact []string
  87. Agreement string
  88. }
  89. decodeJWSRequest(t, &j, r)
  90. // Test request
  91. if j.Resource != "new-reg" {
  92. t.Errorf("j.Resource = %q; want new-reg", j.Resource)
  93. }
  94. if !reflect.DeepEqual(j.Contact, contacts) {
  95. t.Errorf("j.Contact = %v; want %v", j.Contact, contacts)
  96. }
  97. w.Header().Set("Location", "https://ca.tld/acme/reg/1")
  98. w.Header().Set("Link", `<https://ca.tld/acme/new-authz>;rel="next"`)
  99. w.Header().Add("Link", `<https://ca.tld/acme/recover-reg>;rel="recover"`)
  100. w.Header().Add("Link", `<https://ca.tld/acme/terms>;rel="terms-of-service"`)
  101. w.WriteHeader(http.StatusCreated)
  102. b, _ := json.Marshal(contacts)
  103. fmt.Fprintf(w, `{
  104. "key":%q,
  105. "contact":%s
  106. }`, testKeyThumbprint, b)
  107. }))
  108. defer ts.Close()
  109. prompt := func(url string) bool {
  110. const terms = "https://ca.tld/acme/terms"
  111. if url != terms {
  112. t.Errorf("prompt url = %q; want %q", url, terms)
  113. }
  114. return false
  115. }
  116. c := Client{Key: testKey, dir: &Directory{RegURL: ts.URL}}
  117. a := &Account{Contact: contacts}
  118. var err error
  119. if a, err = c.Register(a, prompt); err != nil {
  120. t.Fatal(err)
  121. }
  122. if a.URI != "https://ca.tld/acme/reg/1" {
  123. t.Errorf("a.URI = %q; want https://ca.tld/acme/reg/1", a.URI)
  124. }
  125. if a.Authz != "https://ca.tld/acme/new-authz" {
  126. t.Errorf("a.Authz = %q; want https://ca.tld/acme/new-authz", a.Authz)
  127. }
  128. if a.CurrentTerms != "https://ca.tld/acme/terms" {
  129. t.Errorf("a.CurrentTerms = %q; want https://ca.tld/acme/terms", a.CurrentTerms)
  130. }
  131. if !reflect.DeepEqual(a.Contact, contacts) {
  132. t.Errorf("a.Contact = %v; want %v", a.Contact, contacts)
  133. }
  134. }
  135. func TestUpdateReg(t *testing.T) {
  136. const terms = "https://ca.tld/acme/terms"
  137. contacts := []string{"mailto:admin@example.com"}
  138. ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  139. if r.Method == "HEAD" {
  140. w.Header().Set("replay-nonce", "test-nonce")
  141. return
  142. }
  143. if r.Method != "POST" {
  144. t.Errorf("r.Method = %q; want POST", r.Method)
  145. }
  146. var j struct {
  147. Resource string
  148. Contact []string
  149. Agreement string
  150. }
  151. decodeJWSRequest(t, &j, r)
  152. // Test request
  153. if j.Resource != "reg" {
  154. t.Errorf("j.Resource = %q; want reg", j.Resource)
  155. }
  156. if j.Agreement != terms {
  157. t.Errorf("j.Agreement = %q; want %q", j.Agreement, terms)
  158. }
  159. if !reflect.DeepEqual(j.Contact, contacts) {
  160. t.Errorf("j.Contact = %v; want %v", j.Contact, contacts)
  161. }
  162. w.Header().Set("Link", `<https://ca.tld/acme/new-authz>;rel="next"`)
  163. w.Header().Add("Link", `<https://ca.tld/acme/recover-reg>;rel="recover"`)
  164. w.Header().Add("Link", fmt.Sprintf(`<%s>;rel="terms-of-service"`, terms))
  165. w.WriteHeader(http.StatusOK)
  166. b, _ := json.Marshal(contacts)
  167. fmt.Fprintf(w, `{
  168. "key":%q,
  169. "contact":%s,
  170. "agreement":%q
  171. }`, testKeyThumbprint, b, terms)
  172. }))
  173. defer ts.Close()
  174. c := Client{Key: testKey}
  175. a := &Account{URI: ts.URL, Contact: contacts, AgreedTerms: terms}
  176. var err error
  177. if a, err = c.UpdateReg(a); err != nil {
  178. t.Fatal(err)
  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.AgreedTerms != terms {
  184. t.Errorf("a.AgreedTerms = %q; want %q", a.AgreedTerms, terms)
  185. }
  186. if a.CurrentTerms != terms {
  187. t.Errorf("a.CurrentTerms = %q; want %q", a.CurrentTerms, terms)
  188. }
  189. }
  190. func TestGetReg(t *testing.T) {
  191. const terms = "https://ca.tld/acme/terms"
  192. const newTerms = "https://ca.tld/acme/new-terms"
  193. contacts := []string{"mailto:admin@example.com"}
  194. ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  195. if r.Method == "HEAD" {
  196. w.Header().Set("replay-nonce", "test-nonce")
  197. return
  198. }
  199. if r.Method != "POST" {
  200. t.Errorf("r.Method = %q; want POST", r.Method)
  201. }
  202. var j struct {
  203. Resource string
  204. Contact []string
  205. Agreement string
  206. }
  207. decodeJWSRequest(t, &j, r)
  208. // Test request
  209. if j.Resource != "reg" {
  210. t.Errorf("j.Resource = %q; want reg", j.Resource)
  211. }
  212. if len(j.Contact) != 0 {
  213. t.Errorf("j.Contact = %v", j.Contact)
  214. }
  215. if j.Agreement != "" {
  216. t.Errorf("j.Agreement = %q", j.Agreement)
  217. }
  218. w.Header().Set("Link", `<https://ca.tld/acme/new-authz>;rel="next"`)
  219. w.Header().Add("Link", `<https://ca.tld/acme/recover-reg>;rel="recover"`)
  220. w.Header().Add("Link", fmt.Sprintf(`<%s>;rel="terms-of-service"`, newTerms))
  221. w.WriteHeader(http.StatusOK)
  222. b, _ := json.Marshal(contacts)
  223. fmt.Fprintf(w, `{
  224. "key":%q,
  225. "contact":%s,
  226. "agreement":%q
  227. }`, testKeyThumbprint, b, terms)
  228. }))
  229. defer ts.Close()
  230. c := Client{Key: testKey}
  231. a, err := c.GetReg(ts.URL)
  232. if err != nil {
  233. t.Fatal(err)
  234. }
  235. if a.Authz != "https://ca.tld/acme/new-authz" {
  236. t.Errorf("a.AuthzURL = %q; want https://ca.tld/acme/new-authz", a.Authz)
  237. }
  238. if a.AgreedTerms != terms {
  239. t.Errorf("a.AgreedTerms = %q; want %q", a.AgreedTerms, terms)
  240. }
  241. if a.CurrentTerms != newTerms {
  242. t.Errorf("a.CurrentTerms = %q; want %q", a.CurrentTerms, newTerms)
  243. }
  244. }
  245. func TestAuthorize(t *testing.T) {
  246. ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  247. if r.Method == "HEAD" {
  248. w.Header().Set("replay-nonce", "test-nonce")
  249. return
  250. }
  251. if r.Method != "POST" {
  252. t.Errorf("r.Method = %q; want POST", r.Method)
  253. }
  254. var j struct {
  255. Resource string
  256. Identifier struct {
  257. Type string
  258. Value string
  259. }
  260. }
  261. decodeJWSRequest(t, &j, r)
  262. // Test request
  263. if j.Resource != "new-authz" {
  264. t.Errorf("j.Resource = %q; want new-authz", j.Resource)
  265. }
  266. if j.Identifier.Type != "dns" {
  267. t.Errorf("j.Identifier.Type = %q; want dns", j.Identifier.Type)
  268. }
  269. if j.Identifier.Value != "example.com" {
  270. t.Errorf("j.Identifier.Value = %q; want example.com", j.Identifier.Value)
  271. }
  272. w.Header().Set("Location", "https://ca.tld/acme/auth/1")
  273. w.WriteHeader(http.StatusCreated)
  274. fmt.Fprintf(w, `{
  275. "identifier": {"type":"dns","value":"example.com"},
  276. "status":"pending",
  277. "challenges":[
  278. {
  279. "type":"http-01",
  280. "status":"pending",
  281. "uri":"https://ca.tld/acme/challenge/publickey/id1",
  282. "token":"token1"
  283. },
  284. {
  285. "type":"tls-sni-01",
  286. "status":"pending",
  287. "uri":"https://ca.tld/acme/challenge/publickey/id2",
  288. "token":"token2"
  289. }
  290. ],
  291. "combinations":[[0],[1]]}`)
  292. }))
  293. defer ts.Close()
  294. cl := Client{Key: testKey, dir: &Directory{AuthzURL: ts.URL}}
  295. auth, err := cl.Authorize("example.com")
  296. if err != nil {
  297. t.Fatal(err)
  298. }
  299. if auth.URI != "https://ca.tld/acme/auth/1" {
  300. t.Errorf("URI = %q; want https://ca.tld/acme/auth/1", auth.URI)
  301. }
  302. if auth.Status != "pending" {
  303. t.Errorf("Status = %q; want pending", auth.Status)
  304. }
  305. if auth.Identifier.Type != "dns" {
  306. t.Errorf("Identifier.Type = %q; want dns", auth.Identifier.Type)
  307. }
  308. if auth.Identifier.Value != "example.com" {
  309. t.Errorf("Identifier.Value = %q; want example.com", auth.Identifier.Value)
  310. }
  311. if n := len(auth.Challenges); n != 2 {
  312. t.Fatalf("len(auth.Challenges) = %d; want 2", n)
  313. }
  314. c := auth.Challenges[0]
  315. if c.Type != "http-01" {
  316. t.Errorf("c.Type = %q; want http-01", c.Type)
  317. }
  318. if c.URI != "https://ca.tld/acme/challenge/publickey/id1" {
  319. t.Errorf("c.URI = %q; want https://ca.tld/acme/challenge/publickey/id1", c.URI)
  320. }
  321. if c.Token != "token1" {
  322. t.Errorf("c.Token = %q; want token1", c.Type)
  323. }
  324. c = auth.Challenges[1]
  325. if c.Type != "tls-sni-01" {
  326. t.Errorf("c.Type = %q; want tls-sni-01", c.Type)
  327. }
  328. if c.URI != "https://ca.tld/acme/challenge/publickey/id2" {
  329. t.Errorf("c.URI = %q; want https://ca.tld/acme/challenge/publickey/id2", c.URI)
  330. }
  331. if c.Token != "token2" {
  332. t.Errorf("c.Token = %q; want token2", c.Type)
  333. }
  334. combs := [][]int{{0}, {1}}
  335. if !reflect.DeepEqual(auth.Combinations, combs) {
  336. t.Errorf("auth.Combinations: %+v\nwant: %+v\n", auth.Combinations, combs)
  337. }
  338. }
  339. func TestPollAuthz(t *testing.T) {
  340. ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  341. if r.Method != "GET" {
  342. t.Errorf("r.Method = %q; want GET", r.Method)
  343. }
  344. w.WriteHeader(http.StatusOK)
  345. fmt.Fprintf(w, `{
  346. "identifier": {"type":"dns","value":"example.com"},
  347. "status":"pending",
  348. "challenges":[
  349. {
  350. "type":"http-01",
  351. "status":"pending",
  352. "uri":"https://ca.tld/acme/challenge/publickey/id1",
  353. "token":"token1"
  354. },
  355. {
  356. "type":"tls-sni-01",
  357. "status":"pending",
  358. "uri":"https://ca.tld/acme/challenge/publickey/id2",
  359. "token":"token2"
  360. }
  361. ],
  362. "combinations":[[0],[1]]}`)
  363. }))
  364. defer ts.Close()
  365. cl := Client{Key: testKey}
  366. auth, err := cl.GetAuthz(ts.URL)
  367. if err != nil {
  368. t.Fatal(err)
  369. }
  370. if auth.Status != "pending" {
  371. t.Errorf("Status = %q; want pending", auth.Status)
  372. }
  373. if auth.Identifier.Type != "dns" {
  374. t.Errorf("Identifier.Type = %q; want dns", auth.Identifier.Type)
  375. }
  376. if auth.Identifier.Value != "example.com" {
  377. t.Errorf("Identifier.Value = %q; want example.com", auth.Identifier.Value)
  378. }
  379. if n := len(auth.Challenges); n != 2 {
  380. t.Fatalf("len(set.Challenges) = %d; want 2", n)
  381. }
  382. c := auth.Challenges[0]
  383. if c.Type != "http-01" {
  384. t.Errorf("c.Type = %q; want http-01", c.Type)
  385. }
  386. if c.URI != "https://ca.tld/acme/challenge/publickey/id1" {
  387. t.Errorf("c.URI = %q; want https://ca.tld/acme/challenge/publickey/id1", c.URI)
  388. }
  389. if c.Token != "token1" {
  390. t.Errorf("c.Token = %q; want token1", c.Type)
  391. }
  392. c = auth.Challenges[1]
  393. if c.Type != "tls-sni-01" {
  394. t.Errorf("c.Type = %q; want tls-sni-01", c.Type)
  395. }
  396. if c.URI != "https://ca.tld/acme/challenge/publickey/id2" {
  397. t.Errorf("c.URI = %q; want https://ca.tld/acme/challenge/publickey/id2", c.URI)
  398. }
  399. if c.Token != "token2" {
  400. t.Errorf("c.Token = %q; want token2", c.Type)
  401. }
  402. combs := [][]int{{0}, {1}}
  403. if !reflect.DeepEqual(auth.Combinations, combs) {
  404. t.Errorf("auth.Combinations: %+v\nwant: %+v\n", auth.Combinations, combs)
  405. }
  406. }
  407. func TestPollChallenge(t *testing.T) {
  408. ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  409. if r.Method != "GET" {
  410. t.Errorf("r.Method = %q; want GET", r.Method)
  411. }
  412. w.WriteHeader(http.StatusOK)
  413. fmt.Fprintf(w, `{
  414. "type":"http-01",
  415. "status":"pending",
  416. "uri":"https://ca.tld/acme/challenge/publickey/id1",
  417. "token":"token1"}`)
  418. }))
  419. defer ts.Close()
  420. cl := Client{Key: testKey}
  421. chall, err := cl.GetChallenge(ts.URL)
  422. if err != nil {
  423. t.Fatal(err)
  424. }
  425. if chall.Status != "pending" {
  426. t.Errorf("Status = %q; want pending", chall.Status)
  427. }
  428. if chall.Type != "http-01" {
  429. t.Errorf("c.Type = %q; want http-01", chall.Type)
  430. }
  431. if chall.URI != "https://ca.tld/acme/challenge/publickey/id1" {
  432. t.Errorf("c.URI = %q; want https://ca.tld/acme/challenge/publickey/id1", chall.URI)
  433. }
  434. if chall.Token != "token1" {
  435. t.Errorf("c.Token = %q; want token1", chall.Type)
  436. }
  437. }
  438. func TestAcceptChallenge(t *testing.T) {
  439. ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  440. if r.Method == "HEAD" {
  441. w.Header().Set("replay-nonce", "test-nonce")
  442. return
  443. }
  444. if r.Method != "POST" {
  445. t.Errorf("r.Method = %q; want POST", r.Method)
  446. }
  447. var j struct {
  448. Resource string
  449. Type string
  450. Auth string `json:"keyAuthorization"`
  451. }
  452. decodeJWSRequest(t, &j, r)
  453. // Test request
  454. if j.Resource != "challenge" {
  455. t.Errorf(`resource = %q; want "challenge"`, j.Resource)
  456. }
  457. if j.Type != "http-01" {
  458. t.Errorf(`type = %q; want "http-01"`, j.Type)
  459. }
  460. keyAuth := "token1." + testKeyThumbprint
  461. if j.Auth != keyAuth {
  462. t.Errorf(`keyAuthorization = %q; want %q`, j.Auth, keyAuth)
  463. }
  464. // Respond to request
  465. w.WriteHeader(http.StatusAccepted)
  466. fmt.Fprintf(w, `{
  467. "type":"http-01",
  468. "status":"pending",
  469. "uri":"https://ca.tld/acme/challenge/publickey/id1",
  470. "token":"token1",
  471. "keyAuthorization":%q
  472. }`, keyAuth)
  473. }))
  474. defer ts.Close()
  475. cl := Client{Key: testKey}
  476. c, err := cl.Accept(&Challenge{
  477. URI: ts.URL,
  478. Token: "token1",
  479. Type: "http-01",
  480. })
  481. if err != nil {
  482. t.Fatal(err)
  483. }
  484. if c.Type != "http-01" {
  485. t.Errorf("c.Type = %q; want http-01", c.Type)
  486. }
  487. if c.URI != "https://ca.tld/acme/challenge/publickey/id1" {
  488. t.Errorf("c.URI = %q; want https://ca.tld/acme/challenge/publickey/id1", c.URI)
  489. }
  490. if c.Token != "token1" {
  491. t.Errorf("c.Token = %q; want token1", c.Type)
  492. }
  493. }
  494. func TestNewCert(t *testing.T) {
  495. notBefore := time.Now()
  496. notAfter := notBefore.AddDate(0, 2, 0)
  497. timeNow = func() time.Time { return notBefore }
  498. ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  499. if r.Method == "HEAD" {
  500. w.Header().Set("replay-nonce", "test-nonce")
  501. return
  502. }
  503. if r.Method != "POST" {
  504. t.Errorf("r.Method = %q; want POST", r.Method)
  505. }
  506. var j struct {
  507. Resource string `json:"resource"`
  508. CSR string `json:"csr"`
  509. NotBefore string `json:"notBefore,omitempty"`
  510. NotAfter string `json:"notAfter,omitempty"`
  511. }
  512. decodeJWSRequest(t, &j, r)
  513. // Test request
  514. if j.Resource != "new-cert" {
  515. t.Errorf(`resource = %q; want "new-cert"`, j.Resource)
  516. }
  517. if j.NotBefore != notBefore.Format(time.RFC3339) {
  518. t.Errorf(`notBefore = %q; wanted %q`, j.NotBefore, notBefore.Format(time.RFC3339))
  519. }
  520. if j.NotAfter != notAfter.Format(time.RFC3339) {
  521. t.Errorf(`notAfter = %q; wanted %q`, j.NotAfter, notAfter.Format(time.RFC3339))
  522. }
  523. // Respond to request
  524. template := x509.Certificate{
  525. SerialNumber: big.NewInt(int64(1)),
  526. Subject: pkix.Name{
  527. Organization: []string{"goacme"},
  528. },
  529. NotBefore: notBefore,
  530. NotAfter: notAfter,
  531. KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
  532. ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
  533. BasicConstraintsValid: true,
  534. }
  535. sampleCert, err := x509.CreateCertificate(rand.Reader, &template, &template, &testKey.PublicKey, testKey)
  536. if err != nil {
  537. t.Fatalf("Error creating certificate: %v", err)
  538. }
  539. w.Header().Set("Location", "https://ca.tld/acme/cert/1")
  540. w.WriteHeader(http.StatusCreated)
  541. w.Write(sampleCert)
  542. }))
  543. defer ts.Close()
  544. csr := x509.CertificateRequest{
  545. Version: 0,
  546. Subject: pkix.Name{
  547. CommonName: "example.com",
  548. Organization: []string{"goacme"},
  549. },
  550. }
  551. csrb, err := x509.CreateCertificateRequest(rand.Reader, &csr, testKey)
  552. if err != nil {
  553. t.Fatal(err)
  554. }
  555. c := Client{Key: testKey, dir: &Directory{CertURL: ts.URL}}
  556. cert, certURL, err := c.CreateCert(context.Background(), csrb, notAfter.Sub(notBefore), false)
  557. if err != nil {
  558. t.Fatal(err)
  559. }
  560. if cert == nil {
  561. t.Errorf("cert is nil")
  562. }
  563. if certURL != "https://ca.tld/acme/cert/1" {
  564. t.Errorf("certURL = %q; want https://ca.tld/acme/cert/1", certURL)
  565. }
  566. }
  567. func TestFetchCert(t *testing.T) {
  568. ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  569. w.Write([]byte{1})
  570. }))
  571. defer ts.Close()
  572. res, err := (&Client{}).FetchCert(context.Background(), ts.URL, false)
  573. if err != nil {
  574. t.Fatalf("FetchCert: %v", err)
  575. }
  576. cert := [][]byte{{1}}
  577. if !reflect.DeepEqual(res, cert) {
  578. t.Errorf("res = %v; want %v", res, cert)
  579. }
  580. }
  581. func TestFetchCertRetry(t *testing.T) {
  582. var count int
  583. ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  584. if count < 1 {
  585. w.Header().Set("retry-after", "0")
  586. w.WriteHeader(http.StatusAccepted)
  587. count++
  588. return
  589. }
  590. w.Write([]byte{1})
  591. }))
  592. defer ts.Close()
  593. res, err := (&Client{}).FetchCert(context.Background(), ts.URL, false)
  594. if err != nil {
  595. t.Fatalf("FetchCert: %v", err)
  596. }
  597. cert := [][]byte{{1}}
  598. if !reflect.DeepEqual(res, cert) {
  599. t.Errorf("res = %v; want %v", res, cert)
  600. }
  601. }
  602. func TestFetchCertCancel(t *testing.T) {
  603. ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  604. w.Header().Set("retry-after", "0")
  605. w.WriteHeader(http.StatusAccepted)
  606. }))
  607. defer ts.Close()
  608. ctx, cancel := context.WithCancel(context.Background())
  609. done := make(chan struct{})
  610. var err error
  611. go func() {
  612. _, err = (&Client{}).FetchCert(ctx, ts.URL, false)
  613. close(done)
  614. }()
  615. cancel()
  616. <-done
  617. if err != context.Canceled {
  618. t.Errorf("err = %v; want %v", err, context.Canceled)
  619. }
  620. }
  621. func TestFetchNonce(t *testing.T) {
  622. tests := []struct {
  623. code int
  624. nonce string
  625. }{
  626. {http.StatusOK, "nonce1"},
  627. {http.StatusBadRequest, "nonce2"},
  628. {http.StatusOK, ""},
  629. }
  630. var i int
  631. ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  632. if r.Method != "HEAD" {
  633. t.Errorf("%d: r.Method = %q; want HEAD", i, r.Method)
  634. }
  635. w.Header().Set("replay-nonce", tests[i].nonce)
  636. w.WriteHeader(tests[i].code)
  637. }))
  638. defer ts.Close()
  639. for ; i < len(tests); i++ {
  640. test := tests[i]
  641. n, err := fetchNonce(http.DefaultClient, ts.URL)
  642. if n != test.nonce {
  643. t.Errorf("%d: n=%q; want %q", i, n, test.nonce)
  644. }
  645. switch {
  646. case err == nil && test.nonce == "":
  647. t.Errorf("%d: n=%q, err=%v; want non-nil error", i, n, err)
  648. case err != nil && test.nonce != "":
  649. t.Errorf("%d: n=%q, err=%v; want %q", i, n, err, test.nonce)
  650. }
  651. }
  652. }
  653. func TestLinkHeader(t *testing.T) {
  654. h := http.Header{"Link": {
  655. `<https://example.com/acme/new-authz>;rel="next"`,
  656. `<https://example.com/acme/recover-reg>; rel=recover`,
  657. `<https://example.com/acme/terms>; foo=bar; rel="terms-of-service"`,
  658. }}
  659. tests := []struct{ in, out string }{
  660. {"next", "https://example.com/acme/new-authz"},
  661. {"recover", "https://example.com/acme/recover-reg"},
  662. {"terms-of-service", "https://example.com/acme/terms"},
  663. {"empty", ""},
  664. }
  665. for i, test := range tests {
  666. if v := linkHeader(h, test.in); v != test.out {
  667. t.Errorf("%d: parseLinkHeader(%q): %q; want %q", i, test.in, v, test.out)
  668. }
  669. }
  670. }
  671. func TestErrorResponse(t *testing.T) {
  672. s := `{
  673. "status": 400,
  674. "type": "urn:acme:error:xxx",
  675. "detail": "text"
  676. }`
  677. res := &http.Response{
  678. StatusCode: 400,
  679. Status: "400 Bad Request",
  680. Body: ioutil.NopCloser(strings.NewReader(s)),
  681. Header: http.Header{"X-Foo": {"bar"}},
  682. }
  683. err := responseError(res)
  684. v, ok := err.(*Error)
  685. if !ok {
  686. t.Fatalf("err = %+v (%T); want *Error type", err, err)
  687. }
  688. if v.StatusCode != 400 {
  689. t.Errorf("v.StatusCode = %v; want 400", v.StatusCode)
  690. }
  691. if v.ProblemType != "urn:acme:error:xxx" {
  692. t.Errorf("v.ProblemType = %q; want urn:acme:error:xxx", v.ProblemType)
  693. }
  694. if v.Detail != "text" {
  695. t.Errorf("v.Detail = %q; want text", v.Detail)
  696. }
  697. if !reflect.DeepEqual(v.Header, res.Header) {
  698. t.Errorf("v.Header = %+v; want %+v", v.Header, res.Header)
  699. }
  700. }