2018-06-10 11:11:41 +02:00
|
|
|
// 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 server response statuses used to describe Authorization and Challenge states.
|
|
|
|
const (
|
|
|
|
StatusUnknown = "unknown"
|
|
|
|
StatusPending = "pending"
|
|
|
|
StatusProcessing = "processing"
|
|
|
|
StatusValid = "valid"
|
|
|
|
StatusInvalid = "invalid"
|
|
|
|
StatusRevoked = "revoked"
|
|
|
|
)
|
|
|
|
|
|
|
|
// 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
|
|
|
|
)
|
|
|
|
|
|
|
|
// ErrUnsupportedKey is returned when an unsupported key type is encountered.
|
|
|
|
var ErrUnsupportedKey = errors.New("acme: unknown key type; only RSA and ECDSA are supported")
|
|
|
|
|
|
|
|
// 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
|
|
|
|
// 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, "; "))
|
|
|
|
}
|
|
|
|
|
|
|
|
// 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
|
|
|
|
}
|
2018-12-01 00:26:56 +01:00
|
|
|
return retryAfter(e.Header.Get("Retry-After")), true
|
2018-06-10 11:11:41 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// Account is a user account. It is associated with a private key.
|
|
|
|
type Account struct {
|
|
|
|
// URI is the account unique ID, which is also a URL used to retrieve
|
|
|
|
// account data from the CA.
|
|
|
|
URI string
|
|
|
|
|
|
|
|
// Contact is a slice of contact info used during registration.
|
|
|
|
Contact []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.
|
|
|
|
AgreedTerms string
|
|
|
|
|
|
|
|
// Actual terms of a CA.
|
|
|
|
CurrentTerms string
|
|
|
|
|
|
|
|
// Authz is the authorization URL used to initiate a new authz flow.
|
|
|
|
Authz string
|
|
|
|
|
|
|
|
// Authorizations is a URI from which a list of authorizations
|
|
|
|
// granted to this account can be fetched via a GET request.
|
|
|
|
Authorizations string
|
|
|
|
|
|
|
|
// Certificates is a URI from which a list of certificates
|
|
|
|
// issued for this account can be fetched via a GET request.
|
|
|
|
Certificates string
|
|
|
|
}
|
|
|
|
|
|
|
|
// Directory is ACME server discovery data.
|
|
|
|
type Directory struct {
|
|
|
|
// RegURL is an account endpoint URL, allowing for creating new
|
|
|
|
// and modifying existing accounts.
|
|
|
|
RegURL string
|
|
|
|
|
|
|
|
// AuthzURL is used to initiate Identifier Authorization flow.
|
|
|
|
AuthzURL string
|
|
|
|
|
|
|
|
// CertURL is a new certificate issuance endpoint URL.
|
|
|
|
CertURL string
|
|
|
|
|
|
|
|
// RevokeURL is used to initiate a certificate revocation flow.
|
|
|
|
RevokeURL 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
|
|
|
|
}
|
|
|
|
|
|
|
|
// 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-sni-02", "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.
|
|
|
|
Status string
|
|
|
|
|
|
|
|
// Error indicates the reason for an authorization failure
|
|
|
|
// when this challenge was used.
|
|
|
|
// The type of a non-nil value is *Error.
|
|
|
|
Error error
|
|
|
|
}
|
|
|
|
|
|
|
|
// Authorization encodes an authorization response.
|
|
|
|
type Authorization struct {
|
|
|
|
// URI uniquely identifies a authorization.
|
|
|
|
URI string
|
|
|
|
|
|
|
|
// Status identifies the status of an authorization.
|
|
|
|
Status string
|
|
|
|
|
|
|
|
// Identifier is what the account is authorized to represent.
|
|
|
|
Identifier AuthzID
|
|
|
|
|
|
|
|
// Challenges that the client needs to fulfill in order to prove possession
|
|
|
|
// of the identifier (for pending authorizations).
|
|
|
|
// For final authorizations, the challenges that were used.
|
|
|
|
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.
|
|
|
|
Combinations [][]int
|
|
|
|
}
|
|
|
|
|
|
|
|
// AuthzID is an identifier that an account is authorized to represent.
|
|
|
|
type AuthzID struct {
|
|
|
|
Type string // The type of identifier, e.g. "dns".
|
|
|
|
Value string // The identifier itself, e.g. "example.org".
|
|
|
|
}
|
|
|
|
|
|
|
|
// wireAuthz is ACME JSON representation of Authorization objects.
|
|
|
|
type wireAuthz struct {
|
|
|
|
Status string
|
|
|
|
Challenges []wireChallenge
|
|
|
|
Combinations [][]int
|
|
|
|
Identifier struct {
|
|
|
|
Type string
|
|
|
|
Value string
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (z *wireAuthz) authorization(uri string) *Authorization {
|
|
|
|
a := &Authorization{
|
|
|
|
URI: uri,
|
|
|
|
Status: z.Status,
|
|
|
|
Identifier: AuthzID{Type: z.Identifier.Type, Value: z.Identifier.Value},
|
|
|
|
Combinations: z.Combinations, // shallow copy
|
|
|
|
Challenges: make([]*Challenge, len(z.Challenges)),
|
|
|
|
}
|
|
|
|
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
|
|
|
|
}
|
|
|
|
|
|
|
|
// wireChallenge is ACME JSON challenge representation.
|
|
|
|
type wireChallenge struct {
|
|
|
|
URI string `json:"uri"`
|
|
|
|
Type string
|
|
|
|
Token string
|
|
|
|
Status string
|
|
|
|
Error *wireError
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *wireChallenge) challenge() *Challenge {
|
|
|
|
v := &Challenge{
|
|
|
|
URI: c.URI,
|
|
|
|
Type: c.Type,
|
|
|
|
Token: c.Token,
|
|
|
|
Status: c.Status,
|
|
|
|
}
|
|
|
|
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
|
|
|
|
}
|
|
|
|
|
|
|
|
func (e *wireError) error(h http.Header) *Error {
|
|
|
|
return &Error{
|
|
|
|
StatusCode: e.Status,
|
|
|
|
ProblemType: e.Type,
|
|
|
|
Detail: e.Detail,
|
|
|
|
Header: h,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-12-01 00:26:56 +01:00
|
|
|
// CertOption is an optional argument type for the TLS ChallengeCert methods for
|
|
|
|
// customizing a temporary certificate for TLS-based challenges.
|
2018-06-10 11:11:41 +02:00
|
|
|
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.
|
|
|
|
//
|
2018-12-01 00:26:56 +01:00
|
|
|
// In TLS ChallengeCert methods, the template is also used as parent,
|
2018-06-10 11:11:41 +02:00
|
|
|
// 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() {}
|