mirror of
				https://github.com/go-gitea/gitea
				synced 2025-11-04 05:18:25 +00:00 
			
		
		
		
	* use certmagic for more extensible/robust ACME cert handling * accept TOS based on config option Signed-off-by: Andrew Thornton <art27@cantab.net> Co-authored-by: zeripath <art27@cantab.net> Co-authored-by: Lauris BH <lauris@nix.lv>
		
			
				
	
	
		
			248 lines
		
	
	
		
			9.4 KiB
		
	
	
	
		
			Go
		
	
	
	
		
			Vendored
		
	
	
	
			
		
		
	
	
			248 lines
		
	
	
		
			9.4 KiB
		
	
	
	
		
			Go
		
	
	
	
		
			Vendored
		
	
	
	
// Copyright 2020 Matthew Holt
 | 
						|
//
 | 
						|
// Licensed under the Apache License, Version 2.0 (the "License");
 | 
						|
// you may not use this file except in compliance with the License.
 | 
						|
// You may obtain a copy of the License at
 | 
						|
//
 | 
						|
//     http://www.apache.org/licenses/LICENSE-2.0
 | 
						|
//
 | 
						|
// Unless required by applicable law or agreed to in writing, software
 | 
						|
// distributed under the License is distributed on an "AS IS" BASIS,
 | 
						|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
						|
// See the License for the specific language governing permissions and
 | 
						|
// limitations under the License.
 | 
						|
 | 
						|
package acme
 | 
						|
 | 
						|
import (
 | 
						|
	"context"
 | 
						|
	"encoding/base64"
 | 
						|
	"errors"
 | 
						|
	"fmt"
 | 
						|
	"time"
 | 
						|
)
 | 
						|
 | 
						|
// Order is an object that "represents a client's request for a certificate
 | 
						|
// and is used to track the progress of that order through to issuance.
 | 
						|
// Thus, the object contains information about the requested
 | 
						|
// certificate, the authorizations that the server requires the client
 | 
						|
// to complete, and any certificates that have resulted from this order."
 | 
						|
// §7.1.3
 | 
						|
type Order struct {
 | 
						|
	// status (required, string):  The status of this order.  Possible
 | 
						|
	// values are "pending", "ready", "processing", "valid", and
 | 
						|
	// "invalid".  See Section 7.1.6.
 | 
						|
	Status string `json:"status"`
 | 
						|
 | 
						|
	// expires (optional, string):  The timestamp after which the server
 | 
						|
	// will consider this order invalid, encoded in the format specified
 | 
						|
	// in [RFC3339].  This field is REQUIRED for objects with "pending"
 | 
						|
	// or "valid" in the status field.
 | 
						|
	Expires time.Time `json:"expires,omitempty"`
 | 
						|
 | 
						|
	// identifiers (required, array of object):  An array of identifier
 | 
						|
	// objects that the order pertains to.
 | 
						|
	Identifiers []Identifier `json:"identifiers"`
 | 
						|
 | 
						|
	// notBefore (optional, string):  The requested value of the notBefore
 | 
						|
	// field in the certificate, in the date format defined in [RFC3339].
 | 
						|
	NotBefore *time.Time `json:"notBefore,omitempty"`
 | 
						|
 | 
						|
	// notAfter (optional, string):  The requested value of the notAfter
 | 
						|
	// field in the certificate, in the date format defined in [RFC3339].
 | 
						|
	NotAfter *time.Time `json:"notAfter,omitempty"`
 | 
						|
 | 
						|
	// error (optional, object):  The error that occurred while processing
 | 
						|
	// the order, if any.  This field is structured as a problem document
 | 
						|
	// [RFC7807].
 | 
						|
	Error *Problem `json:"error,omitempty"`
 | 
						|
 | 
						|
	// authorizations (required, array of string):  For pending orders, the
 | 
						|
	// authorizations that the client needs to complete before the
 | 
						|
	// requested certificate can be issued (see Section 7.5), including
 | 
						|
	// unexpired authorizations that the client has completed in the past
 | 
						|
	// for identifiers specified in the order.  The authorizations
 | 
						|
	// required are dictated by server policy; there may not be a 1:1
 | 
						|
	// relationship between the order identifiers and the authorizations
 | 
						|
	// required.  For final orders (in the "valid" or "invalid" state),
 | 
						|
	// the authorizations that were completed.  Each entry is a URL from
 | 
						|
	// which an authorization can be fetched with a POST-as-GET request.
 | 
						|
	Authorizations []string `json:"authorizations"`
 | 
						|
 | 
						|
	// finalize (required, string):  A URL that a CSR must be POSTed to once
 | 
						|
	// all of the order's authorizations are satisfied to finalize the
 | 
						|
	// order.  The result of a successful finalization will be the
 | 
						|
	// population of the certificate URL for the order.
 | 
						|
	Finalize string `json:"finalize"`
 | 
						|
 | 
						|
	// certificate (optional, string):  A URL for the certificate that has
 | 
						|
	// been issued in response to this order.
 | 
						|
	Certificate string `json:"certificate"`
 | 
						|
 | 
						|
	// Similar to new-account, the server returns a 201 response with
 | 
						|
	// the URL to the order object in the Location header.
 | 
						|
	//
 | 
						|
	// We transfer the value from the header to this field for
 | 
						|
	// storage and recall purposes.
 | 
						|
	Location string `json:"-"`
 | 
						|
}
 | 
						|
 | 
						|
