| 
									
										
										
										
											2021-08-12 21:03:24 +02:00
										 |  |  | // 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 { | 
					
						
							| 
									
										
										
										
											2022-09-28 18:30:40 +01:00
										 |  |  | 	if _, err := c.Discover(ctx); err != nil { // required by c.accountKID | 
					
						
							|  |  |  | 		return err | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2021-08-12 21:03:24 +02:00
										 |  |  | 	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 equivalent to c.Register but for CAs implementing RFC 8555. | 
					
						
							|  |  |  | // It expects c.Discover to have already been called. | 
					
						
							|  |  |  | 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"` | 
					
						
							|  |  |  | 		ExternalAccountBinding *jsonWebSignature `json:"externalAccountBinding,omitempty"` | 
					
						
							|  |  |  | 	}{ | 
					
						
							|  |  |  | 		Contact: acct.Contact, | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	if c.dir.Terms != "" { | 
					
						
							|  |  |  | 		req.TermsAgreed = prompt(c.dir.Terms) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// set 'externalAccountBinding' field if requested | 
					
						
							|  |  |  | 	if acct.ExternalAccountBinding != nil { | 
					
						
							|  |  |  | 		eabJWS, err := c.encodeExternalAccountBinding(acct.ExternalAccountBinding) | 
					
						
							|  |  |  | 		if err != nil { | 
					
						
							|  |  |  | 			return nil, fmt.Errorf("acme: failed to encode external account binding: %v", err) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		req.ExternalAccountBinding = eabJWS | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	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. | 
					
						
							| 
									
										
										
										
											2022-05-02 14:05:18 +01:00
										 |  |  | 	c.KID = KeyID(a.URI) | 
					
						
							| 
									
										
										
										
											2021-08-12 21:03:24 +02:00
										 |  |  | 	if res.StatusCode == http.StatusOK { | 
					
						
							|  |  |  | 		return nil, ErrAccountAlreadyExists | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return a, nil | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // encodeExternalAccountBinding will encode an external account binding stanza | 
					
						
							|  |  |  | // as described in https://tools.ietf.org/html/rfc8555#section-7.3.4. | 
					
						
							|  |  |  | func (c *Client) encodeExternalAccountBinding(eab *ExternalAccountBinding) (*jsonWebSignature, error) { | 
					
						
							|  |  |  | 	jwk, err := jwkEncode(c.Key.Public()) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		return nil, err | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return jwsWithMAC(eab.Key, eab.KID, c.dir.RegURL, []byte(jwk)) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // updateRegRFC 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 | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-09-28 18:30:40 +01:00
										 |  |  | // accountKeyRollover attempts to perform account key rollover. | 
					
						
							|  |  |  | // On success it will change client.Key to the new key. | 
					
						
							|  |  |  | func (c *Client) accountKeyRollover(ctx context.Context, newKey crypto.Signer) error { | 
					
						
							|  |  |  | 	dir, err := c.Discover(ctx) // Also required by c.accountKID | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		return err | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	kid := c.accountKID(ctx) | 
					
						
							|  |  |  | 	if kid == noKeyID { | 
					
						
							|  |  |  | 		return ErrNoAccount | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	oldKey, err := jwkEncode(c.Key.Public()) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		return err | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	payload := struct { | 
					
						
							|  |  |  | 		Account string          `json:"account"` | 
					
						
							|  |  |  | 		OldKey  json.RawMessage `json:"oldKey"` | 
					
						
							|  |  |  | 	}{ | 
					
						
							|  |  |  | 		Account: string(kid), | 
					
						
							|  |  |  | 		OldKey:  json.RawMessage(oldKey), | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	inner, err := jwsEncodeJSON(payload, newKey, noKeyID, noNonce, dir.KeyChangeURL) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		return err | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	res, err := c.post(ctx, nil, dir.KeyChangeURL, base64.RawURLEncoding.EncodeToString(inner), wantStatus(http.StatusOK)) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		return err | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	defer res.Body.Close() | 
					
						
							|  |  |  | 	c.Key = newKey | 
					
						
							|  |  |  | 	return nil | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-08-12 21:03:24 +02:00
										 |  |  | // 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" | 
					
						
							|  |  |  | } | 
					
						
							| 
									
										
										
										
											2021-11-27 15:26:58 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  | // ListCertAlternates retrieves any alternate certificate chain URLs for the | 
					
						
							|  |  |  | // given certificate chain URL. These alternate URLs can be passed to FetchCert | 
					
						
							|  |  |  | // in order to retrieve the alternate certificate chains. | 
					
						
							|  |  |  | // | 
					
						
							|  |  |  | // If there are no alternate issuer certificate chains, a nil slice will be | 
					
						
							|  |  |  | // returned. | 
					
						
							|  |  |  | func (c *Client) ListCertAlternates(ctx context.Context, url string) ([]string, error) { | 
					
						
							|  |  |  | 	if _, err := c.Discover(ctx); err != nil { // required by c.accountKID | 
					
						
							|  |  |  | 		return nil, err | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	res, err := c.postAsGet(ctx, url, wantStatus(http.StatusOK)) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		return nil, err | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	defer res.Body.Close() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// We don't need the body but we need to discard it so we don't end up | 
					
						
							|  |  |  | 	// preventing keep-alive | 
					
						
							|  |  |  | 	if _, err := io.Copy(ioutil.Discard, res.Body); err != nil { | 
					
						
							|  |  |  | 		return nil, fmt.Errorf("acme: cert alternates response stream: %v", err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	alts := linkHeader(res.Header, "alternate") | 
					
						
							|  |  |  | 	return alts, nil | 
					
						
							|  |  |  | } |