ACMEv2 client library for Go.

api-res.go 13KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504
  1. package acmeapi
  2. import (
  3. "context"
  4. "crypto"
  5. "encoding/json"
  6. "errors"
  7. "fmt"
  8. "git.devever.net/hlandau/acmeapi/acmeutils"
  9. denet "git.devever.net/hlandau/goutils/net"
  10. "io/ioutil"
  11. "mime"
  12. "net/http"
  13. "time"
  14. )
  15. type postAccount struct {
  16. TermsOfServiceAgreed bool `json:"termsOfServiceAgreed,omitempty"`
  17. ContactURIs []string `json:"contact,omitempty"`
  18. Status AccountStatus `json:"status,omitempty"`
  19. OnlyReturnExisting bool `json:"onlyReturnExisting,omitempty"`
  20. }
  21. func (c *RealmClient) postAccount(ctx context.Context, acct *Account, onlyReturnExisting bool) error {
  22. postAcct := &postAccount{
  23. ContactURIs: acct.ContactURIs,
  24. TermsOfServiceAgreed: acct.TermsOfServiceAgreed,
  25. OnlyReturnExisting: onlyReturnExisting,
  26. }
  27. if acct.Status == AccountDeactivated {
  28. postAcct.Status = acct.Status
  29. }
  30. endp := acct.URL
  31. expectCode := updateAccountCodes
  32. updating := true
  33. if endp == "" {
  34. di, err := c.getDirectory(ctx)
  35. if err != nil {
  36. return err
  37. }
  38. endp = di.NewAccount
  39. expectCode = newAccountCodes
  40. updating = false
  41. }
  42. acctU := acct
  43. if !updating {
  44. acctU = &noAccountNeeded
  45. }
  46. res, err := c.doReq(ctx, "POST", endp, acctU, acct.PrivateKey, postAcct, acct)
  47. if res == nil {
  48. return err
  49. }
  50. if !isStatusCode(res, expectCode) {
  51. if err != nil {
  52. return err
  53. }
  54. return fmt.Errorf("unexpected status code: %d: %q", res.StatusCode, endp)
  55. }
  56. loc := res.Header.Get("Location")
  57. if !updating {
  58. if !ValidURL(loc) {
  59. return fmt.Errorf("invalid URL: %q", loc)
  60. }
  61. acct.URL = loc
  62. } else {
  63. if loc != "" {
  64. return fmt.Errorf("unexpected Location header: %q", loc)
  65. }
  66. }
  67. return nil
  68. }
  69. func (c *RealmClient) registerAccount(ctx context.Context, acct *Account, onlyReturnExisting bool) error {
  70. if acct.URL != "" {
  71. return fmt.Errorf("cannot register account which already has an URL")
  72. }
  73. return c.postAccount(ctx, acct, onlyReturnExisting)
  74. }
  75. // Registers a new account. acct.URL must be empty and TermsOfServiceAgreed must
  76. // be true.
  77. //
  78. // The only fields of acct used for requests (that is, the only fields of an
  79. // account modifiable by the client) are the ContactURIs field, the
  80. // TermsOfServiceAgreed field and the Status field. The Status field is only sent
  81. // if it is set to AccountDeactivated ("deactivated"); no other transition can be
  82. // manually requested by the client.
  83. func (c *RealmClient) RegisterAccount(ctx context.Context, acct *Account) error {
  84. return c.registerAccount(ctx, acct, false)
  85. }
  86. // Tries to find an existing account by key if the URL is not yet known.
  87. // acct.URL must be empty. Fails if the account does not exist.
  88. func (c *RealmClient) LocateAccount(ctx context.Context, acct *Account) error {
  89. return c.registerAccount(ctx, acct, true)
  90. }
  91. // Updates an existing account. acct.URL must be set.
  92. func (c *RealmClient) UpdateAccount(ctx context.Context, acct *Account) error {
  93. if acct.URL == "" {
  94. return fmt.Errorf("cannot update account for which URL is unknown")
  95. }
  96. return c.postAccount(ctx, acct, false)
  97. }
  98. var newAccountCodes = []int{201 /* Created */, 200 /* OK */}
  99. var updateAccountCodes = []int{200 /* OK */}
  100. func isStatusCode(res *http.Response, codes []int) bool {
  101. for _, c := range codes {
  102. if c == res.StatusCode {
  103. return true
  104. }
  105. }
  106. return false
  107. }
  108. const defaultPollTime = 10 * time.Second
  109. // AUTHORIZATIONS
  110. // Load or reload the details of an authorization via its URI.
  111. //
  112. // You can load an authorization from only the URI by creating an Authorization
  113. // with the URI set and then calling this method.
  114. func (c *RealmClient) LoadAuthorization(ctx context.Context, acct *Account, az *Authorization) error {
  115. res, err := c.doReq(ctx, "GET", az.URL, acct, nil, nil, az)
  116. if err != nil {
  117. return err
  118. }
  119. err = az.validate()
  120. if err != nil {
  121. return err
  122. }
  123. az.retryAt = retryAtDefault(res.Header, defaultPollTime)
  124. return nil
  125. }
  126. // Like LoadAuthorization, but waits the retry time if this is not the first attempt
  127. // to load this authorization. To be used when polling.
  128. //
  129. // The retry delay will not work if you recreate the object; use the same
  130. // Authorization struct between calls.
  131. func (c *RealmClient) WaitLoadAuthorization(ctx context.Context, acct *Account, az *Authorization) error {
  132. err := waitUntil(ctx, az.retryAt)
  133. if err != nil {
  134. return err
  135. }
  136. return c.LoadAuthorization(ctx, acct, az)
  137. }
  138. func (az *Authorization) validate() error {
  139. if len(az.Challenges) == 0 {
  140. return errors.New("no challenges offered")
  141. }
  142. return nil
  143. }
  144. // Create a new authorization for the given hostname.
  145. //
  146. // IDN hostnames must be in punycoded form.
  147. //
  148. // Use of this method facilitates preauthentication, which is rarely necessary.
  149. // Consider simply using NewOrder instead.
  150. func (c *RealmClient) NewAuthorization(ctx context.Context, acct *Account, ident Identifier) (*Authorization, error) {
  151. di, err := c.getDirectory(ctx)
  152. if err != nil {
  153. return nil, err
  154. }
  155. az := &Authorization{
  156. Identifier: ident,
  157. }
  158. res, err := c.doReq(ctx, "POST", di.NewAuthz, acct, nil, az, az)
  159. if err != nil {
  160. return nil, err
  161. }
  162. if res.StatusCode != 201 {
  163. return nil, fmt.Errorf("expected status code 201, got %v", res.StatusCode)
  164. }
  165. loc := res.Header.Get("Location")
  166. if !ValidURL(loc) {
  167. return nil, fmt.Errorf("expected valid location, got %q", loc)
  168. }
  169. az.URL = loc
  170. err = az.validate()
  171. if err != nil {
  172. return nil, err
  173. }
  174. return az, nil
  175. }
  176. type postOrder struct {
  177. Identifiers []Identifier `json:"identifiers,omitempty"`
  178. NotBefore *time.Time `json:"notBefore,omitempty"`
  179. NotAfter *time.Time `json:"notAfter,omitempty"`
  180. }
  181. // Creates a new order. You must set at least the Identifiers field of Order.
  182. // The NotBefore and NotAfter fields may also optionally be set. The other
  183. // fields, including URI, will be filled in when the method returns.
  184. func (c *RealmClient) NewOrder(ctx context.Context, acct *Account, order *Order) error {
  185. di, err := c.getDirectory(ctx)
  186. if err != nil {
  187. return err
  188. }
  189. po := &postOrder{
  190. Identifiers: order.Identifiers,
  191. NotBefore: &order.NotBefore,
  192. NotAfter: &order.NotAfter,
  193. }
  194. if po.NotBefore.IsZero() {
  195. po.NotBefore = nil
  196. }
  197. if po.NotAfter.IsZero() {
  198. po.NotAfter = nil
  199. }
  200. res, err := c.doReq(ctx, "POST", di.NewOrder, acct, nil, po, order)
  201. if err != nil {
  202. return err
  203. }
  204. defer res.Body.Close()
  205. if res.StatusCode != 201 {
  206. return fmt.Errorf("expected status code 201, got %v", res.StatusCode)
  207. }
  208. loc := res.Header.Get("Location")
  209. if !ValidURL(loc) {
  210. return fmt.Errorf("invalid URI: %#v", loc)
  211. }
  212. order.URL = loc
  213. return nil
  214. }
  215. // Load or reload an order.
  216. //
  217. // You can load an order from its URI by creating an Order with the URI set and
  218. // then calling this.
  219. func (c *RealmClient) LoadOrder(ctx context.Context, order *Order) error {
  220. res, err := c.doReq(ctx, "GET", order.URL, nil, nil, nil, order)
  221. if err != nil {
  222. return err
  223. }
  224. err = order.validate()
  225. if err != nil {
  226. return err
  227. }
  228. order.retryAt = retryAtDefault(res.Header, defaultPollTime)
  229. return nil
  230. }
  231. // Like LoadOrder, but waits the retry time if this is not the first attempt to load
  232. // this certificate. To be used when polling.
  233. //
  234. // The retry delay will not work if you recreate the object; use the same Challenge
  235. // struct between calls.
  236. func (c *RealmClient) WaitLoadOrder(ctx context.Context, order *Order) error {
  237. err := waitUntil(ctx, order.retryAt)
  238. if err != nil {
  239. return err
  240. }
  241. return c.LoadOrder(ctx, order)
  242. }
  243. // Wait for an order to finish processing. The order must be in the
  244. // "processing" state and the method returns once this ceases to be the case.
  245. // Only the URI is required to be set.
  246. func (c *RealmClient) WaitForOrder(ctx context.Context, order *Order) error {
  247. for {
  248. if order.Status != "" && order.Status != OrderProcessing {
  249. return nil
  250. }
  251. err := c.WaitLoadOrder(ctx, order)
  252. if err != nil {
  253. return err
  254. }
  255. }
  256. }
  257. func (c *RealmClient) LoadCertificate(ctx context.Context, cert *Certificate) error {
  258. // Check input.
  259. if !ValidURL(cert.URL) {
  260. return fmt.Errorf("invalid request URL: %q", cert.URL)
  261. }
  262. // Make request.
  263. req, err := http.NewRequest("GET", cert.URL, nil)
  264. if err != nil {
  265. return err
  266. }
  267. req.Header.Set("Accept", "application/pem-certificate-chain")
  268. res, err := c.doReqServer(ctx, req)
  269. if err != nil {
  270. return err
  271. }
  272. defer res.Body.Close()
  273. mimeType, params, err := mime.ParseMediaType(res.Header.Get("Content-Type"))
  274. if err != nil {
  275. return err
  276. }
  277. err = validateContentType(mimeType, params, "application/pem-certificate-chain")
  278. if err != nil {
  279. return err
  280. }
  281. b, err := ioutil.ReadAll(denet.LimitReader(res.Body, 512*1024))
  282. if err != nil {
  283. return err
  284. }
  285. cert.CertificateChain, err = acmeutils.LoadCertificates(b)
  286. if err != nil {
  287. return err
  288. }
  289. return nil
  290. }
  291. // This is a rather kludgy method needed for backwards compatibility with
  292. // old-ACME URLs. If it is not known whether an URL is to a certificate or an
  293. // order, this method can be used to load the URL. Returns with isCertificate
  294. // == true if the URL appears to address a certificate (in which case the
  295. // passed cert structure is populated), and isCertificate == false if the URL
  296. // appears to address an order (in which case the passed order structure is
  297. // populated). If the URL does not appear to address either type of resource,
  298. // an error is returned.
  299. func (c *RealmClient) LoadOrderOrCertificate(ctx context.Context, url string, order *Order, cert *Certificate) (isCertificate bool, err error) {
  300. // Check input.
  301. if !ValidURL(url) {
  302. err = fmt.Errorf("invalid request URL: %q", url)
  303. return
  304. }
  305. // Make request.
  306. req, err := http.NewRequest("GET", url, nil)
  307. if err != nil {
  308. return
  309. }
  310. req.Header.Set("Accept", "application/json, application/pem-certificate-chain")
  311. res, err := c.doReqServer(ctx, req)
  312. if err != nil {
  313. return
  314. }
  315. defer res.Body.Close()
  316. mimeType, params, err := mime.ParseMediaType(res.Header.Get("Content-Type"))
  317. if err != nil {
  318. return
  319. }
  320. err = validateContentType(mimeType, params, mimeType) // check params only
  321. if err != nil {
  322. return
  323. }
  324. switch mimeType {
  325. case "application/json":
  326. order.URL = url
  327. err = json.NewDecoder(res.Body).Decode(order)
  328. if err != nil {
  329. return
  330. }
  331. err = order.validate()
  332. if err != nil {
  333. return
  334. }
  335. order.retryAt = retryAtDefault(res.Header, defaultPollTime)
  336. return
  337. case "application/pem-certificate-chain":
  338. var b []byte
  339. b, err = ioutil.ReadAll(denet.LimitReader(res.Body, 512*1024))
  340. cert.URL = url
  341. cert.CertificateChain, err = acmeutils.LoadCertificates(b)
  342. isCertificate = true
  343. return
  344. }
  345. err = fmt.Errorf("response was not an order or certificate (unexpected content type %q)", mimeType)
  346. return
  347. }
  348. type finalizeReq struct {
  349. // Required. The CSR to be used for issuance.
  350. CSR denet.Base64up `json:"csr"`
  351. }
  352. // Finalize the order. This will only work if the order has the "ready" status.
  353. func (c *RealmClient) Finalize(ctx context.Context, acct *Account, order *Order, csr []byte) error {
  354. req := finalizeReq{
  355. CSR: csr,
  356. }
  357. _, err := c.doReq(ctx, "POST", order.FinalizeURL, acct, nil, &req, order)
  358. if err != nil {
  359. return err
  360. }
  361. return nil
  362. }
  363. type revokeReq struct {
  364. Certificate []byte `json:"certificate"`
  365. Reason int `json:"reason,omitempty"`
  366. }
  367. // Requests revocation of a certificate. The certificate must be provided in
  368. // DER form. If revocationKey is non-nil, the revocation request is signed with
  369. // the given key; otherwise, the request is signed with the account key.
  370. //
  371. // In general, you should expect to be able to revoke any certificate if a
  372. // request to do so is signed using that certificate's key. You should also
  373. // expect to be able to revoke a certificate if the request is signed with the
  374. // account key of the account for which the certificate was issued, or where
  375. // the request is signed with the account key of an account for which presently
  376. // valid authorizations are held for all DNS names on the certificate.
  377. //
  378. // The reason is a CRL reason code, or 0 if no explicit reason code is to be
  379. // given.
  380. func (c *RealmClient) Revoke(ctx context.Context, acct *Account, certificateDER []byte, revocationKey crypto.PrivateKey, reason int) error {
  381. di, err := c.getDirectory(ctx)
  382. if err != nil {
  383. return err
  384. }
  385. if di.RevokeCert == "" {
  386. return fmt.Errorf("endpoint does not support revocation")
  387. }
  388. req := &revokeReq{
  389. Certificate: certificateDER,
  390. Reason: reason,
  391. }
  392. res, err := c.doReq(ctx, "POST", di.RevokeCert, nil, revocationKey, req, nil)
  393. if err != nil {
  394. return err
  395. }
  396. defer res.Body.Close()
  397. return nil
  398. }
  399. func (ord *Order) validate() error {
  400. return nil
  401. }
  402. // Submit a challenge response. Only the challenge URL is required to be set in
  403. // the Challenge object. The account need only have the URL set.
  404. func (c *RealmClient) RespondToChallenge(ctx context.Context, acct *Account, ch *Challenge, response json.RawMessage) error {
  405. _, err := c.doReq(ctx, "POST", ch.URL, acct, nil, &response, ch)
  406. if err != nil {
  407. return err
  408. }
  409. return nil
  410. }
  411. // Submit a key change request. The acct specified is used to authorize the
  412. // change; the key for the account identified by acct.URL is changed from
  413. // acct.PrivateKey/acct.Key to the key specified by newKey.
  414. //
  415. // When this method returns nil error, the key has been successfully changed.
  416. // The acct object's Key and PrivateKey fields will also be changed to newKey.
  417. func (c *RealmClient) ChangeKey(ctx context.Context, acct *Account, newKey crypto.PrivateKey) error {
  418. panic("not yet implemented")
  419. }