// Identifier is used in order and authorization (authz) objects.
 | 
						|
type Identifier struct {
 | 
						|
	// type (required, string):  The type of identifier.  This document
 | 
						|
	// defines the "dns" identifier type.  See the registry defined in
 | 
						|
	// Section 9.7.7 for any others.
 | 
						|
	Type string `json:"type"`
 | 
						|
 | 
						|
	// value (required, string):  The identifier itself.
 | 
						|
	Value string `json:"value"`
 | 
						|
}
 | 
						|
 | 
						|
// NewOrder creates a new order with the server.
 | 
						|
//
 | 
						|
// "The client begins the certificate issuance process by sending a POST
 | 
						|
// request to the server's newOrder resource." §7.4
 | 
						|
func (c *Client) NewOrder(ctx context.Context, account Account, order Order) (Order, error) {
 | 
						|
	if err := c.provision(ctx); err != nil {
 | 
						|
		return order, err
 | 
						|
	}
 | 
						|
	resp, err := c.httpPostJWS(ctx, account.PrivateKey, account.Location, c.dir.NewOrder, order, &order)
 | 
						|
	if err != nil {
 | 
						|
		return order, err
 | 
						|
	}
 | 
						|
	order.Location = resp.Header.Get("Location")
 | 
						|
	return order, nil
 | 
						|
}
 | 
						|
 | 
						|
// FinalizeOrder finalizes the order with the server and polls under the server has
 | 
						|
// updated the order status. The CSR must be in ASN.1 DER-encoded format. If this
 | 
						|
// succeeds, the certificate is ready to download once this returns.
 | 
						|
//
 | 
						|
// "Once the client believes it has fulfilled the server's requirements,
 | 
						|
// it should send a POST request to the order resource's finalize URL." §7.4
 | 
						|
