Update
This commit is contained in:
1 parent
3530afb854
commit
6903fcff27
2146 files changed
+112256
-190703
No files matched your search
+47
-59
@@ -31,12 +31,11 @@ import (
|
||||
"crypto/x509/pkix"
|
||||
"encoding/asn1"
|
||||
"encoding/base64"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"encoding/pem"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"net"
|
||||
"net/http"
|
||||
"strings"
|
||||
"sync"
|
||||
@@ -187,10 +186,11 @@ func (c *Client) Discover(ctx context.Context) (Directory, error) {
|
||||
KeyChange string `json:"keyChange"`
|
||||
RenewalInfo string `json:"renewalInfo"`
|
||||
Meta struct {
|
||||
Terms string `json:"termsOfService"`
|
||||
Website string `json:"website"`
|
||||
CAA []string `json:"caaIdentities"`
|
||||
ExternalAcct bool `json:"externalAccountRequired"`
|
||||
Terms string `json:"termsOfService"`
|
||||
Website string `json:"website"`
|
||||
CAA []string `json:"caaIdentities"`
|
||||
ExternalAcct bool `json:"externalAccountRequired"`
|
||||
Profiles map[string]string `json:"profiles"`
|
||||
}
|
||||
}
|
||||
if err := json.NewDecoder(res.Body).Decode(&v); err != nil {
|
||||
@@ -211,6 +211,7 @@ func (c *Client) Discover(ctx context.Context) (Directory, error) {
|
||||
Website: v.Meta.Website,
|
||||
CAA: v.Meta.CAA,
|
||||
ExternalAccountRequired: v.Meta.ExternalAcct,
|
||||
Profiles: v.Meta.Profiles,
|
||||
}
|
||||
return *c.dir, nil
|
||||
}
|
||||
@@ -397,6 +398,10 @@ func (c *Client) authorize(ctx context.Context, typ, val string) (*Authorization
|
||||
if _, err := c.Discover(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if c.dir.AuthzURL == "" {
|
||||
// Pre-Authorization is unsupported
|
||||
return nil, errPreAuthorizationNotSupported
|
||||
}
|
||||
|
||||
type authzID struct {
|
||||
Type string `json:"type"`
|
||||
@@ -511,7 +516,7 @@ func (c *Client) WaitAuthorization(ctx context.Context, url string) (*Authorizat
|
||||
// while waiting for a final authorization status.
|
||||
d := retryAfter(res.Header.Get("Retry-After"))
|
||||
if d == 0 {
|
||||
// Given that the fastest challenges TLS-SNI and HTTP-01
|
||||
// Given that the fastest challenges TLS-ALPN and HTTP-01
|
||||
// require a CA to make at least 1 network round trip
|
||||
// and most likely persist a challenge state,
|
||||
// this default delay seems reasonable.
|
||||
@@ -612,50 +617,28 @@ func (c *Client) HTTP01ChallengePath(token string) string {
|
||||
}
|
||||
|
||||
// TLSSNI01ChallengeCert creates a certificate for TLS-SNI-01 challenge response.
|
||||
// Always returns an error.
|
||||
//
|
||||
// Deprecated: This challenge type is unused in both draft-02 and RFC versions of the ACME spec.
|
||||
func (c *Client) TLSSNI01ChallengeCert(token string, opt ...CertOption) (cert tls.Certificate, name string, err error) {
|
||||
ka, err := keyAuth(c.Key.Public(), token)
|
||||
if err != nil {
|
||||
return tls.Certificate{}, "", err
|
||||
}
|
||||
b := sha256.Sum256([]byte(ka))
|
||||
h := hex.EncodeToString(b[:])
|
||||
name = fmt.Sprintf("%s.%s.acme.invalid", h[:32], h[32:])
|
||||
cert, err = tlsChallengeCert([]string{name}, opt)
|
||||
if err != nil {
|
||||
return tls.Certificate{}, "", err
|
||||
}
|
||||
return cert, name, nil
|
||||
// Deprecated: This challenge type was only present in pre-standardized ACME
|
||||
// protocol drafts and is insecure for use in shared hosting environments.
|
||||
func (c *Client) TLSSNI01ChallengeCert(token string, opt ...CertOption) (tls.Certificate, string, error) {
|
||||
return tls.Certificate{}, "", errPreRFC
|
||||
}
|
||||
|
||||
// TLSSNI02ChallengeCert creates a certificate for TLS-SNI-02 challenge response.
|
||||
// Always returns an error.
|
||||
//
|
||||
// Deprecated: This challenge type is unused in both draft-02 and RFC versions of the ACME spec.
|
||||
func (c *Client) TLSSNI02ChallengeCert(token string, opt ...CertOption) (cert tls.Certificate, name string, err error) {
|
||||
b := sha256.Sum256([]byte(token))
|
||||
h := hex.EncodeToString(b[:])
|
||||
sanA := fmt.Sprintf("%s.%s.token.acme.invalid", h[:32], h[32:])
|
||||
|
||||
ka, err := keyAuth(c.Key.Public(), token)
|
||||
if err != nil {
|
||||
return tls.Certificate{}, "", err
|
||||
}
|
||||
b = sha256.Sum256([]byte(ka))
|
||||
h = hex.EncodeToString(b[:])
|
||||
sanB := fmt.Sprintf("%s.%s.ka.acme.invalid", h[:32], h[32:])
|
||||
|
||||
cert, err = tlsChallengeCert([]string{sanA, sanB}, opt)
|
||||
if err != nil {
|
||||
return tls.Certificate{}, "", err
|
||||
}
|
||||
return cert, sanA, nil
|
||||
// Deprecated: This challenge type was only present in pre-standardized ACME
|
||||
// protocol drafts and is insecure for use in shared hosting environments.
|
||||
func (c *Client) TLSSNI02ChallengeCert(token string, opt ...CertOption) (tls.Certificate, string, error) {
|
||||
return tls.Certificate{}, "", errPreRFC
|
||||
}
|
||||
|
||||
// TLSALPN01ChallengeCert creates a certificate for TLS-ALPN-01 challenge response.
|
||||
// Servers can present the certificate to validate the challenge and prove control
|
||||
// over a domain name. For more details on TLS-ALPN-01 see
|
||||
// https://tools.ietf.org/html/draft-shoemaker-acme-tls-alpn-00#section-3
|
||||
// over an identifier (either a DNS name or the textual form of an IPv4 or IPv6
|
||||
// address). For more details on TLS-ALPN-01 see
|
||||
// https://www.rfc-editor.org/rfc/rfc8737 and https://www.rfc-editor.org/rfc/rfc8738
|
||||
//
|
||||
// The token argument is a Challenge.Token value.
|
||||
// If a WithKey option is provided, its private part signs the returned cert,
|
||||
@@ -663,9 +646,13 @@ func (c *Client) TLSSNI02ChallengeCert(token string, opt ...CertOption) (cert tl
|
||||
// If no WithKey option is provided, a new ECDSA key is generated using P-256 curve.
|
||||
//
|
||||
// The returned certificate is valid for the next 24 hours and must be presented only when
|
||||
// the server name in the TLS ClientHello matches the domain, and the special acme-tls/1 ALPN protocol
|
||||
// the server name in the TLS ClientHello matches the identifier, and the special acme-tls/1 ALPN protocol
|
||||
// has been specified.
|
||||
func (c *Client) TLSALPN01ChallengeCert(token, domain string, opt ...CertOption) (cert tls.Certificate, err error) {
|
||||
//
|
||||
// Validation requests for IP address identifiers will use the reverse DNS form in the server name
|
||||
// in the TLS ClientHello since the SNI extension is not supported for IP addresses.
|
||||
// See RFC 8738 Section 6 for more information.
|
||||
func (c *Client) TLSALPN01ChallengeCert(token, identifier string, opt ...CertOption) (cert tls.Certificate, err error) {
|
||||
ka, err := keyAuth(c.Key.Public(), token)
|
||||
if err != nil {
|
||||
return tls.Certificate{}, err
|
||||
@@ -695,7 +682,7 @@ func (c *Client) TLSALPN01ChallengeCert(token, domain string, opt ...CertOption)
|
||||
}
|
||||
tmpl.ExtraExtensions = append(tmpl.ExtraExtensions, acmeExtension)
|
||||
newOpt = append(newOpt, WithTemplate(tmpl))
|
||||
return tlsChallengeCert([]string{domain}, newOpt)
|
||||
return tlsChallengeCert(identifier, newOpt)
|
||||
}
|
||||
|
||||
// popNonce returns a nonce value previously stored with c.addNonce
|
||||
@@ -749,7 +736,7 @@ func (c *Client) addNonce(h http.Header) {
|
||||
}
|
||||
|
||||
func (c *Client) fetchNonce(ctx context.Context, url string) (string, error) {
|
||||
r, err := http.NewRequest("HEAD", url, nil)
|
||||
r, err := http.NewRequestWithContext(ctx, "HEAD", url, nil)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
@@ -813,11 +800,15 @@ func defaultTLSChallengeCertTemplate() *x509.Certificate {
|
||||
}
|
||||
}
|
||||
|
||||
// tlsChallengeCert creates a temporary certificate for TLS-SNI challenges
|
||||
// with the given SANs and auto-generated public/private key pair.
|
||||
// The Subject Common Name is set to the first SAN to aid debugging.
|
||||
// tlsChallengeCert creates a temporary certificate for TLS-ALPN challenges
|
||||
// for the given identifier, using an auto-generated public/private key pair.
|
||||
//
|
||||
// If the provided identifier is a domain name, it will be used as a DNS type SAN and for the
|
||||
// subject common name. If the provided identifier is an IP address it will be used as an IP type
|
||||
// SAN.
|
||||
//
|
||||
// To create a cert with a custom key pair, specify WithKey option.
|
||||
func tlsChallengeCert(san []string, opt []CertOption) (tls.Certificate, error) {
|
||||
func tlsChallengeCert(identifier string, opt []CertOption) (tls.Certificate, error) {
|
||||
var key crypto.Signer
|
||||
tmpl := defaultTLSChallengeCertTemplate()
|
||||
for _, o := range opt {
|
||||
@@ -841,9 +832,12 @@ func tlsChallengeCert(san []string, opt []CertOption) (tls.Certificate, error) {
|
||||
return tls.Certificate{}, err
|
||||
}
|
||||
}
|
||||
tmpl.DNSNames = san
|
||||
if len(san) > 0 {
|
||||
tmpl.Subject.CommonName = san[0]
|
||||
|
||||
if ip := net.ParseIP(identifier); ip != nil {
|
||||
tmpl.IPAddresses = []net.IP{ip}
|
||||
} else {
|
||||
tmpl.DNSNames = []string{identifier}
|
||||
tmpl.Subject.CommonName = identifier
|
||||
}
|
||||
|
||||
der, err := x509.CreateCertificate(rand.Reader, tmpl, tmpl, key.Public(), key)
|
||||
@@ -856,11 +850,5 @@ func tlsChallengeCert(san []string, opt []CertOption) (tls.Certificate, error) {
|
||||
}, nil
|
||||
}
|
||||
|
||||
// encodePEM returns b encoded as PEM with block of type typ.
|
||||
func encodePEM(typ string, b []byte) []byte {
|
||||
pb := &pem.Block{Type: typ, Bytes: b}
|
||||
return pem.EncodeToMemory(pb)
|
||||
}
|
||||
|
||||
// timeNow is time.Now, except in tests which can mess with it.
|
||||
var timeNow = time.Now
|
||||
+4
-7
@@ -66,7 +66,7 @@ func (c *Client) retryTimer() *retryTimer {
|
||||
// The n argument is always bounded between 1 and 30.
|
||||
// The returned value is always greater than 0.
|
||||
func defaultBackoff(n int, r *http.Request, res *http.Response) time.Duration {
|
||||
const max = 10 * time.Second
|
||||
const maxVal = 10 * time.Second
|
||||
var jitter time.Duration
|
||||
if x, err := rand.Int(rand.Reader, big.NewInt(1000)); err == nil {
|
||||
// Set the minimum to 1ms to avoid a case where
|
||||
@@ -86,10 +86,7 @@ func defaultBackoff(n int, r *http.Request, res *http.Response) time.Duration {
|
||||
n = 30
|
||||
}
|
||||
d := time.Duration(1<<uint(n-1))*time.Second + jitter
|
||||
if d > max {
|
||||
return max
|
||||
}
|
||||
return d
|
||||
return min(d, maxVal)
|
||||
}
|
||||
|
||||
// retryAfter parses a Retry-After HTTP header value,
|
||||
@@ -131,7 +128,7 @@ func wantStatus(codes ...int) resOkay {
|
||||
func (c *Client) get(ctx context.Context, url string, ok resOkay) (*http.Response, error) {
|
||||
retry := c.retryTimer()
|
||||
for {
|
||||
req, err := http.NewRequest("GET", url, nil)
|
||||
req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -231,7 +228,7 @@ func (c *Client) postNoRetry(ctx context.Context, key crypto.Signer, url string,
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
req, err := http.NewRequest("POST", url, bytes.NewReader(b))
|
||||
req, err := http.NewRequestWithContext(ctx, "POST", url, bytes.NewReader(b))
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
+1
-1
@@ -92,7 +92,7 @@ func jwsEncodeJSON(claimset interface{}, key crypto.Signer, kid KeyID, nonce, ur
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
phead := base64.RawURLEncoding.EncodeToString([]byte(phJSON))
|
||||
phead := base64.RawURLEncoding.EncodeToString(phJSON)
|
||||
var payload string
|
||||
if val, ok := claimset.(string); ok {
|
||||
payload = val
|
||||
|
||||
+16
-3
@@ -54,6 +54,9 @@ func (c *Client) registerRFC(ctx context.Context, acct *Account, prompt func(tos
|
||||
Contact: acct.Contact,
|
||||
}
|
||||
if c.dir.Terms != "" {
|
||||
if prompt == nil {
|
||||
return nil, errors.New("acme: missing Manager.Prompt to accept server's terms of service")
|
||||
}
|
||||
req.TermsAgreed = prompt(c.dir.Terms)
|
||||
}
|
||||
|
||||
@@ -207,6 +210,7 @@ func (c *Client) AuthorizeOrder(ctx context.Context, id []AuthzID, opt ...OrderO
|
||||
NotBefore string `json:"notBefore,omitempty"`
|
||||
NotAfter string `json:"notAfter,omitempty"`
|
||||
Replaces string `json:"replaces,omitempty"`
|
||||
Profile string `json:"profile,omitempty"`
|
||||
}{}
|
||||
for _, v := range id {
|
||||
req.Identifiers = append(req.Identifiers, wireAuthzID{
|
||||
@@ -228,6 +232,15 @@ func (c *Client) AuthorizeOrder(ctx context.Context, id []AuthzID, opt ...OrderO
|
||||
return nil, fmt.Errorf("failed to parse certificate being replaced: %w", err)
|
||||
}
|
||||
req.Replaces = certRenewalIdentifier(cert)
|
||||
case orderProfileOpt:
|
||||
if !dir.Profiles.isSupported() {
|
||||
return nil, ErrCADoesNotSupportProfiles
|
||||
}
|
||||
profileName := string(o)
|
||||
if !dir.Profiles.Has(profileName) {
|
||||
return nil, fmt.Errorf("%w %s", ErrProfileNotInSetOfSupportedProfiles, profileName)
|
||||
}
|
||||
req.Profile = profileName
|
||||
default:
|
||||
// Package's fault if we let this happen.
|
||||
panic(fmt.Sprintf("unsupported order option type %T", o))
|
||||
@@ -242,7 +255,7 @@ func (c *Client) AuthorizeOrder(ctx context.Context, id []AuthzID, opt ...OrderO
|
||||
return responseOrder(res)
|
||||
}
|
||||
|
||||
// GetOrder retrives an order identified by the given URL.
|
||||
// GetOrder retrieves 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,
|
||||
@@ -282,7 +295,7 @@ func (c *Client) WaitOrder(ctx context.Context, url string) (*Order, error) {
|
||||
case err != nil:
|
||||
// Skip and retry.
|
||||
case o.Status == StatusInvalid:
|
||||
return nil, &OrderError{OrderURL: o.URI, Status: o.Status}
|
||||
return nil, &OrderError{OrderURL: o.URI, Status: o.Status, Problem: o.Error}
|
||||
case o.Status == StatusReady || o.Status == StatusValid:
|
||||
return o, nil
|
||||
}
|
||||
@@ -379,7 +392,7 @@ func (c *Client) CreateOrderCert(ctx context.Context, url string, csr []byte, bu
|
||||
}
|
||||
// The only acceptable status post finalize and WaitOrder is "valid".
|
||||
if o.Status != StatusValid {
|
||||
return nil, "", &OrderError{OrderURL: o.URI, Status: o.Status}
|
||||
return nil, "", &OrderError{OrderURL: o.URI, Status: o.Status, Problem: o.Error}
|
||||
}
|
||||
crt, err := c.fetchCertRFC(ctx, o.CertURL, bundle)
|
||||
return crt, o.CertURL, err
|
||||
|
||||
+55
-3
@@ -56,6 +56,19 @@ var (
|
||||
|
||||
// ErrNoAccount indicates that the Client's key has not been registered with the CA.
|
||||
ErrNoAccount = errors.New("acme: account does not exist")
|
||||
|
||||
// errPreAuthorizationNotSupported indicates that the server does not
|
||||
// support pre-authorization of identifiers.
|
||||
errPreAuthorizationNotSupported = errors.New("acme: pre-authorization is not supported")
|
||||
|
||||
// ErrCADoesNotSupportProfiles indicates that [WithOrderProfile] was
|
||||
// included with a CA that does not advertise support for profiles in
|
||||
// their directory.
|
||||
ErrCADoesNotSupportProfiles = errors.New("acme: certificate authority does not support profiles")
|
||||
|
||||
// ErrProfileNotInSetOfSupportedProfiles indicates that the profile
|
||||
// specified with [WithOrderProfile} is not one supported by the CA
|
||||
ErrProfileNotInSetOfSupportedProfiles = errors.New("acme: certificate authority does not advertise a profile with name")
|
||||
)
|
||||
|
||||
// A Subproblem describes an ACME subproblem as reported in an Error.
|
||||
@@ -150,17 +163,24 @@ func (a *AuthorizationError) Error() string {
|
||||
|
||||
// OrderError is returned from Client's order related methods.
|
||||
// It indicates the order is unusable and the clients should start over with
|
||||
// AuthorizeOrder.
|
||||
// AuthorizeOrder. A Problem description may be provided with details on
|
||||
// what caused the order to become unusable.
|
||||
//
|
||||
// The clients can still fetch the order object from CA using GetOrder
|
||||
// to inspect its state.
|
||||
type OrderError struct {
|
||||
OrderURL string
|
||||
Status string
|
||||
// Problem is the error that occurred while processing the order.
|
||||
Problem *Error
|
||||
}
|
||||
|
||||
func (oe *OrderError) Error() string {
|
||||
return fmt.Sprintf("acme: order %s status: %s", oe.OrderURL, oe.Status)
|
||||
str := fmt.Sprintf("acme: order %s status: %s", oe.OrderURL, oe.Status)
|
||||
if oe.Problem != nil {
|
||||
str += fmt.Sprintf("; problem: %s", oe.Problem)
|
||||
}
|
||||
return str
|
||||
}
|
||||
|
||||
// RateLimit reports whether err represents a rate limit error and
|
||||
@@ -308,6 +328,10 @@ type Directory struct {
|
||||
// ExternalAccountRequired indicates that the CA requires for all account-related
|
||||
// requests to include external account binding information.
|
||||
ExternalAccountRequired bool
|
||||
|
||||
// Profiles indicates that the CA supports specifying a profile for an
|
||||
// order. See also [WithOrderNotAfter].
|
||||
Profiles Profiles
|
||||
}
|
||||
|
||||
// Order represents a client's request for a certificate.
|
||||
@@ -383,6 +407,15 @@ func WithOrderNotAfter(t time.Time) OrderOption {
|
||||
return orderNotAfterOpt(t)
|
||||
}
|
||||
|
||||
// WithOrderProfile sets an order's Profile field for servers which support
|
||||
// profiles.
|
||||
// See also:
|
||||
// * https://datatracker.ietf.org/doc/draft-aaron-acme-profiles/
|
||||
// * https://letsencrypt.org/docs/profiles/
|
||||
func WithOrderProfile(name string) OrderOption {
|
||||
return orderProfileOpt(name)
|
||||
}
|
||||
|
||||
type orderNotBeforeOpt time.Time
|
||||
|
||||
func (orderNotBeforeOpt) privateOrderOpt() {}
|
||||
@@ -415,6 +448,10 @@ type orderReplacesCertDER []byte
|
||||
|
||||
func (orderReplacesCertDER) privateOrderOpt() {}
|
||||
|
||||
type orderProfileOpt string
|
||||
|
||||
func (orderProfileOpt) privateOrderOpt() {}
|
||||
|
||||
// Authorization encodes an authorization response.
|
||||
type Authorization struct {
|
||||
// URI uniquely identifies a authorization.
|
||||
@@ -643,7 +680,7 @@ func (*certOptKey) privateCertOpt() {}
|
||||
//
|
||||
// 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.
|
||||
// The DNSNames or IPAddresses fields of t are always overwritten for tls-alpn challenge certs.
|
||||
func WithTemplate(t *x509.Certificate) CertOption {
|
||||
return (*certOptTemplate)(t)
|
||||
}
|
||||
@@ -665,3 +702,18 @@ type RenewalInfo struct {
|
||||
SuggestedWindow RenewalInfoWindow `json:"suggestedWindow"`
|
||||
ExplanationURL string `json:"explanationURL"`
|
||||
}
|
||||
|
||||
type Profiles map[string]string
|
||||
|
||||
func (ps Profiles) isSupported() bool {
|
||||
return len(ps) > 0
|
||||
}
|
||||
|
||||
func (ps Profiles) GetDescription(name string) string {
|
||||
return ps[name]
|
||||
}
|
||||
|
||||
func (ps Profiles) Has(name string) bool {
|
||||
_, ok := ps[name]
|
||||
return ok
|
||||
}
|
||||
-27
@@ -1,27 +0,0 @@
|
||||
// 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.
|
||||
|
||||
//go:build go1.12
|
||||
|
||||
package acme
|
||||
|
||||
import "runtime/debug"
|
||||
|
||||
func init() {
|
||||
// Set packageVersion if the binary was built in modules mode and x/crypto
|
||||
// was not replaced with a different module.
|
||||
info, ok := debug.ReadBuildInfo()
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
for _, m := range info.Deps {
|
||||
if m.Path != "golang.org/x/crypto" {
|
||||
continue
|
||||
}
|
||||
if m.Replace == nil {
|
||||
packageVersion = m.Version
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user