Платформа ЦРНП "Мирокод" для разработки проектов
https://git.mirocod.ru
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
947 lines
30 KiB
947 lines
30 KiB
// Copyright 2015 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 certmagic |
|
|
|
import ( |
|
"bytes" |
|
"context" |
|
"crypto" |
|
"crypto/rand" |
|
"crypto/tls" |
|
"crypto/x509" |
|
"crypto/x509/pkix" |
|
"encoding/asn1" |
|
"encoding/json" |
|
"fmt" |
|
weakrand "math/rand" |
|
"net" |
|
"net/url" |
|
"strings" |
|
"time" |
|
|
|
"github.com/mholt/acmez" |
|
"github.com/mholt/acmez/acme" |
|
"go.uber.org/zap" |
|
"golang.org/x/net/idna" |
|
) |
|
|
|
// Config configures a certificate manager instance. |
|
// An empty Config is not valid: use New() to obtain |
|
// a valid Config. |
|
type Config struct { |
|
// How much of a certificate's lifetime becomes the |
|
// renewal window, which is the span of time at the |
|
// end of the certificate's validity period in which |
|
// it should be renewed; for most certificates, the |
|
// global default is good, but for extremely short- |
|
// lived certs, you may want to raise this to ~0.5. |
|
RenewalWindowRatio float64 |
|
|
|
// An optional event callback clients can set |
|
// to subscribe to certain things happening |
|
// internally by this config; invocations are |
|
// synchronous, so make them return quickly! |
|
OnEvent func(event string, data interface{}) |
|
|
|
// DefaultServerName specifies a server name |
|
// to use when choosing a certificate if the |
|
// ClientHello's ServerName field is empty. |
|
DefaultServerName string |
|
|
|
// The state needed to operate on-demand TLS; |
|
// if non-nil, on-demand TLS is enabled and |
|
// certificate operations are deferred to |
|
// TLS handshakes (or as-needed). |
|
// TODO: Can we call this feature "Reactive/Lazy/Passive TLS" instead? |
|
OnDemand *OnDemandConfig |
|
|
|
// Adds the must staple TLS extension to the CSR. |
|
MustStaple bool |
|
|
|
// The source for getting new certificates; the |
|
// default Issuer is ACMEManager. If multiple |
|
// issuers are specified, they will be tried in |
|
// turn until one succeeds. |
|
Issuers []Issuer |
|
|
|
// The source of new private keys for certificates; |
|
// the default KeySource is StandardKeyGenerator. |
|
KeySource KeyGenerator |
|
|
|
// CertSelection chooses one of the certificates |
|
// with which the ClientHello will be completed; |
|
// if not set, DefaultCertificateSelector will |
|
// be used. |
|
CertSelection CertificateSelector |
|
|
|
// OCSP configures how OCSP is handled. By default, |
|
// OCSP responses are fetched for every certificate |
|
// with a responder URL, and cached on disk. Changing |
|
// these defaults is STRONGLY discouraged unless you |
|
// have a compelling reason to put clients at greater |
|
// risk and reduce their privacy. |
|
OCSP OCSPConfig |
|
|
|
// The storage to access when storing or loading |
|
// TLS assets. Default is the local file system. |
|
Storage Storage |
|
|
|
// Set a logger to enable logging. |
|
Logger *zap.Logger |
|
|
|
// required pointer to the in-memory cert cache |
|
certCache *Cache |
|
} |
|
|
|
// NewDefault makes a valid config based on the package |
|
// Default config. Most users will call this function |
|
// instead of New() since most use cases require only a |
|
// single config for any and all certificates. |
|
// |
|
// If your requirements are more advanced (for example, |
|
// multiple configs depending on the certificate), then use |
|
// New() instead. (You will need to make your own Cache |
|
// first.) If you only need a single Config to manage your |
|
// certs (even if that config changes, as long as it is the |
|
// only one), customize the Default package variable before |
|
// calling NewDefault(). |
|
// |
|
// All calls to NewDefault() will return configs that use the |
|
// same, default certificate cache. All configs returned |
|
// by NewDefault() are based on the values of the fields of |
|
// Default at the time it is called. |
|
// |
|
// This is the only way to get a config that uses the |
|
// default certificate cache. |
|
func NewDefault() *Config { |
|
defaultCacheMu.Lock() |
|
if defaultCache == nil { |
|
defaultCache = NewCache(CacheOptions{ |
|
// the cache will likely need to renew certificates, |
|
// so it will need to know how to do that, which |
|
// depends on the certificate being managed and which |
|
// can change during the lifetime of the cache; this |
|
// callback makes it possible to get the latest and |
|
// correct config with which to manage the cert, |
|
// but if the user does not provide one, we can only |
|
// assume that we are to use the default config |
|
GetConfigForCert: func(Certificate) (*Config, error) { |
|
return NewDefault(), nil |
|
}, |
|
}) |
|
} |
|
certCache := defaultCache |
|
defaultCacheMu.Unlock() |
|
|
|
return newWithCache(certCache, Default) |
|
} |
|
|
|
// New makes a new, valid config based on cfg and |
|
// uses the provided certificate cache. certCache |
|
// MUST NOT be nil or this function will panic. |
|
// |
|
// Use this method when you have an advanced use case |
|
// that requires a custom certificate cache and config |
|
// that may differ from the Default. For example, if |
|
// not all certificates are managed/renewed the same |
|
// way, you need to make your own Cache value with a |
|
// GetConfigForCert callback that returns the correct |
|
// configuration for each certificate. However, for |
|
// the vast majority of cases, there will be only a |
|
// single Config, thus the default cache (which always |
|
// uses the default Config) and default config will |
|
// suffice, and you should use NewDefault() instead. |
|
func New(certCache *Cache, cfg Config) *Config { |
|
if certCache == nil { |
|
panic("a certificate cache is required") |
|
} |
|
if certCache.options.GetConfigForCert == nil { |
|
panic("cache must have GetConfigForCert set in its options") |
|
} |
|
return newWithCache(certCache, cfg) |
|
} |
|
|
|
// newWithCache ensures that cfg is a valid config by populating |
|
// zero-value fields from the Default Config. If certCache is |
|
// nil, this function panics. |
|
func newWithCache(certCache *Cache, cfg Config) *Config { |
|
if certCache == nil { |
|
panic("cannot make a valid config without a pointer to a certificate cache") |
|
} |
|
|
|
if cfg.OnDemand == nil { |
|
cfg.OnDemand = Default.OnDemand |
|
} |
|
if cfg.RenewalWindowRatio == 0 { |
|
cfg.RenewalWindowRatio = Default.RenewalWindowRatio |
|
} |
|
if cfg.OnEvent == nil { |
|
cfg.OnEvent = Default.OnEvent |
|
} |
|
if cfg.KeySource == nil { |
|
cfg.KeySource = Default.KeySource |
|
} |
|
if cfg.DefaultServerName == "" { |
|
cfg.DefaultServerName = Default.DefaultServerName |
|
} |
|
if cfg.OnDemand == nil { |
|
cfg.OnDemand = Default.OnDemand |
|
} |
|
if !cfg.MustStaple { |
|
cfg.MustStaple = Default.MustStaple |
|
} |
|
if cfg.Storage == nil { |
|
cfg.Storage = Default.Storage |
|
} |
|
if len(cfg.Issuers) == 0 { |
|
cfg.Issuers = Default.Issuers |
|
if len(cfg.Issuers) == 0 { |
|
// at least one issuer is absolutely required |
|
cfg.Issuers = []Issuer{NewACMEManager(&cfg, DefaultACME)} |
|
} |
|
} |
|
|
|
// absolutely don't allow a nil storage, |
|
// because that would make almost anything |
|
// a config can do pointless |
|
if cfg.Storage == nil { |
|
cfg.Storage = defaultFileStorage |
|
} |
|
|
|
cfg.certCache = certCache |
|
|
|
return &cfg |
|
} |
|
|
|
// ManageSync causes the certificates for domainNames to be managed |
|
// according to cfg. If cfg.OnDemand is not nil, then this simply |
|
// whitelists the domain names and defers the certificate operations |
|
// to when they are needed. Otherwise, the certificates for each |
|
// name are loaded from storage or obtained from the CA. If loaded |
|
// from storage, they are renewed if they are expiring or expired. |
|
// It then caches the certificate in memory and is prepared to serve |
|
// them up during TLS handshakes. |
|
// |
|
// Note that name whitelisting for on-demand management only takes |
|
// effect if cfg.OnDemand.DecisionFunc is not set (is nil); it will |
|
// not overwrite an existing DecisionFunc, nor will it overwrite |
|
// its decision; i.e. the implicit whitelist is only used if no |
|
// DecisionFunc is set. |
|
// |
|
// This method is synchronous, meaning that certificates for all |
|
// domainNames must be successfully obtained (or renewed) before |
|
// it returns. It returns immediately on the first error for any |
|
// of the given domainNames. This behavior is recommended for |
|
// interactive use (i.e. when an administrator is present) so |
|
// that errors can be reported and fixed immediately. |
|
func (cfg *Config) ManageSync(domainNames []string) error { |
|
return cfg.manageAll(nil, domainNames, false) |
|
} |
|
|
|
// ClientCredentials returns a list of TLS client certificate chains for the given identifiers. |
|
// The return value can be used in a tls.Config to enable client authentication using managed certificates. |
|
// Any certificates that need to be obtained or renewed for these identifiers will be managed accordingly. |
|
func (cfg *Config) ClientCredentials(ctx context.Context, identifiers []string) ([]tls.Certificate, error) { |
|
err := cfg.manageAll(ctx, identifiers, false) |
|
if err != nil { |
|
return nil, err |
|
} |
|
var chains []tls.Certificate |
|
for _, id := range identifiers { |
|
certRes, err := cfg.loadCertResourceAnyIssuer(id) |
|
if err != nil { |
|
return chains, err |
|
} |
|
chain, err := tls.X509KeyPair(certRes.CertificatePEM, certRes.PrivateKeyPEM) |
|
if err != nil { |
|
return chains, err |
|
} |
|
chains = append(chains, chain) |
|
} |
|
return chains, nil |
|
} |
|
|
|
// ManageAsync is the same as ManageSync, except that ACME |
|
// operations are performed asynchronously (in the background). |
|
// This method returns before certificates are ready. It is |
|
// crucial that the administrator monitors the logs and is |
|
// notified of any errors so that corrective action can be |
|
// taken as soon as possible. Any errors returned from this |
|
// method occurred before ACME transactions started. |
|
// |
|
// As long as logs are monitored, this method is typically |
|
// recommended for non-interactive environments. |
|
// |
|
// If there are failures loading, obtaining, or renewing a |
|
// certificate, it will be retried with exponential backoff |
|
// for up to about 30 days, with a maximum interval of about |
|
// 24 hours. Cancelling ctx will cancel retries and shut down |
|
// any goroutines spawned by ManageAsync. |
|
func (cfg *Config) ManageAsync(ctx context.Context, domainNames []string) error { |
|
return cfg.manageAll(ctx, domainNames, true) |
|
} |
|
|
|
func (cfg *Config) manageAll(ctx context.Context, domainNames []string, async bool) error { |
|
if ctx == nil { |
|
ctx = context.Background() |
|
} |
|
|
|
for _, domainName := range domainNames { |
|
// if on-demand is configured, defer obtain and renew operations |
|
if cfg.OnDemand != nil { |
|
if !cfg.OnDemand.whitelistContains(domainName) { |
|
cfg.OnDemand.hostWhitelist = append(cfg.OnDemand.hostWhitelist, domainName) |
|
} |
|
continue |
|
} |
|
|
|
// otherwise, begin management immediately |
|
err := cfg.manageOne(ctx, domainName, async) |
|
if err != nil { |
|
return err |
|
} |
|
} |
|
|
|
return nil |
|
} |
|
|
|
func (cfg *Config) manageOne(ctx context.Context, domainName string, async bool) error { |
|
// first try loading existing certificate from storage |
|
cert, err := cfg.CacheManagedCertificate(domainName) |
|
if err != nil { |
|
if _, ok := err.(ErrNotExist); !ok { |
|
return fmt.Errorf("%s: caching certificate: %v", domainName, err) |
|
} |
|
// if we don't have one in storage, obtain one |
|
obtain := func() error { |
|
err := cfg.ObtainCert(ctx, domainName, !async) |
|
if err != nil { |
|
return fmt.Errorf("%s: obtaining certificate: %w", domainName, err) |
|
} |
|
cert, err = cfg.CacheManagedCertificate(domainName) |
|
if err != nil { |
|
return fmt.Errorf("%s: caching certificate after obtaining it: %v", domainName, err) |
|
} |
|
return nil |
|
} |
|
if async { |
|
// Leave the job name empty so as to allow duplicate 'obtain' |
|
// jobs; this is because Caddy calls ManageAsync() before the |
|
// previous config is stopped (and before its context is |
|
// canceled), which means that if an obtain job is still |
|
// running for the same domain, Submit() would not queue the |
|
// new one because it is still running, even though it is |
|
// (probably) about to be canceled (it might not if the new |
|
// config fails to finish loading, however). In any case, we |
|
// presume it is safe to enqueue a duplicate obtain job because |
|
// either the old one (or sometimes the new one) is about to be |
|
// canceled. This seems like reasonable logic for any consumer |
|
// of this lib. See https://github.com/caddyserver/caddy/issues/3202 |
|
jm.Submit(cfg.Logger, "", obtain) |
|
return nil |
|
} |
|
return obtain() |
|
} |
|
|
|
// for an existing certificate, make sure it is renewed |
|
renew := func() error { |
|
err := cfg.RenewCert(ctx, domainName, !async) |
|
if err != nil { |
|
return fmt.Errorf("%s: renewing certificate: %w", domainName, err) |
|
} |
|
// successful renewal, so update in-memory cache |
|
err = cfg.reloadManagedCertificate(cert) |
|
if err != nil { |
|
return fmt.Errorf("%s: reloading renewed certificate into memory: %v", domainName, err) |
|
} |
|
return nil |
|
} |
|
if cert.NeedsRenewal(cfg) { |
|
if async { |
|
jm.Submit(cfg.Logger, "renew_"+domainName, renew) |
|
return nil |
|
} |
|
return renew() |
|
} |
|
|
|
return nil |
|
} |
|
|
|
// Unmanage causes the certificates for domainNames to stop being managed. |
|
// If there are certificates for the supplied domain names in the cache, they |
|
// are evicted from the cache. |
|
func (cfg *Config) Unmanage(domainNames []string) { |
|
var deleteQueue []Certificate |
|
for _, domainName := range domainNames { |
|
certs := cfg.certCache.AllMatchingCertificates(domainName) |
|
for _, cert := range certs { |
|
if !cert.managed { |
|
continue |
|
} |
|
deleteQueue = append(deleteQueue, cert) |
|
} |
|
} |
|
|
|
cfg.certCache.mu.Lock() |
|
for _, cert := range deleteQueue { |
|
cfg.certCache.removeCertificate(cert) |
|
} |
|
cfg.certCache.mu.Unlock() |
|
} |
|
|
|
// ObtainCert obtains a certificate for name using cfg, as long |
|
// as a certificate does not already exist in storage for that |
|
// name. The name must qualify and cfg must be flagged as Managed. |
|
// This function is a no-op if storage already has a certificate |
|
// for name. |
|
// |
|
// It only obtains and stores certificates (and their keys), |
|
// it does not load them into memory. If interactive is true, |
|
// the user may be shown a prompt. |
|
// TODO: consider moving interactive param into the Config struct, |
|
// and maybe retry settings into the Config struct as well? (same for RenewCert) |
|
func (cfg *Config) ObtainCert(ctx context.Context, name string, interactive bool) error { |
|
if len(cfg.Issuers) == 0 { |
|
return fmt.Errorf("no issuers configured; impossible to obtain or check for existing certificate in storage") |
|
} |
|
if cfg.storageHasCertResourcesAnyIssuer(name) { |
|
return nil |
|
} |
|
// ensure storage is writeable and readable |
|
// TODO: this is not necessary every time; should only perform check once every so often for each storage, which may require some global state... |
|
err := cfg.checkStorage() |
|
if err != nil { |
|
return fmt.Errorf("failed storage check: %v - storage is probably misconfigured", err) |
|
} |
|
return cfg.obtainCert(ctx, name, interactive) |
|
} |
|
|
|
func (cfg *Config) obtainCert(ctx context.Context, name string, interactive bool) error { |
|
log := loggerNamed(cfg.Logger, "obtain") |
|
|
|
if log != nil { |
|
log.Info("acquiring lock", zap.String("identifier", name)) |
|
} |
|
|
|
// ensure idempotency of the obtain operation for this name |
|
lockKey := cfg.lockKey(certIssueLockOp, name) |
|
err := acquireLock(ctx, cfg.Storage, lockKey) |
|
if err != nil { |
|
return fmt.Errorf("unable to acquire lock '%s': %v", lockKey, err) |
|
} |
|
defer func() { |
|
if log != nil { |
|
log.Info("releasing lock", zap.String("identifier", name)) |
|
} |
|
if err := releaseLock(cfg.Storage, lockKey); err != nil { |
|
if log != nil { |
|
log.Error("unable to unlock", |
|
zap.String("identifier", name), |
|
zap.String("lock_key", lockKey), |
|
zap.Error(err)) |
|
} |
|
} |
|
}() |
|
if log != nil { |
|
log.Info("lock acquired", zap.String("identifier", name)) |
|
} |
|
|
|
f := func(ctx context.Context) error { |
|
// check if obtain is still needed -- might have been obtained during lock |
|
if cfg.storageHasCertResourcesAnyIssuer(name) { |
|
if log != nil { |
|
log.Info("certificate already exists in storage", zap.String("identifier", name)) |
|
} |
|
return nil |
|
} |
|
|
|
privateKey, err := cfg.KeySource.GenerateKey() |
|
if err != nil { |
|
return err |
|
} |
|
privKeyPEM, err := encodePrivateKey(privateKey) |
|
if err != nil { |
|
return err |
|
} |
|
|
|
csr, err := cfg.generateCSR(privateKey, []string{name}) |
|
if err != nil { |
|
return err |
|
} |
|
|
|
// try to obtain from each issuer until we succeed |
|
var issuedCert *IssuedCertificate |
|
var issuerUsed Issuer |
|
for _, issuer := range cfg.Issuers { |
|
if prechecker, ok := issuer.(PreChecker); ok { |
|
err = prechecker.PreCheck(ctx, []string{name}, interactive) |
|
if err != nil { |
|
continue |
|
} |
|
} |
|
issuedCert, err = issuer.Issue(ctx, csr) |
|
if err == nil { |
|
issuerUsed = issuer |
|
break |
|
} |
|
} |
|
if err != nil { |
|
// TODO: only the error from the last issuer will be returned, oh well? |
|
return fmt.Errorf("[%s] Obtain: %w", name, err) |
|
} |
|
|
|
// success - immediately save the certificate resource |
|
certRes := CertificateResource{ |
|
SANs: namesFromCSR(csr), |
|
CertificatePEM: issuedCert.Certificate, |
|
PrivateKeyPEM: privKeyPEM, |
|
IssuerData: issuedCert.Metadata, |
|
} |
|
err = cfg.saveCertResource(issuerUsed, certRes) |
|
if err != nil { |
|
return fmt.Errorf("[%s] Obtain: saving assets: %v", name, err) |
|
} |
|
|
|
cfg.emit("cert_obtained", name) |
|
|
|
if log != nil { |
|
log.Info("certificate obtained successfully", zap.String("identifier", name)) |
|
} |
|
|
|
return nil |
|
} |
|
|
|
if interactive { |
|
err = f(ctx) |
|
} else { |
|
err = doWithRetry(ctx, log, f) |
|
} |
|
|
|
return err |
|
} |
|
|
|
func (cfg *Config) storageHasCertResourcesAnyIssuer(name string) bool { |
|
for _, iss := range cfg.Issuers { |
|
if cfg.storageHasCertResources(iss, name) { |
|
return true |
|
} |
|
} |
|
return false |
|
} |
|
|
|
// RenewCert renews the certificate for name using cfg. It stows the |
|
// renewed certificate and its assets in storage if successful. It |
|
// DOES NOT update the in-memory cache with the new certificate. |
|
func (cfg *Config) RenewCert(ctx context.Context, name string, interactive bool) error { |
|
if len(cfg.Issuers) == 0 { |
|
return fmt.Errorf("no issuers configured; impossible to renew or check existing certificate in storage") |
|
} |
|
// ensure storage is writeable and readable |
|
// TODO: this is not necessary every time; should only perform check once every so often for each storage, which may require some global state... |
|
err := cfg.checkStorage() |
|
if err != nil { |
|
return fmt.Errorf("failed storage check: %v - storage is probably misconfigured", err) |
|
} |
|
return cfg.renewCert(ctx, name, interactive) |
|
} |
|
|
|
func (cfg *Config) renewCert(ctx context.Context, name string, interactive bool) error { |
|
log := loggerNamed(cfg.Logger, "renew") |
|
|
|
if log != nil { |
|
log.Info("acquiring lock", zap.String("identifier", name)) |
|
} |
|
|
|
// ensure idempotency of the renew operation for this name |
|
lockKey := cfg.lockKey(certIssueLockOp, name) |
|
err := acquireLock(ctx, cfg.Storage, lockKey) |
|
if err != nil { |
|
return fmt.Errorf("unable to acquire lock '%s': %v", lockKey, err) |
|
} |
|
defer func() { |
|
if log != nil { |
|
log.Info("releasing lock", zap.String("identifier", name)) |
|
} |
|
if err := releaseLock(cfg.Storage, lockKey); err != nil { |
|
if log != nil { |
|
log.Error("unable to unlock", |
|
zap.String("identifier", name), |
|
zap.String("lock_key", lockKey), |
|
zap.Error(err)) |
|
} |
|
} |
|
}() |
|
if log != nil { |
|
log.Info("lock acquired", zap.String("identifier", name)) |
|
} |
|
|
|
f := func(ctx context.Context) error { |
|
// prepare for renewal (load PEM cert, key, and meta) |
|
certRes, err := cfg.loadCertResourceAnyIssuer(name) |
|
if err != nil { |
|
return err |
|
} |
|
|
|
// check if renew is still needed - might have been renewed while waiting for lock |
|
timeLeft, needsRenew := cfg.managedCertNeedsRenewal(certRes) |
|
if !needsRenew { |
|
if log != nil { |
|
log.Info("certificate appears to have been renewed already", |
|
zap.String("identifier", name), |
|
zap.Duration("remaining", timeLeft)) |
|
} |
|
return nil |
|
} |
|
if log != nil { |
|
log.Info("renewing certificate", |
|
zap.String("identifier", name), |
|
zap.Duration("remaining", timeLeft)) |
|
} |
|
|
|
privateKey, err := decodePrivateKey(certRes.PrivateKeyPEM) |
|
if err != nil { |
|
return err |
|
} |
|
csr, err := cfg.generateCSR(privateKey, []string{name}) |
|
if err != nil { |
|
return err |
|
} |
|
|
|
// try to obtain from each issuer until we succeed |
|
var issuedCert *IssuedCertificate |
|
var issuerUsed Issuer |
|
for _, issuer := range cfg.Issuers { |
|
if prechecker, ok := issuer.(PreChecker); ok { |
|
err = prechecker.PreCheck(ctx, []string{name}, interactive) |
|
if err != nil { |
|
continue |
|
} |
|
} |
|
issuedCert, err = issuer.Issue(ctx, csr) |
|
if err == nil { |
|
issuerUsed = issuer |
|
break |
|
} |
|
} |
|
if err != nil { |
|
// TODO: only the error from the last issuer will be returned, oh well? |
|
return fmt.Errorf("[%s] Renew: %w", name, err) |
|
} |
|
|
|
// success - immediately save the renewed certificate resource |
|
newCertRes := CertificateResource{ |
|
SANs: namesFromCSR(csr), |
|
CertificatePEM: issuedCert.Certificate, |
|
PrivateKeyPEM: certRes.PrivateKeyPEM, |
|
IssuerData: issuedCert.Metadata, |
|
} |
|
err = cfg.saveCertResource(issuerUsed, newCertRes) |
|
if err != nil { |
|
return fmt.Errorf("[%s] Renew: saving assets: %v", name, err) |
|
} |
|
|
|
cfg.emit("cert_renewed", name) |
|
|
|
if log != nil { |
|
log.Info("certificate renewed successfully", zap.String("identifier", name)) |
|
} |
|
|
|
return nil |
|
} |
|
|
|
if interactive { |
|
err = f(ctx) |
|
} else { |
|
err = doWithRetry(ctx, log, f) |
|
} |
|
|
|
return err |
|
} |
|
|
|
func (cfg *Config) generateCSR(privateKey crypto.PrivateKey, sans []string) (*x509.CertificateRequest, error) { |
|
csrTemplate := new(x509.CertificateRequest) |
|
|
|
for _, name := range sans { |
|
if ip := net.ParseIP(name); ip != nil { |
|
csrTemplate.IPAddresses = append(csrTemplate.IPAddresses, ip) |
|
} else if strings.Contains(name, "@") { |
|
csrTemplate.EmailAddresses = append(csrTemplate.EmailAddresses, name) |
|
} else if u, err := url.Parse(name); err == nil && strings.Contains(name, "/") { |
|
csrTemplate.URIs = append(csrTemplate.URIs, u) |
|
} else { |
|
// convert IDNs to ASCII according to RFC 5280 section 7 |
|
normalizedName, err := idna.ToASCII(name) |
|
if err != nil { |
|
return nil, fmt.Errorf("converting identifier '%s' to ASCII: %v", name, err) |
|
} |
|
csrTemplate.DNSNames = append(csrTemplate.DNSNames, normalizedName) |
|
} |
|
} |
|
|
|
if cfg.MustStaple { |
|
csrTemplate.ExtraExtensions = append(csrTemplate.ExtraExtensions, mustStapleExtension) |
|
} |
|
|
|
csrDER, err := x509.CreateCertificateRequest(rand.Reader, csrTemplate, privateKey) |
|
if err != nil { |
|
return nil, err |
|
} |
|
|
|
return x509.ParseCertificateRequest(csrDER) |
|
} |
|
|
|
// RevokeCert revokes the certificate for domain via ACME protocol. It requires |
|
// that cfg.Issuers is properly configured with the same issuer that issued the |
|
// certificate being revoked. See RFC 5280 §5.3.1 for reason codes. |
|
func (cfg *Config) RevokeCert(ctx context.Context, domain string, reason int, interactive bool) error { |
|
for i, issuer := range cfg.Issuers { |
|
issuerKey := issuer.IssuerKey() |
|
|
|
rev, ok := issuer.(Revoker) |
|
if !ok { |
|
return fmt.Errorf("issuer %d (%s) is not a Revoker", i, issuerKey) |
|
} |
|
|
|
certRes, err := cfg.loadCertResource(issuer, domain) |
|
if err != nil { |
|
return err |
|
} |
|
|
|
if !cfg.Storage.Exists(StorageKeys.SitePrivateKey(issuerKey, domain)) { |
|
return fmt.Errorf("private key not found for %s", certRes.SANs) |
|
} |
|
|
|
err = rev.Revoke(ctx, certRes, reason) |
|
if err != nil { |
|
return fmt.Errorf("issuer %d (%s): %v", i, issuerKey, err) |
|
} |
|
|
|
cfg.emit("cert_revoked", domain) |
|
|
|
err = cfg.Storage.Delete(StorageKeys.SiteCert(issuerKey, domain)) |
|
if err != nil { |
|
return fmt.Errorf("certificate revoked, but unable to delete certificate file: %v", err) |
|
} |
|
err = cfg.Storage.Delete(StorageKeys.SitePrivateKey(issuerKey, domain)) |
|
if err != nil { |
|
return fmt.Errorf("certificate revoked, but unable to delete private key: %v", err) |
|
} |
|
err = cfg.Storage.Delete(StorageKeys.SiteMeta(issuerKey, domain)) |
|
if err != nil { |
|
return fmt.Errorf("certificate revoked, but unable to delete certificate metadata: %v", err) |
|
} |
|
} |
|
|
|
return nil |
|
} |
|
|
|
// TLSConfig is an opinionated method that returns a |
|
// recommended, modern TLS configuration that can be |
|
// used to configure TLS listeners, which also supports |
|
// the TLS-ALPN challenge and serves up certificates |
|
// managed by cfg. |
|
// |
|
// Unlike the package TLS() function, this method does |
|
// not, by itself, enable certificate management for |
|
// any domain names. |
|
// |
|
// Feel free to further customize the returned tls.Config, |
|
// but do not mess with the GetCertificate or NextProtos |
|
// fields unless you know what you're doing, as they're |
|
// necessary to solve the TLS-ALPN challenge. |
|
func (cfg *Config) TLSConfig() *tls.Config { |
|
return &tls.Config{ |
|
// these two fields necessary for TLS-ALPN challenge |
|
GetCertificate: cfg.GetCertificate, |
|
NextProtos: []string{acmez.ACMETLS1Protocol}, |
|
|
|
// the rest recommended for modern TLS servers |
|
MinVersion: tls.VersionTLS12, |
|
CurvePreferences: []tls.CurveID{ |
|
tls.X25519, |
|
tls.CurveP256, |
|
}, |
|
CipherSuites: preferredDefaultCipherSuites(), |
|
PreferServerCipherSuites: true, |
|
} |
|
} |
|
|
|
// getChallengeInfo loads the challenge info from either the internal challenge memory |
|
// or the external storage (implying distributed solving). The second return value |
|
// indicates whether challenge info was loaded from external storage. If true, the |
|
// challenge is being solved in a distributed fashion; if false, from internal memory. |
|
// If no matching challenge information can be found, an error is returned. |
|
func (cfg *Config) getChallengeInfo(identifier string) (Challenge, bool, error) { |
|
// first, check if our process initiated this challenge; if so, just return it |
|
chalData, ok := GetACMEChallenge(identifier) |
|
if ok { |
|
return chalData, false, nil |
|
} |
|
|
|
// otherwise, perhaps another instance in the cluster initiated it; check |
|
// the configured storage to retrieve challenge data |
|
|
|
var chalInfo acme.Challenge |
|
var chalInfoBytes []byte |
|
var tokenKey string |
|
for _, issuer := range cfg.Issuers { |
|
ds := distributedSolver{ |
|
storage: cfg.Storage, |
|
storageKeyIssuerPrefix: storageKeyACMECAPrefix(issuer.IssuerKey()), |
|
} |
|
tokenKey = ds.challengeTokensKey(identifier) |
|
var err error |
|
chalInfoBytes, err = cfg.Storage.Load(tokenKey) |
|
if err == nil { |
|
break |
|
} |
|
if _, ok := err.(ErrNotExist); ok { |
|
continue |
|
} |
|
return Challenge{}, false, fmt.Errorf("opening distributed challenge token file %s: %v", tokenKey, err) |
|
} |
|
if len(chalInfoBytes) == 0 { |
|
return Challenge{}, false, fmt.Errorf("no information found to solve challenge for identifier: %s", identifier) |
|
} |
|
|
|
err := json.Unmarshal(chalInfoBytes, &chalInfo) |
|
if err != nil { |
|
return Challenge{}, false, fmt.Errorf("decoding challenge token file %s (corrupted?): %v", tokenKey, err) |
|
} |
|
|
|
return Challenge{Challenge: chalInfo}, true, nil |
|
} |
|
|
|
// checkStorage tests the storage by writing random bytes |
|
// to a random key, and then loading those bytes and |
|
// comparing the loaded value. If this fails, the provided |
|
// cfg.Storage mechanism should not be used. |
|
func (cfg *Config) checkStorage() error { |
|
key := fmt.Sprintf("rw_test_%d", weakrand.Int()) |
|
contents := make([]byte, 1024*10) // size sufficient for one or two ACME resources |
|
_, err := weakrand.Read(contents) |
|
if err != nil { |
|
return err |
|
} |
|
err = cfg.Storage.Store(key, contents) |
|
if err != nil { |
|
return err |
|
} |
|
defer func() { |
|
deleteErr := cfg.Storage.Delete(key) |
|
if deleteErr != nil { |
|
if cfg.Logger != nil { |
|
cfg.Logger.Error("deleting test key from storage", |
|
zap.String("key", key), zap.Error(err)) |
|
} |
|
} |
|
// if there was no other error, make sure |
|
// to return any error returned from Delete |
|
if err == nil { |
|
err = deleteErr |
|
} |
|
}() |
|
loaded, err := cfg.Storage.Load(key) |
|
if err != nil { |
|
return err |
|
} |
|
if !bytes.Equal(contents, loaded) { |
|
return fmt.Errorf("load yielded different value than was stored; expected %d bytes, got %d bytes of differing elements", len(contents), len(loaded)) |
|
} |
|
return nil |
|
} |
|
|
|
// storageHasCertResources returns true if the storage |
|
// associated with cfg's certificate cache has all the |
|
// resources related to the certificate for domain: the |
|
// certificate, the private key, and the metadata. |
|
func (cfg *Config) storageHasCertResources(issuer Issuer, domain string) bool { |
|
issuerKey := issuer.IssuerKey() |
|
certKey := StorageKeys.SiteCert(issuerKey, domain) |
|
keyKey := StorageKeys.SitePrivateKey(issuerKey, domain) |
|
metaKey := StorageKeys.SiteMeta(issuerKey, domain) |
|
return cfg.Storage.Exists(certKey) && |
|
cfg.Storage.Exists(keyKey) && |
|
cfg.Storage.Exists(metaKey) |
|
} |
|
|
|
// lockKey returns a key for a lock that is specific to the operation |
|
// named op being performed related to domainName and this config's CA. |
|
func (cfg *Config) lockKey(op, domainName string) string { |
|
return fmt.Sprintf("%s_%s", op, domainName) |
|
} |
|
|
|
// managedCertNeedsRenewal returns true if certRes is expiring soon or already expired, |
|
// or if the process of decoding the cert and checking its expiration returned an error. |
|
func (cfg *Config) managedCertNeedsRenewal(certRes CertificateResource) (time.Duration, bool) { |
|
certChain, err := parseCertsFromPEMBundle(certRes.CertificatePEM) |
|
if err != nil { |
|
return 0, true |
|
} |
|
remaining := time.Until(certChain[0].NotAfter) |
|
needsRenew := currentlyInRenewalWindow(certChain[0].NotBefore, certChain[0].NotAfter, cfg.RenewalWindowRatio) |
|
return remaining, needsRenew |
|
} |
|
|
|
func (cfg *Config) emit(eventName string, data interface{}) { |
|
if cfg.OnEvent == nil { |
|
return |
|
} |
|
cfg.OnEvent(eventName, data) |
|
} |
|
|
|
func loggerNamed(l *zap.Logger, name string) *zap.Logger { |
|
if l == nil { |
|
return nil |
|
} |
|
return l.Named(name) |
|
} |
|
|
|
// CertificateSelector is a type which can select a certificate to use given multiple choices. |
|
type CertificateSelector interface { |
|
SelectCertificate(*tls.ClientHelloInfo, []Certificate) (Certificate, error) |
|
} |
|
|
|
// OCSPConfig configures how OCSP is handled. |
|
type OCSPConfig struct { |
|
// Disable automatic OCSP stapling; strongly |
|
// discouraged unless you have a good reason. |
|
// Disabling this puts clients at greater risk |
|
// and reduces their privacy. |
|
DisableStapling bool |
|
|
|
// A map of OCSP responder domains to replacement |
|
// domains for querying OCSP servers. Used for |
|
// overriding the OCSP responder URL that is |
|
// embedded in certificates. Mapping to an empty |
|
// URL will disable OCSP from that responder. |
|
ResponderOverrides map[string]string |
|
} |
|
|
|
// certIssueLockOp is the name of the operation used |
|
// when naming a lock to make it mutually exclusive |
|
// with other certificate issuance operations for a |
|
// certain name. |
|
const certIssueLockOp = "issue_cert" |
|
|
|
// Constants for PKIX MustStaple extension. |
|
var ( |
|
tlsFeatureExtensionOID = asn1.ObjectIdentifier{1, 3, 6, 1, 5, 5, 7, 1, 24} |
|
ocspMustStapleFeature = []byte{0x30, 0x03, 0x02, 0x01, 0x05} |
|
mustStapleExtension = pkix.Extension{ |
|
Id: tlsFeatureExtensionOID, |
|
Value: ocspMustStapleFeature, |
|
} |
|
)
|
|
|