ACMEv2 client library for Go.

api.go 17KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572
  1. // Package acmeapi provides an API for accessing ACME servers.
  2. //
  3. // See type RealmClient for introductory documentation.
  4. package acmeapi // import "git.devever.net/hlandau/acmeapi"
  5. import (
  6. "context"
  7. "crypto"
  8. "crypto/ecdsa"
  9. "crypto/rsa"
  10. "encoding/json"
  11. "errors"
  12. "fmt"
  13. gnet "git.devever.net/hlandau/goutils/net"
  14. "git.devever.net/hlandau/xlog"
  15. "github.com/peterhellberg/link"
  16. "golang.org/x/net/context/ctxhttp"
  17. "gopkg.in/square/go-jose.v2"
  18. "io"
  19. "mime"
  20. "net/http"
  21. "net/url"
  22. "runtime"
  23. "strings"
  24. "sync"
  25. "sync/atomic"
  26. "time"
  27. )
  28. var log, Log = xlog.NewQuiet("acmeapi")
  29. // Sentinel value for doReq.
  30. var noAccountNeeded = Account{}
  31. // Internal use only. All ACME URLs must use "https" and not "http". However,
  32. // for testing purposes, if this is set, "http" URLs will be allowed. This is useful
  33. // for testing when a test ACME server doesn't have TLS configured.
  34. var TestingAllowHTTP = false
  35. // You should set this to a string identifying the code invoking this library.
  36. // Optional.
  37. //
  38. // You can alternatively set the user agent on a per-Client basis via
  39. // Client.UserAgent, but usually a user agent is set at program scope and it
  40. // makes more sense to set it here.
  41. var UserAgent string
  42. // Returns true if the URL given is (potentially) a valid ACME resource URL.
  43. //
  44. // The URL must be an HTTPS URL.
  45. func ValidURL(u string) bool {
  46. ur, err := url.Parse(u)
  47. return err == nil && (ur.Scheme == "https" || (TestingAllowHTTP && ur.Scheme == "http"))
  48. }
  49. // Configuration data used to instantiate a RealmClient.
  50. type RealmClientConfig struct {
  51. // Optional but usually necessary. The Directory URL for the ACME realm (ACME
  52. // server). This must be an HTTPS URL. This will usually be provided via
  53. // out-of-band means; it is the root from which all other ACME resources are
  54. // accessed.
  55. //
  56. // Specifying the directory URL is usually necessary, but it can be omitted
  57. // in some cases; see the documentation for RealmClient.
  58. DirectoryURL string
  59. // Optional. HTTP client used to make HTTP requests. If this is nil, the
  60. // default net/http Client is used, which will suffice in the vast majority
  61. // of cases.
  62. HTTPClient *http.Client
  63. // Optional. Custom User-Agent string. If not specified, uses the global
  64. // User-Agent string configured at acmeapi package level (UserAgent var).
  65. UserAgent string
  66. }
  67. // Client used to access and mutate resources provided by an ACME server.
  68. //
  69. //
  70. // REALM TERMINOLOGY
  71. //
  72. // A “realm” means an ACME server, including all resources provided by it (e.g.
  73. // accounts, orders, nonces). A nonce can be used to issue a signed request
  74. // against a resource in a given realm if and only if it that nonce was issued
  75. // by the same realm. (This term is specific to this client, and not a general
  76. // ACME term. It is coined here to aid clarity in understanding the scope of
  77. // ACME resources.)
  78. //
  79. // You instantiate a RealmClient to consume the services of a realm. If you
  80. // want to consume the services of multiple ACME servers — that is, multiple
  81. // realms — you must create one RealmClient for each such realm. For example,
  82. // if you wanted to use both the ExampleCA Live ACME server (which issues live
  83. // certificates) and the ExampleCA Staging ACME server (which issues non-live
  84. // certificates), you would need to create one RealmClient for each, and make
  85. // any calls to the right RealmClient. Calling a method on the wrong
  86. // RealmClient will fail under most circumstances.
  87. //
  88. //
  89. // INSTANTIATION
  90. //
  91. // Call NewRealmClient to create a new RealmClient. When you create a RealmClient,
  92. // you begin by passing the realm's (ACME server's) directory URL as part of
  93. // the client configuration. This is the entrypoint for the consumption of the
  94. // services provided by an ACME server realm. See RealmClientConfig for details.
  95. //
  96. //
  97. // DIRECTORY AUTO-DISCOVERY
  98. //
  99. // It is possible to instantiate a RealmClient without passing a directory URL.
  100. // If you do this, it is still possible to access some resources, where their
  101. // particular URL is explicitly known. Moreover, a RealmClient which has no
  102. // particular directory URL configured will automatically ascertain the
  103. // appropriate directory URL when it (if ever) first loads a resource where the
  104. // response from the server states the directory URL for the realm of which
  105. // that resource is a member. Once this occurs, that RealmClient is thereafter
  106. // specific to that realm, and must not be used for other purposes.
  107. //
  108. // This directory auto-discovery mechanic is useful when you have an URL for a
  109. // specific resource of an ACME realm but don't know the directory URL or the
  110. // identity of the realm or any other information about the realm. This allows
  111. // e.g. a certificate to be revoked knowing only its URL and private key. (The
  112. // revocation endpoint is discoverable from the directory resource, which is
  113. // itself discoverable by a link provided at the certificate resource, which is
  114. // addressed via the certificate URL.)
  115. //
  116. //
  117. // CONCURRENCY
  118. //
  119. // All methods of RealmClient are concurrency-safe. This means you can make
  120. // multiple in-flight requests. This is useful, for example, when you create a
  121. // new order and wish to retrieve all the authorizations created as part of it,
  122. // which are referenced by URL and not serialized inline as part of the order
  123. // object.
  124. //
  125. //
  126. // STANDARD METHOD ARGUMENTS
  127. //
  128. // All methods which involve network I/O, or which may involve network I/O,
  129. // take a context, to facilitate timeouts and cancellations.
  130. //
  131. // All methods which involve making signed requests take an *Account argument.
  132. // This is used to provide the URL and private key for the account; the other
  133. // fields of *Account arguments are only used by methods which work directly
  134. // with account resources.
  135. //
  136. // The URL and PrivateKey fields of a provided *Account are mandatory in most cases.
  137. // They are optional only in the following cases:
  138. //
  139. // When calling UpsertAccount, the account URL may be omitted. (If the URL of
  140. // an existing account is not known, this method may (and must) be used to
  141. // discover the URL of the account before methods requiring an URL may be
  142. // called.)
  143. //
  144. // When calling Revoke, the account URL and account private key may be omitted,
  145. // but only if the revocation request is being authorized on the basis of
  146. // possession of the certificate's corresponding private key. All other
  147. // revocation requests require an account URL and account private key.
  148. type RealmClient struct {
  149. cfg RealmClientConfig
  150. directoryURLMutex sync.RWMutex // Protects cfg.DirectoryURL.
  151. nonceSource nonceSource
  152. dir atomic.Value // *directoryInfo
  153. dirMutex sync.Mutex // Ensures single flight for directory requests.
  154. }
  155. // Directory resource structure.
  156. type directoryInfo struct {
  157. NewNonce string `json:"newNonce"`
  158. NewAccount string `json:"newAccount"`
  159. NewOrder string `json:"newOrder"`
  160. NewAuthz string `json:"newAuthz"`
  161. RevokeCert string `json:"revokeCert"`
  162. KeyChange string `json:"keyChange"`
  163. Meta RealmMeta `json:"meta"`
  164. }
  165. // Metadata for a realm, retrieved from the directory resource.
  166. type RealmMeta struct {
  167. // (Sent by server; optional.) If the CA requires agreement to certain terms of
  168. // service, this is set to an URL for the terms of service document.
  169. TermsOfServiceURL string `json:"termsOfService,omitempty"`
  170. // (Sent by server; optional.) A website pertaining to the CA.
  171. WebsiteURL string `json:"website,omitempty"`
  172. // (Sent by server; optional.) List of domain names which the CA recognises as
  173. // referring to itself for the purposes of CAA record validation.
  174. CAAIdentities []string `json:"caaIdentities,omitempty"`
  175. // (Sent by server; optional.) As per specification.
  176. ExternalAccountRequired bool `json:"externalAccountRequired,omitempty"`
  177. }
  178. // Instantiates a new RealmClient.
  179. func NewRealmClient(cfg RealmClientConfig) (*RealmClient, error) {
  180. rc := &RealmClient{
  181. cfg: cfg,
  182. }
  183. if rc.cfg.DirectoryURL != "" && !ValidURL(rc.cfg.DirectoryURL) {
  184. return nil, fmt.Errorf("not a valid directory URL: %q", rc.cfg.DirectoryURL)
  185. }
  186. rc.nonceSource.GetNonceFunc = rc.obtainNewNonce
  187. return rc, nil
  188. }
  189. func (c *RealmClient) getDirectoryURL() string {
  190. c.directoryURLMutex.RLock()
  191. defer c.directoryURLMutex.RUnlock()
  192. return c.cfg.DirectoryURL
  193. }
  194. // Directory Retrieval
  195. // Returns the directory information for the realm accessed by the RealmClient.
  196. //
  197. // This may return instantly (if the directory information has already been
  198. // retrieved and cached), or may cause a request to be made to retrieve and
  199. // cache the information, hence the context argument.
  200. //
  201. // Multiple concurrent calls to getDirectory with no directory information
  202. // cached result only in a single request being made; all of the callers to
  203. // getDirectory wait for the single request.
  204. func (c *RealmClient) getDirectory(ctx context.Context) (*directoryInfo, error) {
  205. dir := c.getDirp()
  206. if dir != nil {
  207. return dir, nil
  208. }
  209. c.dirMutex.Lock()
  210. defer c.dirMutex.Unlock()
  211. if dir := c.getDirp(); dir != nil {
  212. return dir, nil
  213. }
  214. dir, err := c.getDirectoryActual(ctx)
  215. if err != nil {
  216. return nil, err
  217. }
  218. c.setDirp(dir)
  219. return dir, nil
  220. }
  221. func (c *RealmClient) getDirp() *directoryInfo {
  222. v, _ := c.dir.Load().(*directoryInfo)
  223. return v
  224. }
  225. func (c *RealmClient) setDirp(d *directoryInfo) {
  226. c.dir.Store(d)
  227. }
  228. // Error returned when directory URL was needed for an operation but it is unknown.
  229. var ErrUnknownDirectoryURL = errors.New("unable to retrieve directory because the directory URL is unknown")
  230. // Error returned if directory does not provide endpoints required by the specification.
  231. var ErrMissingEndpoints = errors.New("directory does not provide required endpoints")
  232. // Make actual request to retrieve directory.
  233. func (c *RealmClient) getDirectoryActual(ctx context.Context) (*directoryInfo, error) {
  234. directoryURL := c.getDirectoryURL()
  235. if directoryURL == "" {
  236. return nil, ErrUnknownDirectoryURL
  237. }
  238. var dir *directoryInfo
  239. _, err := c.doReq(ctx, "GET", directoryURL, nil, nil, nil, &dir)
  240. if err != nil {
  241. return nil, err
  242. }
  243. if !ValidURL(dir.NewNonce) || !ValidURL(dir.NewAccount) || !ValidURL(dir.NewOrder) {
  244. return nil, ErrMissingEndpoints
  245. }
  246. return dir, nil
  247. }
  248. // Returns the directory metadata for the realm.
  249. //
  250. // This method must be used to retrieve the realm's current Terms of Service
  251. // URI when calling UpsertAccount.
  252. func (c *RealmClient) GetMeta(ctx context.Context) (RealmMeta, error) {
  253. di, err := c.getDirectory(ctx)
  254. if err != nil {
  255. return RealmMeta{}, err
  256. }
  257. return di.Meta, nil
  258. }
  259. // This method is configured as the GetNewNonce function for the nonceSource
  260. // which constitutes part of the RealmClient. It is called if the nonceSource's
  261. // cache of nonces is empty, meaning that an HTTP request must be made to
  262. // retrieve a new nonce.
  263. func (c *RealmClient) obtainNewNonce(ctx context.Context) error {
  264. di, err := c.getDirectory(ctx)
  265. if err != nil {
  266. return err
  267. }
  268. // We don't need to cache the nonce explicitly; doReq automatically caches
  269. // any fresh nonces provided in a reply.
  270. res, err := c.doReq(ctx, "HEAD", di.NewNonce, nil, nil, nil, nil)
  271. if res != nil {
  272. res.Body.Close()
  273. }
  274. return err
  275. }
  276. // Request Methods
  277. // Makes an ACME request.
  278. //
  279. // method: HTTP method in uppercase.
  280. //
  281. // url: Absolute HTTPS URL.
  282. //
  283. // requestData: If non-nil, signed and sent as request body.
  284. //
  285. // responseData: If non-nil, response, if JSON, is unmarshalled into this.
  286. //
  287. // acct: Mandatory if requestData is non-nil. This is used to determine the
  288. // account URL, which is used for signing requests. If key is nil, acct.PrivateKey
  289. // is used to sign the request. If the request should be signed with an embedded JWK
  290. // rather than an URL account reference, pass the special sentinel value
  291. // &noAccountNeeded.
  292. //
  293. // key: Overrides the private key used; used instead of acct.PrivateKey if non-nil.
  294. // The HTTP response structure is returned; the state of the Body stream is undefined,
  295. // but need not be manually closed if err is non-nil or if responseData is non-nil.
  296. func (c *RealmClient) doReq(ctx context.Context, method, url string, acct *Account, key crypto.PrivateKey, requestData, responseData interface{}) (*http.Response, error) {
  297. backoff := gnet.Backoff{
  298. MaxTries: 20,
  299. InitialDelay: 100 * time.Millisecond,
  300. MaxDelay: 1 * time.Second,
  301. MaxDelayAfterTries: 4,
  302. Jitter: 0.10,
  303. }
  304. for {
  305. res, err := c.doReqOneTry(ctx, method, url, acct, key, requestData, responseData)
  306. if err == nil {
  307. return res, nil
  308. }
  309. // If the error is specifically a "bad nonce" error, we are supposed to
  310. // retry.
  311. if he, ok := err.(*HTTPError); ok && he.Problem != nil && he.Problem.Type == "urn:ietf:params:acme:error:badNonce" {
  312. if backoff.Sleep() {
  313. log.Debugf("retrying after bad nonce: %v\n", he)
  314. continue
  315. }
  316. }
  317. // Other error, return.
  318. return res, err
  319. }
  320. }
  321. func (c *RealmClient) doReqOneTry(ctx context.Context, method, url string, acct *Account, key crypto.PrivateKey, requestData, responseData interface{}) (*http.Response, error) {
  322. // Check input.
  323. if !ValidURL(url) {
  324. return nil, fmt.Errorf("invalid request URL: %q", url)
  325. }
  326. // Request marshalling and signing.
  327. var rdr io.Reader
  328. if requestData != nil {
  329. if acct == nil {
  330. return nil, fmt.Errorf("must provide account object when making signed requests")
  331. }
  332. if key == nil {
  333. key = acct.PrivateKey
  334. }
  335. if key == nil {
  336. return nil, fmt.Errorf("account key must be specified")
  337. }
  338. b, err := json.Marshal(requestData)
  339. if err != nil {
  340. return nil, err
  341. }
  342. kalg, err := algorithmFromKey(key)
  343. if err != nil {
  344. return nil, err
  345. }
  346. signKey := jose.SigningKey{
  347. Algorithm: kalg,
  348. Key: key,
  349. }
  350. extraHeaders := map[jose.HeaderKey]interface{}{
  351. "url": url,
  352. }
  353. useInlineKey := (acct == &noAccountNeeded)
  354. if !useInlineKey {
  355. accountURL := acct.URL
  356. if !ValidURL(accountURL) {
  357. return nil, fmt.Errorf("acct must have a valid URL, not %q", accountURL)
  358. }
  359. extraHeaders["kid"] = accountURL
  360. }
  361. signOptions := jose.SignerOptions{
  362. NonceSource: c.nonceSource.WithContext(ctx),
  363. EmbedJWK: useInlineKey,
  364. ExtraHeaders: extraHeaders,
  365. }
  366. signer, err := jose.NewSigner(signKey, &signOptions)
  367. if err != nil {
  368. return nil, err
  369. }
  370. sig, err := signer.Sign(b)
  371. if err != nil {
  372. return nil, err
  373. }
  374. s := sig.FullSerialize()
  375. if err != nil {
  376. return nil, err
  377. }
  378. rdr = strings.NewReader(s)
  379. }
  380. // Make request.
  381. req, err := http.NewRequest(method, url, rdr)
  382. if err != nil {
  383. return nil, err
  384. }
  385. req.Header.Set("Accept", "application/jose+json")
  386. if method != "GET" && method != "HEAD" {
  387. req.Header.Set("Content-Type", "application/jose+json")
  388. }
  389. res, err := c.doReqServer(ctx, req)
  390. if err != nil {
  391. return res, err
  392. }
  393. // Otherwise, if we are expecting response data, unmarshal into the provided
  394. // struct.
  395. if responseData != nil {
  396. defer res.Body.Close()
  397. mimeType, params, err := mime.ParseMediaType(res.Header.Get("Content-Type"))
  398. if err != nil {
  399. return res, err
  400. }
  401. err = validateContentType(mimeType, params, "application/json")
  402. if err != nil {
  403. return res, err
  404. }
  405. err = json.NewDecoder(res.Body).Decode(responseData)
  406. if err != nil {
  407. return res, err
  408. }
  409. }
  410. // Done.
  411. return res, nil
  412. }
  413. func validateContentType(mimeType string, params map[string]string, expectedMimeType string) error {
  414. if mimeType != expectedMimeType {
  415. return fmt.Errorf("unexpected response content type: %q", mimeType)
  416. }
  417. if ch, ok := params["charset"]; ok && ch != "" && strings.ToLower(ch) != "utf-8" {
  418. return fmt.Errorf("content type charset is not UTF-8: %q, %q", mimeType, ch)
  419. }
  420. return nil
  421. }
  422. // Make an HTTP request to an ACME endpoint.
  423. func (c *RealmClient) doReqServer(ctx context.Context, req *http.Request) (*http.Response, error) {
  424. res, err := c.doReqActual(ctx, req)
  425. if err != nil {
  426. return nil, err
  427. }
  428. // If the response includes a nonce, add it to our cache of nonces.
  429. if n := res.Header.Get("Replay-Nonce"); n != "" {
  430. c.nonceSource.AddNonce(n)
  431. }
  432. // Autodiscover directory URL if it we didn't prevously know it and it's
  433. // specified in the response.
  434. if c.getDirectoryURL() == "" {
  435. func() {
  436. c.directoryURLMutex.Lock()
  437. defer c.directoryURLMutex.Unlock()
  438. if c.cfg.DirectoryURL != "" {
  439. return
  440. }
  441. if link := link.ParseResponse(res)["index"]; link != nil && ValidURL(link.URI) {
  442. c.cfg.DirectoryURL = link.URI
  443. }
  444. }()
  445. }
  446. // If the response was an error, parse the response body as an error and return.
  447. if res.StatusCode >= 400 && res.StatusCode < 600 {
  448. defer res.Body.Close()
  449. return res, newHTTPError(res)
  450. }
  451. return res, err
  452. }
  453. // Make an HTTP request. This is used by doReq and can also be used for
  454. // non-ACME requests (e.g. OCSP).
  455. func (c *RealmClient) doReqActual(ctx context.Context, req *http.Request) (*http.Response, error) {
  456. req.Header.Set("User-Agent", formUserAgent(c.cfg.UserAgent))
  457. return ctxhttp.Do(ctx, c.cfg.HTTPClient, req)
  458. }
  459. func algorithmFromKey(key crypto.PrivateKey) (jose.SignatureAlgorithm, error) {
  460. switch v := key.(type) {
  461. case *rsa.PrivateKey:
  462. return jose.RS256, nil
  463. case *ecdsa.PrivateKey:
  464. name := v.Curve.Params().Name
  465. switch name {
  466. case "P-256":
  467. return jose.ES256, nil
  468. case "P-384":
  469. return jose.ES384, nil
  470. case "P-521":
  471. return jose.ES512, nil
  472. default:
  473. return "", fmt.Errorf("unsupported ECDSA curve: %s", name)
  474. }
  475. default:
  476. return "", fmt.Errorf("unsupported private key type: %T", key)
  477. }
  478. }
  479. func formUserAgent(userAgent string) string {
  480. if userAgent == "" {
  481. userAgent = UserAgent
  482. }
  483. if userAgent != "" {
  484. userAgent += " "
  485. }
  486. return fmt.Sprintf("%sacmeapi/2 Go-http-client/1.1 %s/%s", userAgent, runtime.GOOS, runtime.GOARCH)
  487. }