func (c *Client) FinalizeOrder(ctx context.Context, account Account, order Order, csrASN1DER []byte) (Order, error) {
 | 
						|
	if err := c.provision(ctx); err != nil {
 | 
						|
		return order, err
 | 
						|
	}
 | 
						|
 | 
						|
	body := struct {
 | 
						|
		// csr (required, string):  A CSR encoding the parameters for the
 | 
						|
		// certificate being requested [RFC2986].  The CSR is sent in the
 | 
						|
		// base64url-encoded version of the DER format.  (Note: Because this
 | 
						|
		// field uses base64url, and does not include headers, it is
 | 
						|
		// different from PEM.) §7.4
 | 
						|
		CSR string `json:"csr"`
 | 
						|
	}{
 | 
						|
		CSR: base64.RawURLEncoding.EncodeToString(csrASN1DER),
 | 
						|
	}
 | 
						|
 | 
						|
	resp, err := c.httpPostJWS(ctx, account.PrivateKey, account.Location, order.Finalize, body, &order)
 | 
						|
	if err != nil {
 | 
						|
		// "A request to finalize an order will result in error if the order is
 | 
						|
		// not in the 'ready' state.  In such cases, the server MUST return a
 | 
						|
		// 403 (Forbidden) error with a problem document of type
 | 
						|
		// 'orderNotReady'.  The client should then send a POST-as-GET request
 | 
						|
		// to the order resource to obtain its current state.  The status of the
 | 
						|
		// order will indicate what action the client should take (see below)."
 | 
						|
		// §7.4
 | 
						|
		var problem Problem
 | 
						|
		if errors.As(err, &problem) {
 | 
						|
			if problem.Type != ProblemTypeOrderNotReady {
 | 
						|
				return order, err
 | 
						|
			}
 | 
						|
		} else {
 | 
						|
			return order, err
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	// unlike with accounts and authorizations, the spec isn't clear on whether
 | 
						|
	// the server MUST set this on finalizing the order, but their example shows a
 | 
						|
	// Location header, so I guess if it's set in the response, we should keep it
 | 
						|
	if newLocation := resp.Header.Get("Location"); newLocation != "" {
 | 
						|
		order.Location = newLocation
 | 
						|
	}
 | 
						|
 | 
						|
	if finished, err := orderIsFinished(order); finished {
 | 
						|
		return order, err
 | 
						|
	}
 | 
						|
 | 
						|
	// TODO: "The elements of the "authorizations" and "identifiers" arrays are
 | 
						|
	// immutable once set. If a client observes a change
 | 
						|
	// in the contents of either array, then it SHOULD consider the order
 | 
						|
	// invalid."
 | 
						|
 | 
						|
	maxDuration := c.pollTimeout()
 | 
						|
	start := time.Now()
 | 
						|
	for time.Since(start) < maxDuration {
 | 
						|
		// querying an order is expensive on the server-side, so we
 | 
						|
		// shouldn't do it too frequently; honor server preference
 | 
						|
		interval, err := retryAfter(resp, c.pollInterval())
 | 
						|
		if err != nil {
 | 
						|
			return order, err
 | 
						|
		}
 | 
						|
		select {
 | 
						|
		case <-time.After(interval):
 | 
						|
		case <-ctx.Done():
 | 
						|
			return order, ctx.Err()
 | 
						|
		}
 | 
						|
 | 
						|
		resp, err = c.httpPostJWS(ctx, account.PrivateKey, account.Location, order.Location, nil, &order)
 | 
						|
		if err != nil {
 | 
						|
			return order, fmt.Errorf("polling order status: %w", err)
 | 
						|
		}
 | 
						|
 | 
						|
		// (same reasoning as above)
 | 
						|
		if newLocation := resp.Header.Get("Location"); newLocation != "" {
 | 
						|
			order.Location = newLocation
 | 
						|
		}
 | 
						|
 | 
						|
		if finished, err := orderIsFinished(order); finished {
 | 
						|
			return order, err
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	return order, fmt.Errorf("order took too long")
 | 
						|
}
 | 
						|
 | 
						|
// orderIsFinished returns true if the order processing is complete,
 | 
						|
// regardless of success or failure. If this function returns true,
 | 
						|
// polling an order status should stop. If there is an error with the
 | 
						|
// order, an error will be returned. This function should be called
 | 
						|
// only after a request to finalize an order. See §7.4.
 | 
						|
func orderIsFinished(order Order) (bool, error) {
 | 
						|
	switch order.Status {
 | 
						|
	case StatusInvalid:
 | 
						|
		// "invalid": The certificate will not be issued.  Consider this
 | 
						|
		//      order process abandoned.
 | 
						|
		return true, fmt.Errorf("final order is invalid: %w", order.Error)
 | 
						|
 | 
						|
	case StatusPending:
 | 
						|
		// "pending": The server does not believe that the client has
 | 
						|
		//      fulfilled the requirements.  Check the "authorizations" array for
 | 
						|
		//      entries that are still pending.
 | 
						|
		return true, fmt.Errorf("order pending, authorizations remaining: %v", order.Authorizations)
 | 
						|
 | 
						|
	case StatusReady:
 | 
						|
		// "ready": The server agrees that the requirements have been
 | 
						|
		//      fulfilled, and is awaiting finalization.  Submit a finalization
 | 
						|
		//      request.
 | 
						|
		// (we did just submit a finalization request, so this is an error)
 | 
						|
		return true, fmt.Errorf("unexpected state: %s - order already finalized", order.Status)
 | 
						|
 | 
						|
	case StatusProcessing:
 | 
						|
		// "processing": The certificate is being issued.  Send a GET request
 | 
						|
		//      after the time given in the "Retry-After" header field of the
 | 
						|
		//      response, if any.
 | 
						|
		return false, nil
 | 
						|
 | 
						|
	case StatusValid:
 | 
						|
		// "valid": The server has issued the certificate and provisioned its
 | 
						|
		//      URL to the "certificate" field of the order.  Download the
 | 
						|
		//      certificate.
 | 
						|
		return true, nil
 | 
						|
 | 
						|
	default:
 | 
						|
		return true, fmt.Errorf("unrecognized order status: %s", order.Status)
 | 
						|
	}
 | 
						|
}
 |