aboutsummaryrefslogtreecommitdiffhomepage
path: root/vendor/golang.org/x/crypto/acme/rfc8555.go
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/golang.org/x/crypto/acme/rfc8555.go')
-rw-r--r--vendor/golang.org/x/crypto/acme/rfc8555.go392
1 files changed, 392 insertions, 0 deletions
diff --git a/vendor/golang.org/x/crypto/acme/rfc8555.go b/vendor/golang.org/x/crypto/acme/rfc8555.go
new file mode 100644
index 0000000..dfb57a6
--- /dev/null
+++ b/vendor/golang.org/x/crypto/acme/rfc8555.go
@@ -0,0 +1,392 @@
+// Copyright 2019 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package acme
+
+import (
+ "context"
+ "crypto"
+ "encoding/base64"
+ "encoding/json"
+ "encoding/pem"
+ "errors"
+ "fmt"
+ "io"
+ "io/ioutil"
+ "net/http"
+ "time"
+)
+
+// DeactivateReg permanently disables an existing account associated with c.Key.
+// A deactivated account can no longer request certificate issuance or access
+// resources related to the account, such as orders or authorizations.
+//
+// It only works with CAs implementing RFC 8555.
+func (c *Client) DeactivateReg(ctx context.Context) error {
+ url := string(c.accountKID(ctx))
+ if url == "" {
+ return ErrNoAccount
+ }
+ req := json.RawMessage(`{"status": "deactivated"}`)
+ res, err := c.post(ctx, nil, url, req, wantStatus(http.StatusOK))
+ if err != nil {
+ return err
+ }
+ res.Body.Close()
+ return nil
+}
+
+// registerRFC is quivalent to c.Register but for CAs implementing RFC 8555.
+// It expects c.Discover to have already been called.
+// TODO: Implement externalAccountBinding.
+func (c *Client) registerRFC(ctx context.Context, acct *Account, prompt func(tosURL string) bool) (*Account, error) {
+ c.cacheMu.Lock() // guard c.kid access
+ defer c.cacheMu.Unlock()
+
+ req := struct {
+ TermsAgreed bool `json:"termsOfServiceAgreed,omitempty"`
+ Contact []string `json:"contact,omitempty"`
+ }{
+ Contact: acct.Contact,
+ }
+ if c.dir.Terms != "" {
+ req.TermsAgreed = prompt(c.dir.Terms)
+ }
+ res, err := c.post(ctx, c.Key, c.dir.RegURL, req, wantStatus(
+ http.StatusOK, // account with this key already registered
+ http.StatusCreated, // new account created
+ ))
+ if err != nil {
+ return nil, err
+ }
+
+ defer res.Body.Close()
+ a, err := responseAccount(res)
+ if err != nil {
+ return nil, err
+ }
+ // Cache Account URL even if we return an error to the caller.
+ // It is by all means a valid and usable "kid" value for future requests.
+ c.kid = keyID(a.URI)
+ if res.StatusCode == http.StatusOK {
+ return nil, ErrAccountAlreadyExists
+ }
+ return a, nil
+}
+
+// updateGegRFC is equivalent to c.UpdateReg but for CAs implementing RFC 8555.
+// It expects c.Discover to have already been called.
+func (c *Client) updateRegRFC(ctx context.Context, a *Account) (*Account, error) {
+ url := string(c.accountKID(ctx))
+ if url == "" {
+ return nil, ErrNoAccount
+ }
+ req := struct {
+ Contact []string `json:"contact,omitempty"`
+ }{
+ Contact: a.Contact,
+ }
+ res, err := c.post(ctx, nil, url, req, wantStatus(http.StatusOK))
+ if err != nil {
+ return nil, err
+ }
+ defer res.Body.Close()
+ return responseAccount(res)
+}
+
+// getGegRFC is equivalent to c.GetReg but for CAs implementing RFC 8555.
+// It expects c.Discover to have already been called.
+func (c *Client) getRegRFC(ctx context.Context) (*Account, error) {
+ req := json.RawMessage(`{"onlyReturnExisting": true}`)
+ res, err := c.post(ctx, c.Key, c.dir.RegURL, req, wantStatus(http.StatusOK))
+ if e, ok := err.(*Error); ok && e.ProblemType == "urn:ietf:params:acme:error:accountDoesNotExist" {
+ return nil, ErrNoAccount
+ }
+ if err != nil {
+ return nil, err
+ }
+
+ defer res.Body.Close()
+ return responseAccount(res)
+}
+
+func responseAccount(res *http.Response) (*Account, error) {
+ var v struct {
+ Status string
+ Contact []string
+ Orders string
+ }
+ if err := json.NewDecoder(res.Body).Decode(&v); err != nil {
+ return nil, fmt.Errorf("acme: invalid account response: %v", err)
+ }
+ return &Account{
+ URI: res.Header.Get("Location"),
+ Status: v.Status,
+ Contact: v.Contact,
+ OrdersURL: v.Orders,
+ }, nil
+}
+
+// AuthorizeOrder initiates the order-based application for certificate issuance,
+// as opposed to pre-authorization in Authorize.
+// It is only supported by CAs implementing RFC 8555.
+//
+// The caller then needs to fetch each authorization with GetAuthorization,
+// identify those with StatusPending status and fulfill a challenge using Accept.
+// Once all authorizations are satisfied, the caller will typically want to poll
+// order status using WaitOrder until it's in StatusReady state.
+// To finalize the order and obtain a certificate, the caller submits a CSR with CreateOrderCert.
+func (c *Client) AuthorizeOrder(ctx context.Context, id []AuthzID, opt ...OrderOption) (*Order, error) {
+ dir, err := c.Discover(ctx)
+ if err != nil {
+ return nil, err
+ }
+
+ req := struct {
+ Identifiers []wireAuthzID `json:"identifiers"`
+ NotBefore string `json:"notBefore,omitempty"`
+ NotAfter string `json:"notAfter,omitempty"`
+ }{}
+ for _, v := range id {
+ req.Identifiers = append(req.Identifiers, wireAuthzID{
+ Type: v.Type,
+ Value: v.Value,
+ })
+ }
+ for _, o := range opt {
+ switch o := o.(type) {
+ case orderNotBeforeOpt:
+ req.NotBefore = time.Time(o).Format(time.RFC3339)
+ case orderNotAfterOpt:
+ req.NotAfter = time.Time(o).Format(time.RFC3339)
+ default:
+ // Package's fault if we let this happen.
+ panic(fmt.Sprintf("unsupported order option type %T", o))
+ }
+ }
+
+ res, err := c.post(ctx, nil, dir.OrderURL, req, wantStatus(http.StatusCreated))
+ if err != nil {
+ return nil, err
+ }
+ defer res.Body.Close()
+ return responseOrder(res)
+}
+
+// GetOrder retrives an order identified by the given URL.
+// For orders created with AuthorizeOrder, the url value is Order.URI.
+//
+// If a caller needs to poll an order until its status is final,
+// see the WaitOrder method.
+func (c *Client) GetOrder(ctx context.Context, url string) (*Order, error) {
+ if _, err := c.Discover(ctx); err != nil {
+ return nil, err
+ }
+
+ res, err := c.postAsGet(ctx, url, wantStatus(http.StatusOK))
+ if err != nil {
+ return nil, err
+ }
+ defer res.Body.Close()
+ return responseOrder(res)
+}
+
+// WaitOrder polls an order from the given URL until it is in one of the final states,
+// StatusReady, StatusValid or StatusInvalid, the CA responded with a non-retryable error
+// or the context is done.
+//
+// It returns a non-nil Order only if its Status is StatusReady or StatusValid.
+// In all other cases WaitOrder returns an error.
+// If the Status is StatusInvalid, the returned error is of type *OrderError.
+func (c *Client) WaitOrder(ctx context.Context, url string) (*Order, error) {
+ if _, err := c.Discover(ctx); err != nil {
+ return nil, err
+ }
+ for {
+ res, err := c.postAsGet(ctx, url, wantStatus(http.StatusOK))
+ if err != nil {
+ return nil, err
+ }
+ o, err := responseOrder(res)
+ res.Body.Close()
+ switch {
+ case err != nil:
+ // Skip and retry.
+ case o.Status == StatusInvalid:
+ return nil, &OrderError{OrderURL: o.URI, Status: o.Status}
+ case o.Status == StatusReady || o.Status == StatusValid:
+ return o, nil
+ }
+
+ d := retryAfter(res.Header.Get("Retry-After"))
+ if d == 0 {
+ // Default retry-after.
+ // Same reasoning as in WaitAuthorization.
+ d = time.Second
+ }
+ t := time.NewTimer(d)
+ select {
+ case <-ctx.Done():
+ t.Stop()
+ return nil, ctx.Err()
+ case <-t.C:
+ // Retry.
+ }
+ }
+}
+
+func responseOrder(res *http.Response) (*Order, error) {
+ var v struct {
+ Status string
+ Expires time.Time
+ Identifiers []wireAuthzID
+ NotBefore time.Time
+ NotAfter time.Time
+ Error *wireError
+ Authorizations []string
+ Finalize string
+ Certificate string
+ }
+ if err := json.NewDecoder(res.Body).Decode(&v); err != nil {
+ return nil, fmt.Errorf("acme: error reading order: %v", err)
+ }
+ o := &Order{
+ URI: res.Header.Get("Location"),
+ Status: v.Status,
+ Expires: v.Expires,
+ NotBefore: v.NotBefore,
+ NotAfter: v.NotAfter,
+ AuthzURLs: v.Authorizations,
+ FinalizeURL: v.Finalize,
+ CertURL: v.Certificate,
+ }
+ for _, id := range v.Identifiers {
+ o.Identifiers = append(o.Identifiers, AuthzID{Type: id.Type, Value: id.Value})
+ }
+ if v.Error != nil {
+ o.Error = v.Error.error(nil /* headers */)
+ }
+ return o, nil
+}
+
+// CreateOrderCert submits the CSR (Certificate Signing Request) to a CA at the specified URL.
+// The URL is the FinalizeURL field of an Order created with AuthorizeOrder.
+//
+// If the bundle argument is true, the returned value also contain the CA (issuer)
+// certificate chain. Otherwise, only a leaf certificate is returned.
+// The returned URL can be used to re-fetch the certificate using FetchCert.
+//
+// This method is only supported by CAs implementing RFC 8555. See CreateCert for pre-RFC CAs.
+//
+// CreateOrderCert returns an error if the CA's response is unreasonably large.
+// Callers are encouraged to parse the returned value to ensure the certificate is valid and has the expected features.
+func (c *Client) CreateOrderCert(ctx context.Context, url string, csr []byte, bundle bool) (der [][]byte, certURL string, err error) {
+ if _, err := c.Discover(ctx); err != nil { // required by c.accountKID
+ return nil, "", err
+ }
+
+ // RFC describes this as "finalize order" request.
+ req := struct {
+ CSR string `json:"csr"`
+ }{
+ CSR: base64.RawURLEncoding.EncodeToString(csr),
+ }
+ res, err := c.post(ctx, nil, url, req, wantStatus(http.StatusOK))
+ if err != nil {
+ return nil, "", err
+ }
+ defer res.Body.Close()
+ o, err := responseOrder(res)
+ if err != nil {
+ return nil, "", err
+ }
+
+ // Wait for CA to issue the cert if they haven't.
+ if o.Status != StatusValid {
+ o, err = c.WaitOrder(ctx, o.URI)
+ }
+ if err != nil {
+ return nil, "", err
+ }
+ // The only acceptable status post finalize and WaitOrder is "valid".
+ if o.Status != StatusValid {
+ return nil, "", &OrderError{OrderURL: o.URI, Status: o.Status}
+ }
+ crt, err := c.fetchCertRFC(ctx, o.CertURL, bundle)
+ return crt, o.CertURL, err
+}
+
+// fetchCertRFC downloads issued certificate from the given URL.
+// It expects the CA to respond with PEM-encoded certificate chain.
+//
+// The URL argument is the CertURL field of Order.
+func (c *Client) fetchCertRFC(ctx context.Context, url string, bundle bool) ([][]byte, error) {
+ res, err := c.postAsGet(ctx, url, wantStatus(http.StatusOK))
+ if err != nil {
+ return nil, err
+ }
+ defer res.Body.Close()
+
+ // Get all the bytes up to a sane maximum.
+ // Account very roughly for base64 overhead.
+ const max = maxCertChainSize + maxCertChainSize/33
+ b, err := ioutil.ReadAll(io.LimitReader(res.Body, max+1))
+ if err != nil {
+ return nil, fmt.Errorf("acme: fetch cert response stream: %v", err)
+ }
+ if len(b) > max {
+ return nil, errors.New("acme: certificate chain is too big")
+ }
+
+ // Decode PEM chain.
+ var chain [][]byte
+ for {
+ var p *pem.Block
+ p, b = pem.Decode(b)
+ if p == nil {
+ break
+ }
+ if p.Type != "CERTIFICATE" {
+ return nil, fmt.Errorf("acme: invalid PEM cert type %q", p.Type)
+ }
+
+ chain = append(chain, p.Bytes)
+ if !bundle {
+ return chain, nil
+ }
+ if len(chain) > maxChainLen {
+ return nil, errors.New("acme: certificate chain is too long")
+ }
+ }
+ if len(chain) == 0 {
+ return nil, errors.New("acme: certificate chain is empty")
+ }
+ return chain, nil
+}
+
+// sends a cert revocation request in either JWK form when key is non-nil or KID form otherwise.
+func (c *Client) revokeCertRFC(ctx context.Context, key crypto.Signer, cert []byte, reason CRLReasonCode) error {
+ req := &struct {
+ Cert string `json:"certificate"`
+ Reason int `json:"reason"`
+ }{
+ Cert: base64.RawURLEncoding.EncodeToString(cert),
+ Reason: int(reason),
+ }
+ res, err := c.post(ctx, key, c.dir.RevokeURL, req, wantStatus(http.StatusOK))
+ if err != nil {
+ if isAlreadyRevoked(err) {
+ // Assume it is not an error to revoke an already revoked cert.
+ return nil
+ }
+ return err
+ }
+ defer res.Body.Close()
+ return nil
+}
+
+func isAlreadyRevoked(err error) bool {
+ e, ok := err.(*Error)
+ return ok && e.ProblemType == "urn:ietf:params:acme:error:alreadyRevoked"
+}