mirror of
https://codeberg.org/forgejo/forgejo.git
synced 2024-08-26 22:47:51 +02:00
548 lines
17 KiB
Go
548 lines
17 KiB
Go
// Copyright 2016 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 (
|
|
"crypto"
|
|
"crypto/x509"
|
|
"errors"
|
|
"fmt"
|
|
"net/http"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
// ACME status values of Account, Order, Authorization and Challenge objects.
|
|
// See https://tools.ietf.org/html/rfc8555#section-7.1.6 for details.
|
|
const (
|
|
StatusDeactivated = "deactivated"
|
|
StatusExpired = "expired"
|
|
StatusInvalid = "invalid"
|
|
StatusPending = "pending"
|
|
StatusProcessing = "processing"
|
|
StatusReady = "ready"
|
|
StatusRevoked = "revoked"
|
|
StatusUnknown = "unknown"
|
|
StatusValid = "valid"
|
|
)
|
|
|
|
// CRLReasonCode identifies the reason for a certificate revocation.
|
|
type CRLReasonCode int
|
|
|
|
// CRL reason codes as defined in RFC 5280.
|
|
const (
|
|
CRLReasonUnspecified CRLReasonCode = 0
|
|
CRLReasonKeyCompromise CRLReasonCode = 1
|
|
CRLReasonCACompromise CRLReasonCode = 2
|
|
CRLReasonAffiliationChanged CRLReasonCode = 3
|
|
CRLReasonSuperseded CRLReasonCode = 4
|
|
CRLReasonCessationOfOperation CRLReasonCode = 5
|
|
CRLReasonCertificateHold CRLReasonCode = 6
|
|
CRLReasonRemoveFromCRL CRLReasonCode = 8
|
|
CRLReasonPrivilegeWithdrawn CRLReasonCode = 9
|
|
CRLReasonAACompromise CRLReasonCode = 10
|
|
)
|
|
|
|
var (
|
|
// ErrUnsupportedKey is returned when an unsupported key type is encountered.
|
|
ErrUnsupportedKey = errors.New("acme: unknown key type; only RSA and ECDSA are supported")
|
|
|
|
// ErrAccountAlreadyExists indicates that the Client's key has already been registered
|
|
// with the CA. It is returned by Register method.
|
|
ErrAccountAlreadyExists = errors.New("acme: account already exists")
|
|
|
|
// ErrNoAccount indicates that the Client's key has not been registered with the CA.
|
|
ErrNoAccount = errors.New("acme: account does not exist")
|
|
)
|
|
|
|
// Error is an ACME error, defined in Problem Details for HTTP APIs doc
|
|
// http://tools.ietf.org/html/draft-ietf-appsawg-http-problem.
|
|
type Error struct {
|
|
// StatusCode is The HTTP status code generated by the origin server.
|
|
StatusCode int
|
|
// ProblemType is a URI reference that identifies the problem type,
|
|
// typically in a "urn:acme:error:xxx" form.
|
|
ProblemType string
|
|
// Detail is a human-readable explanation specific to this occurrence of the problem.
|
|
Detail string
|
|
// Instance indicates a URL that the client should direct a human user to visit
|
|
// in order for instructions on how to agree to the updated Terms of Service.
|
|
// In such an event CA sets StatusCode to 403, ProblemType to
|
|
// "urn:ietf:params:acme:error:userActionRequired" and a Link header with relation
|
|
// "terms-of-service" containing the latest TOS URL.
|
|
Instance string
|
|
// Header is the original server error response headers.
|
|
// It may be nil.
|
|
Header http.Header
|
|
}
|
|
|
|
func (e *Error) Error() string {
|
|
return fmt.Sprintf("%d %s: %s", e.StatusCode, e.ProblemType, e.Detail)
|
|
}
|
|
|
|
// AuthorizationError indicates that an authorization for an identifier
|
|
// did not succeed.
|
|
// It contains all errors from Challenge items of the failed Authorization.
|
|
type AuthorizationError struct {
|
|
// URI uniquely identifies the failed Authorization.
|
|
URI string
|
|
|
|
// Identifier is an AuthzID.Value of the failed Authorization.
|
|
Identifier string
|
|
|
|
// Errors is a collection of non-nil error values of Challenge items
|
|
// of the failed Authorization.
|
|
Errors []error
|
|
}
|
|
|
|
func (a *AuthorizationError) Error() string {
|
|
e := make([]string, len(a.Errors))
|
|
for i, err := range a.Errors {
|
|
e[i] = err.Error()
|
|
}
|
|
return fmt.Sprintf("acme: authorization error for %s: %s", a.Identifier, strings.Join(e, "; "))
|
|
}
|
|
|
|
// OrderError is returned from Client's order related methods.
|
|
// It indicates the order is unusable and the clients should start over with
|
|
// AuthorizeOrder.
|
|
//
|
|
// The clients can still fetch the order object from CA using GetOrder
|
|
// to inspect its state.
|
|
type OrderError struct {
|
|
OrderURL string
|
|
Status string
|
|
}
|
|
|
|
func (oe *OrderError) Error() string {
|
|
return fmt.Sprintf("acme: order %s status: %s", oe.OrderURL, oe.Status)
|
|
}
|
|
|
|
// RateLimit reports whether err represents a rate limit error and
|
|
// any Retry-After duration returned by the server.
|
|
//
|
|
// See the following for more details on rate limiting:
|
|
// https://tools.ietf.org/html/draft-ietf-acme-acme-05#section-5.6
|
|
func RateLimit(err error) (time.Duration, bool) {
|
|
e, ok := err.(*Error)
|
|
if !ok {
|
|
return 0, false
|
|
}
|
|
// Some CA implementations may return incorrect values.
|
|
// Use case-insensitive comparison.
|
|
if !strings.HasSuffix(strings.ToLower(e.ProblemType), ":ratelimited") {
|
|
return 0, false
|
|
}
|
|
if e.Header == nil {
|
|
return 0, true
|
|
}
|
|
return retryAfter(e.Header.Get("Retry-After")), true
|
|
}
|
|
|
|
// Account is a user account. It is associated with a private key.
|
|
// Non-RFC 8555 fields are empty when interfacing with a compliant CA.
|
|
type Account struct {
|
|
// URI is the account unique ID, which is also a URL used to retrieve
|
|
// account data from the CA.
|
|
// When interfacing with RFC 8555-compliant CAs, URI is the "kid" field
|
|
// value in JWS signed requests.
|
|
URI string
|
|
|
|
// Contact is a slice of contact info used during registration.
|
|
// See https://tools.ietf.org/html/rfc8555#section-7.3 for supported
|
|
// formats.
|
|
Contact []string
|
|
|
|
// Status indicates current account status as returned by the CA.
|
|
// Possible values are StatusValid, StatusDeactivated, and StatusRevoked.
|
|
Status string
|
|
|
|
// OrdersURL is a URL from which a list of orders submitted by this account
|
|
// can be fetched.
|
|
OrdersURL string
|
|
|
|
// The terms user has agreed to.
|
|
// A value not matching CurrentTerms indicates that the user hasn't agreed
|
|
// to the actual Terms of Service of the CA.
|
|
//
|
|
// It is non-RFC 8555 compliant. Package users can store the ToS they agree to
|
|
// during Client's Register call in the prompt callback function.
|
|
AgreedTerms string
|
|
|
|
// Actual terms of a CA.
|
|
//
|
|
// It is non-RFC 8555 compliant. Use Directory's Terms field.
|
|
// When a CA updates their terms and requires an account agreement,
|
|
// a URL at which instructions to do so is available in Error's Instance field.
|
|
CurrentTerms string
|
|
|
|
// Authz is the authorization URL used to initiate a new authz flow.
|
|
//
|
|
// It is non-RFC 8555 compliant. Use Directory's AuthzURL or OrderURL.
|
|
Authz string
|
|
|
|
// Authorizations is a URI from which a list of authorizations
|
|
// granted to this account can be fetched via a GET request.
|
|
//
|
|
// It is non-RFC 8555 compliant and is obsoleted by OrdersURL.
|
|
Authorizations string
|
|
|
|
// Certificates is a URI from which a list of certificates
|
|
// issued for this account can be fetched via a GET request.
|
|
//
|
|
// It is non-RFC 8555 compliant and is obsoleted by OrdersURL.
|
|
Certificates string
|
|
}
|
|
|
|
// Directory is ACME server discovery data.
|
|
// See https://tools.ietf.org/html/rfc8555#section-7.1.1 for more details.
|
|
type Directory struct {
|
|
// NonceURL indicates an endpoint where to fetch fresh nonce values from.
|
|
NonceURL string
|
|
|
|
// RegURL is an account endpoint URL, allowing for creating new accounts.
|
|
// Pre-RFC 8555 CAs also allow modifying existing accounts at this URL.
|
|
RegURL string
|
|
|
|
// OrderURL is used to initiate the certificate issuance flow
|
|
// as described in RFC 8555.
|
|
OrderURL string
|
|
|
|
// AuthzURL is used to initiate identifier pre-authorization flow.
|
|
// Empty string indicates the flow is unsupported by the CA.
|
|
AuthzURL string
|
|
|
|
// CertURL is a new certificate issuance endpoint URL.
|
|
// It is non-RFC 8555 compliant and is obsoleted by OrderURL.
|
|
CertURL string
|
|
|
|
// RevokeURL is used to initiate a certificate revocation flow.
|
|
RevokeURL string
|
|
|
|
// KeyChangeURL allows to perform account key rollover flow.
|
|
KeyChangeURL string
|
|
|
|
// Term is a URI identifying the current terms of service.
|
|
Terms string
|
|
|
|
// Website is an HTTP or HTTPS URL locating a website
|
|
// providing more information about the ACME server.
|
|
Website string
|
|
|
|
// CAA consists of lowercase hostname elements, which the ACME server
|
|
// recognises as referring to itself for the purposes of CAA record validation
|
|
// as defined in RFC6844.
|
|
CAA []string
|
|
|
|
// ExternalAccountRequired indicates that the CA requires for all account-related
|
|
// requests to include external account binding information.
|
|
ExternalAccountRequired bool
|
|
}
|
|
|
|
// rfcCompliant reports whether the ACME server implements RFC 8555.
|
|
// Note that some servers may have incomplete RFC implementation
|
|
// even if the returned value is true.
|
|
// If rfcCompliant reports false, the server most likely implements draft-02.
|
|
func (d *Directory) rfcCompliant() bool {
|
|
return d.OrderURL != ""
|
|
}
|
|
|
|
// Order represents a client's request for a certificate.
|
|
// It tracks the request flow progress through to issuance.
|
|
type Order struct {
|
|
// URI uniquely identifies an order.
|
|
URI string
|
|
|
|
// Status represents the current status of the order.
|
|
// It indicates which action the client should take.
|
|
//
|
|
// Possible values are StatusPending, StatusReady, StatusProcessing, StatusValid and StatusInvalid.
|
|
// Pending means the CA does not believe that the client has fulfilled the requirements.
|
|
// Ready indicates that the client has fulfilled all the requirements and can submit a CSR
|
|
// to obtain a certificate. This is done with Client's CreateOrderCert.
|
|
// Processing means the certificate is being issued.
|
|
// Valid indicates the CA has issued the certificate. It can be downloaded
|
|
// from the Order's CertURL. This is done with Client's FetchCert.
|
|
// Invalid means the certificate will not be issued. Users should consider this order
|
|
// abandoned.
|
|
Status string
|
|
|
|
// Expires is the timestamp after which CA considers this order invalid.
|
|
Expires time.Time
|
|
|
|
// Identifiers contains all identifier objects which the order pertains to.
|
|
Identifiers []AuthzID
|
|
|
|
// NotBefore is the requested value of the notBefore field in the certificate.
|
|
NotBefore time.Time
|
|
|
|
// NotAfter is the requested value of the notAfter field in the certificate.
|
|
NotAfter time.Time
|
|
|
|
// AuthzURLs represents authorizations to complete before a certificate
|
|
// for identifiers specified in the order can be issued.
|
|
// It also contains unexpired authorizations that the client has completed
|
|
// in the past.
|
|
//
|
|
// Authorization objects can be fetched using Client's GetAuthorization method.
|
|
//
|
|
// The required authorizations are dictated by CA policies.
|
|
// There may not be a 1:1 relationship between the identifiers and required authorizations.
|
|
// Required authorizations can be identified by their StatusPending status.
|
|
//
|
|
// For orders in the StatusValid or StatusInvalid state these are the authorizations
|
|
// which were completed.
|
|
AuthzURLs []string
|
|
|
|
// FinalizeURL is the endpoint at which a CSR is submitted to obtain a certificate
|
|
// once all the authorizations are satisfied.
|
|
FinalizeURL string
|
|
|
|
// CertURL points to the certificate that has been issued in response to this order.
|
|
CertURL string
|
|
|
|
// The error that occurred while processing the order as received from a CA, if any.
|
|
Error *Error
|
|
}
|
|
|
|
// OrderOption allows customizing Client.AuthorizeOrder call.
|
|
type OrderOption interface {
|
|
privateOrderOpt()
|
|
}
|
|
|
|
// WithOrderNotBefore sets order's NotBefore field.
|
|
func WithOrderNotBefore(t time.Time) OrderOption {
|
|
return orderNotBeforeOpt(t)
|
|
}
|
|
|
|
// WithOrderNotAfter sets order's NotAfter field.
|
|
func WithOrderNotAfter(t time.Time) OrderOption {
|
|
return orderNotAfterOpt(t)
|
|
}
|
|
|
|
type orderNotBeforeOpt time.Time
|
|
|
|
func (orderNotBeforeOpt) privateOrderOpt() {}
|
|
|
|
type orderNotAfterOpt time.Time
|
|
|
|
func (orderNotAfterOpt) privateOrderOpt() {}
|
|
|
|
// Authorization encodes an authorization response.
|
|
type Authorization struct {
|
|
// URI uniquely identifies a authorization.
|
|
URI string
|
|
|
|
// Status is the current status of an authorization.
|
|
// Possible values are StatusPending, StatusValid, StatusInvalid, StatusDeactivated,
|
|
// StatusExpired and StatusRevoked.
|
|
Status string
|
|
|
|
// Identifier is what the account is authorized to represent.
|
|
Identifier AuthzID
|
|
|
|
// The timestamp after which the CA considers the authorization invalid.
|
|
Expires time.Time
|
|
|
|
// Wildcard is true for authorizations of a wildcard domain name.
|
|
Wildcard bool
|
|
|
|
// Challenges that the client needs to fulfill in order to prove possession
|
|
// of the identifier (for pending authorizations).
|
|
// For valid authorizations, the challenge that was validated.
|
|
// For invalid authorizations, the challenge that was attempted and failed.
|
|
//
|
|
// RFC 8555 compatible CAs require users to fuflfill only one of the challenges.
|
|
Challenges []*Challenge
|
|
|
|
// A collection of sets of challenges, each of which would be sufficient
|
|
// to prove possession of the identifier.
|
|
// Clients must complete a set of challenges that covers at least one set.
|
|
// Challenges are identified by their indices in the challenges array.
|
|
// If this field is empty, the client needs to complete all challenges.
|
|
//
|
|
// This field is unused in RFC 8555.
|
|
Combinations [][]int
|
|
}
|
|
|
|
// AuthzID is an identifier that an account is authorized to represent.
|
|
type AuthzID struct {
|
|
Type string // The type of identifier, "dns" or "ip".
|
|
Value string // The identifier itself, e.g. "example.org".
|
|
}
|
|
|
|
// DomainIDs creates a slice of AuthzID with "dns" identifier type.
|
|
func DomainIDs(names ...string) []AuthzID {
|
|
a := make([]AuthzID, len(names))
|
|
for i, v := range names {
|
|
a[i] = AuthzID{Type: "dns", Value: v}
|
|
}
|
|
return a
|
|
}
|
|
|
|
// IPIDs creates a slice of AuthzID with "ip" identifier type.
|
|
// Each element of addr is textual form of an address as defined
|
|
// in RFC1123 Section 2.1 for IPv4 and in RFC5952 Section 4 for IPv6.
|
|
func IPIDs(addr ...string) []AuthzID {
|
|
a := make([]AuthzID, len(addr))
|
|
for i, v := range addr {
|
|
a[i] = AuthzID{Type: "ip", Value: v}
|
|
}
|
|
return a
|
|
}
|
|
|
|
// wireAuthzID is ACME JSON representation of authorization identifier objects.
|
|
type wireAuthzID struct {
|
|
Type string `json:"type"`
|
|
Value string `json:"value"`
|
|
}
|
|
|
|
// wireAuthz is ACME JSON representation of Authorization objects.
|
|
type wireAuthz struct {
|
|
Identifier wireAuthzID
|
|
Status string
|
|
Expires time.Time
|
|
Wildcard bool
|
|
Challenges []wireChallenge
|
|
Combinations [][]int
|
|
}
|
|
|
|
func (z *wireAuthz) authorization(uri string) *Authorization {
|
|
a := &Authorization{
|
|
URI: uri,
|
|
Status: z.Status,
|
|
Identifier: AuthzID{Type: z.Identifier.Type, Value: z.Identifier.Value},
|
|
Expires: z.Expires,
|
|
Wildcard: z.Wildcard,
|
|
Challenges: make([]*Challenge, len(z.Challenges)),
|
|
Combinations: z.Combinations, // shallow copy
|
|
}
|
|
for i, v := range z.Challenges {
|
|
a.Challenges[i] = v.challenge()
|
|
}
|
|
return a
|
|
}
|
|
|
|
func (z *wireAuthz) error(uri string) *AuthorizationError {
|
|
err := &AuthorizationError{
|
|
URI: uri,
|
|
Identifier: z.Identifier.Value,
|
|
}
|
|
for _, raw := range z.Challenges {
|
|
if raw.Error != nil {
|
|
err.Errors = append(err.Errors, raw.Error.error(nil))
|
|
}
|
|
}
|
|
return err
|
|
}
|
|
|
|
// Challenge encodes a returned CA challenge.
|
|
// Its Error field may be non-nil if the challenge is part of an Authorization
|
|
// with StatusInvalid.
|
|
type Challenge struct {
|
|
// Type is the challenge type, e.g. "http-01", "tls-alpn-01", "dns-01".
|
|
Type string
|
|
|
|
// URI is where a challenge response can be posted to.
|
|
URI string
|
|
|
|
// Token is a random value that uniquely identifies the challenge.
|
|
Token string
|
|
|
|
// Status identifies the status of this challenge.
|
|
// In RFC 8555, possible values are StatusPending, StatusProcessing, StatusValid,
|
|
// and StatusInvalid.
|
|
Status string
|
|
|
|
// Validated is the time at which the CA validated this challenge.
|
|
// Always zero value in pre-RFC 8555.
|
|
Validated time.Time
|
|
|
|
// Error indicates the reason for an authorization failure
|
|
// when this challenge was used.
|
|
// The type of a non-nil value is *Error.
|
|
Error error
|
|
}
|
|
|
|
// wireChallenge is ACME JSON challenge representation.
|
|
type wireChallenge struct {
|
|
URL string `json:"url"` // RFC
|
|
URI string `json:"uri"` // pre-RFC
|
|
Type string
|
|
Token string
|
|
Status string
|
|
Validated time.Time
|
|
Error *wireError
|
|
}
|
|
|
|
func (c *wireChallenge) challenge() *Challenge {
|
|
v := &Challenge{
|
|
URI: c.URL,
|
|
Type: c.Type,
|
|
Token: c.Token,
|
|
Status: c.Status,
|
|
}
|
|
if v.URI == "" {
|
|
v.URI = c.URI // c.URL was empty; use legacy
|
|
}
|
|
if v.Status == "" {
|
|
v.Status = StatusPending
|
|
}
|
|
if c.Error != nil {
|
|
v.Error = c.Error.error(nil)
|
|
}
|
|
return v
|
|
}
|
|
|
|
// wireError is a subset of fields of the Problem Details object
|
|
// as described in https://tools.ietf.org/html/rfc7807#section-3.1.
|
|
type wireError struct {
|
|
Status int
|
|
Type string
|
|
Detail string
|
|
Instance string
|
|
}
|
|
|
|
func (e *wireError) error(h http.Header) *Error {
|
|
return &Error{
|
|
StatusCode: e.Status,
|
|
ProblemType: e.Type,
|
|
Detail: e.Detail,
|
|
Instance: e.Instance,
|
|
Header: h,
|
|
}
|
|
}
|
|
|
|
// CertOption is an optional argument type for the TLS ChallengeCert methods for
|
|
// customizing a temporary certificate for TLS-based challenges.
|
|
type CertOption interface {
|
|
privateCertOpt()
|
|
}
|
|
|
|
// WithKey creates an option holding a private/public key pair.
|
|
// The private part signs a certificate, and the public part represents the signee.
|
|
func WithKey(key crypto.Signer) CertOption {
|
|
return &certOptKey{key}
|
|
}
|
|
|
|
type certOptKey struct {
|
|
key crypto.Signer
|
|
}
|
|
|
|
func (*certOptKey) privateCertOpt() {}
|
|
|
|
// WithTemplate creates an option for specifying a certificate template.
|
|
// See x509.CreateCertificate for template usage details.
|
|
//
|
|
// In TLS ChallengeCert methods, the template is also used as parent,
|
|
// resulting in a self-signed certificate.
|
|
// The DNSNames field of t is always overwritten for tls-sni challenge certs.
|
|
func WithTemplate(t *x509.Certificate) CertOption {
|
|
return (*certOptTemplate)(t)
|
|
}
|
|
|
|
type certOptTemplate x509.Certificate
|
|
|
|
func (*certOptTemplate) privateCertOpt() {}
|