diff options
author | 6543 <6543@obermui.de> | 2021-04-22 22:42:33 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2021-04-22 22:42:33 +0200 |
commit | 8ea1d32bea88b6968774b14e5bbe90f2280fe997 (patch) | |
tree | ce28cbc996df6c428b6ac03773714b46650a809e /vendor/github.com | |
parent | e7fc078891d41798703b66bed8bdbf55e8b7c5e1 (diff) | |
download | gitea-8ea1d32bea88b6968774b14e5bbe90f2280fe997.tar.gz gitea-8ea1d32bea88b6968774b14e5bbe90f2280fe997.zip |
[Vendor] update certmagic (#15590)
* update github.com/caddyserver/certmagic v0.12.0 -> v0.13.0
* migrate
Diffstat (limited to 'vendor/github.com')
39 files changed, 1294 insertions, 667 deletions
diff --git a/vendor/github.com/caddyserver/certmagic/README.md b/vendor/github.com/caddyserver/certmagic/README.md index 0bd6c55678..19aa9f8919 100644 --- a/vendor/github.com/caddyserver/certmagic/README.md +++ b/vendor/github.com/caddyserver/certmagic/README.md @@ -260,7 +260,7 @@ magic := certmagic.New(cache, certmagic.Config{ // any customizations you need go here }) -myACME := certmagic.NewACMEManager(magic, ACMEManager{ +myACME := certmagic.NewACMEManager(magic, certmagic.ACMEManager{ CA: certmagic.LetsEncryptStagingCA, Email: "you@yours.com", Agreed: true, @@ -285,7 +285,7 @@ tlsConfig := magic.TLSConfig() // we can simply set its GetCertificate field and append the // TLS-ALPN challenge protocol to the NextProtos myTLSConfig.GetCertificate = magic.GetCertificate -myTLSConfig.NextProtos = append(myTLSConfig.NextProtos, tlsalpn01.ACMETLS1Protocol} +myTLSConfig.NextProtos = append(myTLSConfig.NextProtos, tlsalpn01.ACMETLS1Protocol) // the HTTP challenge has to be handled by your HTTP server; // if you don't have one, you should have disabled it earlier @@ -394,7 +394,7 @@ To enable it, just set the `DNS01Solver` field on a `certmagic.ACMEManager` stru import "github.com/libdns/cloudflare" certmagic.DefaultACME.DNS01Solver = &certmagic.DNS01Solver{ - DNSProvider: cloudflare.Provider{ + DNSProvider: &cloudflare.Provider{ APIToken: "topsecret", }, } diff --git a/vendor/github.com/caddyserver/certmagic/account.go b/vendor/github.com/caddyserver/certmagic/account.go index e1ebb32a04..d7c3841a7e 100644 --- a/vendor/github.com/caddyserver/certmagic/account.go +++ b/vendor/github.com/caddyserver/certmagic/account.go @@ -16,6 +16,8 @@ package certmagic import ( "bufio" + "bytes" + "context" "crypto/ecdsa" "crypto/elliptic" "crypto/rand" @@ -33,18 +35,24 @@ import ( // getAccount either loads or creates a new account, depending on if // an account can be found in storage for the given CA + email combo. func (am *ACMEManager) getAccount(ca, email string) (acme.Account, error) { - regBytes, err := am.config.Storage.Load(am.storageKeyUserReg(ca, email)) + acct, err := am.loadAccount(ca, email) if err != nil { if _, ok := err.(ErrNotExist); ok { return am.newAccount(email) } + return acct, err + } + return acct, err +} + +// loadAccount loads an account from storage, but does not create a new one. +func (am *ACMEManager) loadAccount(ca, email string) (acme.Account, error) { + regBytes, err := am.config.Storage.Load(am.storageKeyUserReg(ca, email)) + if err != nil { return acme.Account{}, err } keyBytes, err := am.config.Storage.Load(am.storageKeyUserPrivateKey(ca, email)) if err != nil { - if _, ok := err.(ErrNotExist); ok { - return am.newAccount(email) - } return acme.Account{}, err } @@ -58,54 +66,6 @@ func (am *ACMEManager) getAccount(ca, email string) (acme.Account, error) { return acct, fmt.Errorf("could not decode account's private key: %v", err) } - // TODO: July 2020 - transition to new ACME lib and account structure; - // for a while, we will need to convert old accounts to new structure - acct, err = am.transitionAccountToACMEzJuly2020Format(ca, acct, regBytes) - if err != nil { - return acct, fmt.Errorf("one-time account transition: %v", err) - } - - return acct, err -} - -// TODO: this is a temporary transition helper starting July 2020. -// It can go away when we think enough time has passed that most active assets have transitioned. -func (am *ACMEManager) transitionAccountToACMEzJuly2020Format(ca string, acct acme.Account, regBytes []byte) (acme.Account, error) { - if acct.Status != "" && acct.Location != "" { - return acct, nil - } - - var oldAcct struct { - Email string `json:"Email"` - Registration struct { - Body struct { - Status string `json:"status"` - TermsOfServiceAgreed bool `json:"termsOfServiceAgreed"` - Orders string `json:"orders"` - ExternalAccountBinding json.RawMessage `json:"externalAccountBinding"` - } `json:"body"` - URI string `json:"uri"` - } `json:"Registration"` - } - err := json.Unmarshal(regBytes, &oldAcct) - if err != nil { - return acct, fmt.Errorf("decoding into old account type: %v", err) - } - - acct.Status = oldAcct.Registration.Body.Status - acct.TermsOfServiceAgreed = oldAcct.Registration.Body.TermsOfServiceAgreed - acct.Location = oldAcct.Registration.URI - acct.ExternalAccountBinding = oldAcct.Registration.Body.ExternalAccountBinding - acct.Orders = oldAcct.Registration.Body.Orders - if oldAcct.Email != "" { - acct.Contact = []string{"mailto:" + oldAcct.Email} - } - - err = am.saveAccount(ca, acct) - if err != nil { - return acct, fmt.Errorf("saving converted account: %v", err) - } - return acct, nil } @@ -124,6 +84,71 @@ func (*ACMEManager) newAccount(email string) (acme.Account, error) { return acct, nil } +// GetAccount first tries loading the account with the associated private key from storage. +// If it does not exist in storage, it will be retrieved from the ACME server and added to storage. +// The account must already exist; it does not create a new account. +func (am *ACMEManager) GetAccount(ctx context.Context, privateKeyPEM []byte) (acme.Account, error) { + account, err := am.loadAccountByKey(ctx, privateKeyPEM) + if err != nil { + if _, ok := err.(ErrNotExist); ok { + account, err = am.lookUpAccount(ctx, privateKeyPEM) + } else { + return account, err + } + } + return account, err +} + +// loadAccountByKey loads the account with the given private key from storage, if it exists. +// If it does not exist, an error of type ErrNotExist is returned. This is not very efficient +// for lots of accounts. +func (am *ACMEManager) loadAccountByKey(ctx context.Context, privateKeyPEM []byte) (acme.Account, error) { + accountList, err := am.config.Storage.List(am.storageKeyUsersPrefix(am.CA), false) + if err != nil { + return acme.Account{}, err + } + for _, accountFolderKey := range accountList { + email := path.Base(accountFolderKey) + keyBytes, err := am.config.Storage.Load(am.storageKeyUserPrivateKey(am.CA, email)) + if err != nil { + return acme.Account{}, err + } + if bytes.Equal(bytes.TrimSpace(keyBytes), bytes.TrimSpace(privateKeyPEM)) { + return am.loadAccount(am.CA, email) + } + } + return acme.Account{}, ErrNotExist(fmt.Errorf("no account found with that key")) +} + +// lookUpAccount looks up the account associated with privateKeyPEM from the ACME server. +// If the account is found by the server, it will be saved to storage and returned. +func (am *ACMEManager) lookUpAccount(ctx context.Context, privateKeyPEM []byte) (acme.Account, error) { + client, err := am.newACMEClient(false) + if err != nil { + return acme.Account{}, fmt.Errorf("creating ACME client: %v", err) + } + + privateKey, err := decodePrivateKey([]byte(privateKeyPEM)) + if err != nil { + return acme.Account{}, fmt.Errorf("decoding private key: %v", err) + } + + // look up the account + account := acme.Account{PrivateKey: privateKey} + account, err = client.GetAccount(ctx, account) + if err != nil { + return acme.Account{}, fmt.Errorf("looking up account with server: %v", err) + } + + // save the account details to storage + err = am.saveAccount(client.Directory, account) + if err != nil { + return account, fmt.Errorf("could not save account to storage: %v", err) + } + + return account, nil +} + // saveAccount persists an ACME account's info and private key to storage. // It does NOT register the account via ACME or prompt the user. func (am *ACMEManager) saveAccount(ca string, account acme.Account) error { @@ -242,8 +267,12 @@ func (am *ACMEManager) askUserAgreement(agreementURL string) bool { return answer == "y" || answer == "yes" } +func storageKeyACMECAPrefix(issuerKey string) string { + return path.Join(prefixACME, StorageKeys.Safe(issuerKey)) +} + func (am *ACMEManager) storageKeyCAPrefix(caURL string) string { - return path.Join(prefixACME, StorageKeys.Safe(am.issuerKey(caURL))) + return storageKeyACMECAPrefix(am.issuerKey(caURL)) } func (am *ACMEManager) storageKeyUsersPrefix(caURL string) string { @@ -305,7 +334,8 @@ func (am *ACMEManager) mostRecentAccountEmail(caURL string) (string, bool) { // get all the key infos ahead of sorting, because // we might filter some out stats := make(map[string]KeyInfo) - for i, u := range accountList { + for i := 0; i < len(accountList); i++ { + u := accountList[i] keyInfo, err := am.config.Storage.Stat(u) if err != nil { continue @@ -318,6 +348,7 @@ func (am *ACMEManager) mostRecentAccountEmail(caURL string) (string, bool) { // frankly one's OS shouldn't mess with the data folder // in the first place. accountList = append(accountList[:i], accountList[i+1:]...) + i-- continue } stats[u] = keyInfo diff --git a/vendor/github.com/caddyserver/certmagic/acmeclient.go b/vendor/github.com/caddyserver/certmagic/acmeclient.go index 342b222d66..8a61a08255 100644 --- a/vendor/github.com/caddyserver/certmagic/acmeclient.go +++ b/vendor/github.com/caddyserver/certmagic/acmeclient.go @@ -37,19 +37,104 @@ func init() { weakrand.Seed(time.Now().UnixNano()) } -// acmeClient holds state necessary for us to perform -// ACME operations for certificate management. Call -// ACMEManager.newACMEClient() to get a valid one to . +// acmeClient holds state necessary to perform ACME operations +// for certificate management with an ACME account. Call +// ACMEManager.newACMEClientWithAccount() to get a valid one. type acmeClient struct { mgr *ACMEManager acmeClient *acmez.Client account acme.Account } -// newACMEClient creates the underlying ACME library client type. -// If useTestCA is true, am.TestCA will be used if it is set; -// otherwise, the primary CA will still be used. -func (am *ACMEManager) newACMEClient(ctx context.Context, useTestCA, interactive bool) (*acmeClient, error) { +// newACMEClientWithAccount creates an ACME client ready to use with an account, including +// loading one from storage or registering a new account with the CA if necessary. If +// useTestCA is true, am.TestCA will be used if set; otherwise, the primary CA will be used. +func (am *ACMEManager) newACMEClientWithAccount(ctx context.Context, useTestCA, interactive bool) (*acmeClient, error) { + // first, get underlying ACME client + client, err := am.newACMEClient(useTestCA) + if err != nil { + return nil, err + } + + // look up or create the ACME account + var account acme.Account + if am.AccountKeyPEM != "" { + account, err = am.GetAccount(ctx, []byte(am.AccountKeyPEM)) + } else { + account, err = am.getAccount(client.Directory, am.Email) + } + if err != nil { + return nil, fmt.Errorf("getting ACME account: %v", err) + } + + // register account if it is new + if account.Status == "" { + if am.NewAccountFunc != nil { + account, err = am.NewAccountFunc(ctx, am, account) + if err != nil { + return nil, fmt.Errorf("account pre-registration callback: %v", err) + } + } + + // agree to terms + if interactive { + if !am.Agreed { + var termsURL string + dir, err := client.GetDirectory(ctx) + if err != nil { + return nil, fmt.Errorf("getting directory: %w", err) + } + if dir.Meta != nil { + termsURL = dir.Meta.TermsOfService + } + if termsURL != "" { + am.Agreed = am.askUserAgreement(termsURL) + if !am.Agreed { + return nil, fmt.Errorf("user must agree to CA terms") + } + } + } + } else { + // can't prompt a user who isn't there; they should + // have reviewed the terms beforehand + am.Agreed = true + } + account.TermsOfServiceAgreed = am.Agreed + + // associate account with external binding, if configured + if am.ExternalAccount != nil { + err := account.SetExternalAccountBinding(ctx, client.Client, *am.ExternalAccount) + if err != nil { + return nil, err + } + } + + // create account + account, err = client.NewAccount(ctx, account) + if err != nil { + return nil, fmt.Errorf("registering account %v with server: %w", account.Contact, err) + } + + // persist the account to storage + err = am.saveAccount(client.Directory, account) + if err != nil { + return nil, fmt.Errorf("could not save account %v: %v", account.Contact, err) + } + } + + c := &acmeClient{ + mgr: am, + acmeClient: client, + account: account, + } + + return c, nil +} + +// newACMEClient creates a new underlying ACME client using the settings in am, +// independent of any particular ACME account. If useTestCA is true, am.TestCA +// will be used if it is set; otherwise, the primary CA will be used. +func (am *ACMEManager) newACMEClient(useTestCA bool) (*acmez.Client, error) { // ensure defaults are filled in var caURL string if useTestCA { @@ -78,12 +163,6 @@ func (am *ACMEManager) newACMEClient(ctx context.Context, useTestCA, interactive return nil, fmt.Errorf("%s: insecure CA URL (HTTPS required)", caURL) } - // look up or create the ACME account - account, err := am.getAccount(caURL, am.Email) - if err != nil { - return nil, fmt.Errorf("getting ACME account: %v", err) - } - // set up the dialers and resolver for the ACME client's HTTP client dialer := &net.Dialer{ Timeout: 30 * time.Second, @@ -153,12 +232,12 @@ func (am *ACMEManager) newACMEClient(ctx context.Context, useTestCA, interactive useHTTPPort = am.AltHTTPPort } client.ChallengeSolvers[acme.ChallengeTypeHTTP01] = distributedSolver{ - acmeManager: am, + storage: am.config.Storage, + storageKeyIssuerPrefix: am.storageKeyCAPrefix(client.Directory), solver: &httpSolver{ acmeManager: am, address: net.JoinHostPort(am.ListenHost, strconv.Itoa(useHTTPPort)), }, - caURL: client.Directory, } } @@ -172,12 +251,12 @@ func (am *ACMEManager) newACMEClient(ctx context.Context, useTestCA, interactive useTLSALPNPort = am.AltTLSALPNPort } client.ChallengeSolvers[acme.ChallengeTypeTLSALPN01] = distributedSolver{ - acmeManager: am, + storage: am.config.Storage, + storageKeyIssuerPrefix: am.storageKeyCAPrefix(client.Directory), solver: &tlsALPNSolver{ config: am.config, address: net.JoinHostPort(am.ListenHost, strconv.Itoa(useTLSALPNPort)), }, - caURL: client.Directory, } } } else { @@ -185,68 +264,26 @@ func (am *ACMEManager) newACMEClient(ctx context.Context, useTestCA, interactive client.ChallengeSolvers[acme.ChallengeTypeDNS01] = am.DNS01Solver } - // register account if it is new - if account.Status == "" { - if am.NewAccountFunc != nil { - err = am.NewAccountFunc(ctx, am, account) - if err != nil { - return nil, fmt.Errorf("account pre-registration callback: %v", err) - } - } - - // agree to terms - if interactive { - if !am.Agreed { - var termsURL string - dir, err := client.GetDirectory(ctx) - if err != nil { - return nil, fmt.Errorf("getting directory: %w", err) - } - if dir.Meta != nil { - termsURL = dir.Meta.TermsOfService - } - if termsURL != "" { - am.Agreed = am.askUserAgreement(termsURL) - if !am.Agreed { - return nil, fmt.Errorf("user must agree to CA terms") - } - } - } - } else { - // can't prompt a user who isn't there; they should - // have reviewed the terms beforehand - am.Agreed = true - } - account.TermsOfServiceAgreed = am.Agreed - - // associate account with external binding, if configured - if am.ExternalAccount != nil { - err := account.SetExternalAccountBinding(ctx, client.Client, *am.ExternalAccount) - if err != nil { - return nil, err - } - } - - // create account - account, err = client.NewAccount(ctx, account) - if err != nil { - return nil, fmt.Errorf("registering account with server: %w", err) - } - - // persist the account to storage - err = am.saveAccount(caURL, account) - if err != nil { - return nil, fmt.Errorf("could not save account: %v", err) - } - } - - c := &acmeClient{ - mgr: am, - acmeClient: client, - account: account, + // wrap solvers in our wrapper so that we can keep track of challenge + // info: this is useful for solving challenges globally as a process; + // for example, usually there is only one process that can solve the + // HTTP and TLS-ALPN challenges, and only one server in that process + // that can bind the necessary port(s), so if a server listening on + // a different port needed a certificate, it would have to know about + // the other server listening on that port, and somehow convey its + // challenge info or share its config, but this isn't always feasible; + // what the wrapper does is it accesses a global challenge memory so + // that unrelated servers in this process can all solve each others' + // challenges without having to know about each other - Caddy's admin + // endpoint uses this functionality since it and the HTTP/TLS modules + // do not know about each other + // (doing this here in a separate loop ensures that even if we expose + // solver config to users later, we will even wrap their own solvers) + for name, solver := range client.ChallengeSolvers { + client.ChallengeSolvers[name] = solverWrapper{solver} } - return c, nil + return client, nil } func (c *acmeClient) throttle(ctx context.Context, names []string) error { @@ -325,7 +362,7 @@ var ( // RateLimitEvents is how many new events can be allowed // in RateLimitEventsWindow. - RateLimitEvents = 10 + RateLimitEvents = 20 // RateLimitEventsWindow is the size of the sliding // window that throttles events. diff --git a/vendor/github.com/caddyserver/certmagic/acmemanager.go b/vendor/github.com/caddyserver/certmagic/acmemanager.go index 6820b4f918..82b6cc12ef 100644 --- a/vendor/github.com/caddyserver/certmagic/acmemanager.go +++ b/vendor/github.com/caddyserver/certmagic/acmemanager.go @@ -7,6 +7,7 @@ import ( "fmt" "net/http" "net/url" + "sort" "strings" "time" @@ -19,7 +20,7 @@ import ( // Issuer, and Revoker interfaces. // // It is NOT VALID to use an ACMEManager without calling NewACMEManager(). -// It fills in default values from DefaultACME as well as setting up +// It fills in any default values from DefaultACME as well as setting up // internal state that is necessary for valid use. Always call // NewACMEManager() to get a valid ACMEManager value. type ACMEManager struct { @@ -37,6 +38,12 @@ type ACMEManager struct { // selecting an existing ACME server account Email string + // The PEM-encoded private key of the ACME + // account to use; only needed if the account + // is already created on the server and + // can be looked up with the ACME protocol + AccountKeyPEM string + // Set to true if agreed to the CA's // subscriber agreement Agreed bool @@ -92,9 +99,13 @@ type ACMEManager struct { // Callback function that is called before a // new ACME account is registered with the CA; // it allows for last-second config changes - // of the ACMEManager (TODO: this feature is - // still EXPERIMENTAL and subject to change) - NewAccountFunc func(context.Context, *ACMEManager, acme.Account) error + // of the ACMEManager and the Account. + // (TODO: this feature is still EXPERIMENTAL and subject to change) + NewAccountFunc func(context.Context, *ACMEManager, acme.Account) (acme.Account, error) + + // Preferences for selecting alternate + // certificate chains + PreferredChains ChainPreference // Set a logger to enable logging Logger *zap.Logger @@ -105,10 +116,12 @@ type ACMEManager struct { // NewACMEManager constructs a valid ACMEManager based on a template // configuration; any empty values will be filled in by defaults in -// DefaultACME. The associated config is also required. +// DefaultACME, and if any required values are still empty, sensible +// defaults will be used. // -// Typically, you'll create the Config first, then call NewACMEManager(), -// then assign the return value to the Issuer/Revoker fields of the Config. +// Typically, you'll create the Config first with New() or NewDefault(), +// then call NewACMEManager(), then assign the return value to the Issuers +// field of the Config. func NewACMEManager(cfg *Config, template ACMEManager) *ACMEManager { if cfg == nil { panic("cannot make valid ACMEManager without an associated CertMagic config") @@ -126,6 +139,9 @@ func NewACMEManager(cfg *Config, template ACMEManager) *ACMEManager { if template.Email == "" { template.Email = DefaultACME.Email } + if template.AccountKeyPEM == "" { + template.AccountKeyPEM = DefaultACME.AccountKeyPEM + } if !template.Agreed { template.Agreed = DefaultACME.Agreed } @@ -175,7 +191,7 @@ func (am *ACMEManager) IssuerKey() string { return am.issuerKey(am.CA) } -func (am *ACMEManager) issuerKey(ca string) string { +func (*ACMEManager) issuerKey(ca string) string { key := ca if caURL, err := url.Parse(key); err == nil { key = caURL.Host @@ -202,11 +218,11 @@ func (am *ACMEManager) issuerKey(ca string) string { // batch is eligible for certificates if using Let's Encrypt. // It also ensures that an email address is available. func (am *ACMEManager) PreCheck(_ context.Context, names []string, interactive bool) error { - letsEncrypt := strings.Contains(am.CA, "api.letsencrypt.org") - if letsEncrypt { + publicCA := strings.Contains(am.CA, "api.letsencrypt.org") || strings.Contains(am.CA, "acme.zerossl.com") + if publicCA { for _, name := range names { if !SubjectQualifiesForPublicCert(name) { - return fmt.Errorf("subject does not qualify for a Let's Encrypt certificate: %s", name) + return fmt.Errorf("subject does not qualify for a public certificate: %s", name) } } } @@ -282,7 +298,7 @@ func (am *ACMEManager) Issue(ctx context.Context, csr *x509.CertificateRequest) } func (am *ACMEManager) doIssue(ctx context.Context, csr *x509.CertificateRequest, useTestCA bool) (*IssuedCertificate, bool, error) { - client, err := am.newACMEClient(ctx, useTestCA, false) + client, err := am.newACMEClientWithAccount(ctx, useTestCA, false) if err != nil { return nil, false, err } @@ -300,20 +316,103 @@ func (am *ACMEManager) doIssue(ctx context.Context, csr *x509.CertificateRequest if err != nil { return nil, usingTestCA, fmt.Errorf("%v %w (ca=%s)", nameSet, err, client.acmeClient.Directory) } + if len(certChains) == 0 { + return nil, usingTestCA, fmt.Errorf("no certificate chains") + } + + preferredChain := am.selectPreferredChain(certChains) - // TODO: ACME server could in theory issue a cert with multiple chains, - // but we don't (yet) have a way to choose one, so just use first one ic := &IssuedCertificate{ - Certificate: certChains[0].ChainPEM, - Metadata: certChains[0], + Certificate: preferredChain.ChainPEM, + Metadata: preferredChain, } return ic, usingTestCA, nil } +// selectPreferredChain sorts and then filters the certificate chains to find the optimal +// chain preferred by the client. If there's only one chain, that is returned without any +// processing. If there are no matches, the first chain is returned. +func (am *ACMEManager) selectPreferredChain(certChains []acme.Certificate) acme.Certificate { + if len(certChains) == 1 { + if am.Logger != nil && (len(am.PreferredChains.AnyCommonName) > 0 || len(am.PreferredChains.RootCommonName) > 0) { + am.Logger.Debug("there is only one chain offered; selecting it regardless of preferences", + zap.String("chain_url", certChains[0].URL)) + } + return certChains[0] + } + + if am.PreferredChains.Smallest != nil { + if *am.PreferredChains.Smallest { + sort.Slice(certChains, func(i, j int) bool { + return len(certChains[i].ChainPEM) < len(certChains[j].ChainPEM) + }) + } else { + sort.Slice(certChains, func(i, j int) bool { + return len(certChains[i].ChainPEM) > len(certChains[j].ChainPEM) + }) + } + } + + if len(am.PreferredChains.AnyCommonName) > 0 || len(am.PreferredChains.RootCommonName) > 0 { + // in order to inspect, we need to decode their PEM contents + decodedChains := make([][]*x509.Certificate, len(certChains)) + for i, chain := range certChains { + certs, err := parseCertsFromPEMBundle(chain.ChainPEM) + if err != nil { + if am.Logger != nil { + am.Logger.Error("unable to parse PEM certificate chain", + zap.Int("chain", i), + zap.Error(err)) + } + continue + } + decodedChains[i] = certs + } + + if len(am.PreferredChains.AnyCommonName) > 0 { + for _, prefAnyCN := range am.PreferredChains.AnyCommonName { + for i, chain := range decodedChains { + for _, cert := range chain { + if cert.Issuer.CommonName == prefAnyCN { + if am.Logger != nil { + am.Logger.Debug("found preferred certificate chain by issuer common name", + zap.String("preference", prefAnyCN), + zap.Int("chain", i)) + } + return certChains[i] + } + } + } + } + } + + if len(am.PreferredChains.RootCommonName) > 0 { + for _, prefRootCN := range am.PreferredChains.RootCommonName { + for i, chain := range decodedChains { + if chain[len(chain)-1].Issuer.CommonName == prefRootCN { + if am.Logger != nil { + am.Logger.Debug("found preferred certificate chain by root common name", + zap.String("preference", prefRootCN), + zap.Int("chain", i)) + } + return certChains[i] + } + } + } + } + + if am.Logger != nil { + am.Logger.Warn("did not find chain matching preferences; using first") + } + } + + return certChains[0] +} + // Revoke implements the Revoker interface. It revokes the given certificate. func (am *ACMEManager) Revoke(ctx context.Context, cert CertificateResource, reason int) error { - client, err := am.newACMEClient(ctx, false, false) + client, err := am.newACMEClientWithAccount(ctx, false, false) if err != nil { return err } @@ -326,8 +425,24 @@ func (am *ACMEManager) Revoke(ctx context.Context, cert CertificateResource, rea return client.revoke(ctx, certs[0], reason) } -// DefaultACME specifies the default settings -// to use for ACMEManagers. +// ChainPreference describes the client's preferred certificate chain, +// useful if the CA offers alternate chains. The first matching chain +// will be selected. +type ChainPreference struct { + // Prefer chains with the fewest number of bytes. + Smallest *bool + + // Select first chain having a root with one of + // these common names. + RootCommonName []string + + // Select first chain that has any issuer with one + // of these common names. + AnyCommonName []string +} + +// DefaultACME specifies default settings to use for ACMEManagers. +// Using this value is optional but can be convenient. var DefaultACME = ACMEManager{ CA: LetsEncryptProductionCA, TestCA: LetsEncryptStagingCA, @@ -337,6 +452,7 @@ var DefaultACME = ACMEManager{ const ( LetsEncryptStagingCA = "https://acme-staging-v02.api.letsencrypt.org/directory" LetsEncryptProductionCA = "https://acme-v02.api.letsencrypt.org/directory" + ZeroSSLProductionCA = "https://acme.zerossl.com/v2/DV90" ) // prefixACME is the storage key prefix used for ACME-specific assets. diff --git a/vendor/github.com/caddyserver/certmagic/certificates.go b/vendor/github.com/caddyserver/certmagic/certificates.go index ebdb61832d..09dc340d03 100644 --- a/vendor/github.com/caddyserver/certmagic/certificates.go +++ b/vendor/github.com/caddyserver/certmagic/certificates.go @@ -113,10 +113,11 @@ func (cfg *Config) CacheManagedCertificate(domain string) (Certificate, error) { return cert, nil } -// loadManagedCertificate loads the managed certificate for domain, -// but it does not add it to the cache. It just loads from storage. +// loadManagedCertificate loads the managed certificate for domain from any +// of the configured issuers' storage locations, but it does not add it to +// the cache. It just loads from storage and returns it. func (cfg *Config) loadManagedCertificate(domain string) (Certificate, error) { - certRes, err := cfg.loadCertResource(domain) + certRes, err := cfg.loadCertResourceAnyIssuer(domain) if err != nil { return Certificate{}, err } @@ -154,7 +155,7 @@ func (cfg *Config) CacheUnmanagedTLSCertificate(tlsCert tls.Certificate, tags [] if err != nil { return err } - _, err = stapleOCSP(cfg.Storage, &cert, nil) + _, err = stapleOCSP(cfg.OCSP, cfg.Storage, &cert, nil) if err != nil && cfg.Logger != nil { cfg.Logger.Warn("stapling OCSP", zap.Error(err)) } @@ -202,7 +203,7 @@ func (cfg Config) makeCertificateWithOCSP(certPEMBlock, keyPEMBlock []byte) (Cer if err != nil { return cert, err } - _, err = stapleOCSP(cfg.Storage, &cert, certPEMBlock) + _, err = stapleOCSP(cfg.OCSP, cfg.Storage, &cert, certPEMBlock) if err != nil && cfg.Logger != nil { cfg.Logger.Warn("stapling OCSP", zap.Error(err)) } @@ -295,19 +296,12 @@ func fillCertFromLeaf(cert *Certificate, tlsCert tls.Certificate) error { // meantime, and it would be a good idea to simply load the cert // into our cache rather than repeating the renewal process again. func (cfg *Config) managedCertInStorageExpiresSoon(cert Certificate) (bool, error) { - certRes, err := cfg.loadCertResource(cert.Names[0]) + certRes, err := cfg.loadCertResourceAnyIssuer(cert.Names[0]) if err != nil { return false, err } - tlsCert, err := tls.X509KeyPair(certRes.CertificatePEM, certRes.PrivateKeyPEM) - if err != nil { - return false, err - } - leaf, err := x509.ParseCertificate(tlsCert.Certificate[0]) - if err != nil { - return false, err - } - return currentlyInRenewalWindow(leaf.NotBefore, leaf.NotAfter, cfg.RenewalWindowRatio), nil + _, needsRenew := cfg.managedCertNeedsRenewal(certRes) + return needsRenew, nil } // reloadManagedCertificate reloads the certificate corresponding to the name(s) @@ -341,8 +335,9 @@ func SubjectQualifiesForCert(subj string) bool { !strings.HasPrefix(subj, ".") && !strings.HasSuffix(subj, ".") && - // if it has a wildcard, must be a left-most label - (!strings.Contains(subj, "*") || strings.HasPrefix(subj, "*.")) && + // if it has a wildcard, must be a left-most label (or exactly "*" + // which won't be trusted by browsers but still technically works) + (!strings.Contains(subj, "*") || strings.HasPrefix(subj, "*.") || subj == "*") && // must not contain other common special characters !strings.ContainsAny(subj, "()[]{}<> \t\n\"\\!@#$%^&|;'+=") @@ -356,32 +351,45 @@ func SubjectQualifiesForCert(subj string) bool { // allowed, as long as they conform to CABF requirements (only // one wildcard label, and it must be the left-most label). func SubjectQualifiesForPublicCert(subj string) bool { - // must at least qualify for certificate + // must at least qualify for a certificate return SubjectQualifiesForCert(subj) && - // localhost is ineligible - subj != "localhost" && - - // .localhost TLD is ineligible - !strings.HasSuffix(subj, ".localhost") && + // localhost, .localhost TLD, and .local TLD are ineligible + !SubjectIsInternal(subj) && - // .local TLD is ineligible - !strings.HasSuffix(subj, ".local") && + // cannot be an IP address (as of yet), see + // https://community.letsencrypt.org/t/certificate-for-static-ip/84/2?u=mholt + !SubjectIsIP(subj) && - // only one wildcard label allowed, and it must be left-most + // only one wildcard label allowed, and it must be left-most, with 3+ labels (!strings.Contains(subj, "*") || (strings.Count(subj, "*") == 1 && + strings.Count(subj, ".") > 1 && len(subj) > 2 && - strings.HasPrefix(subj, "*."))) && + strings.HasPrefix(subj, "*."))) +} - // cannot be an IP address (as of yet), see - // https://community.letsencrypt.org/t/certificate-for-static-ip/84/2?u=mholt - net.ParseIP(subj) == nil +// SubjectIsIP returns true if subj is an IP address. +func SubjectIsIP(subj string) bool { + return net.ParseIP(subj) != nil +} + +// SubjectIsInternal returns true if subj is an internal-facing +// hostname or address. +func SubjectIsInternal(subj string) bool { + return subj == "localhost" || + strings.HasSuffix(subj, ".localhost") || + strings.HasSuffix(subj, ".local") } // MatchWildcard returns true if subject (a candidate DNS name) // matches wildcard (a reference DNS name), mostly according to -// RFC6125-compliant wildcard rules. +// RFC 6125-compliant wildcard rules. See also RFC 2818 which +// states that IP addresses must match exactly, but this function +// does not attempt to distinguish IP addresses from internal or +// external DNS names that happen to look like IP addresses. +// It uses DNS wildcard matching logic. +// https://tools.ietf.org/html/rfc2818#section-3.1 func MatchWildcard(subject, wildcard string) bool { if subject == wildcard { return true diff --git a/vendor/github.com/caddyserver/certmagic/certmagic.go b/vendor/github.com/caddyserver/certmagic/certmagic.go index d2a64151b9..03844b4ad9 100644 --- a/vendor/github.com/caddyserver/certmagic/certmagic.go +++ b/vendor/github.com/caddyserver/certmagic/certmagic.go @@ -125,8 +125,10 @@ func HTTPS(domainNames []string, mux http.Handler) error { WriteTimeout: 5 * time.Second, IdleTimeout: 5 * time.Second, } - if am, ok := cfg.Issuer.(*ACMEManager); ok { - httpServer.Handler = am.HTTPChallengeHandler(http.HandlerFunc(httpRedirectHandler)) + if len(cfg.Issuers) > 0 { + if am, ok := cfg.Issuers[0].(*ACMEManager); ok { + httpServer.Handler = am.HTTPChallengeHandler(http.HandlerFunc(httpRedirectHandler)) + } } httpsServer := &http.Server{ ReadHeaderTimeout: 10 * time.Second, @@ -425,9 +427,11 @@ func (cr *CertificateResource) NamesKey() string { // Default contains the package defaults for the // various Config fields. This is used as a template -// when creating your own Configs with New(), and it -// is also used as the Config by all the high-level -// functions in this package. +// when creating your own Configs with New() or +// NewDefault(), and it is also used as the Config +// by all the high-level functions in this package +// that abstract away most configuration (HTTPS(), +// TLS(), Listen(), etc). // // The fields of this value will be used for Config // fields which are unset. Feel free to modify these @@ -436,8 +440,10 @@ func (cr *CertificateResource) NamesKey() string { // obtained by calling New() (if you have your own // certificate cache) or NewDefault() (if you only // need a single config and want to use the default -// cache). This is the only Config which can access -// the default certificate cache. +// cache). +// +// Even if the Issuers or Storage fields are not set, +// defaults will be applied in the call to New(). var Default = Config{ RenewalWindowRatio: DefaultRenewalWindowRatio, Storage: defaultFileStorage, @@ -459,12 +465,12 @@ const ( // are set to; otherwise ACME challenges will fail. var ( // HTTPPort is the port on which to serve HTTP - // and, by extension, the HTTP challenge (unless + // and, as such, the HTTP challenge (unless // Default.AltHTTPPort is set). HTTPPort = 80 // HTTPSPort is the port on which to serve HTTPS - // and, by extension, the TLS-ALPN challenge + // and, as such, the TLS-ALPN challenge // (unless Default.AltTLSALPNPort is set). HTTPSPort = 443 ) diff --git a/vendor/github.com/caddyserver/certmagic/config.go b/vendor/github.com/caddyserver/certmagic/config.go index 4cab8121b2..1428a14e46 100644 --- a/vendor/github.com/caddyserver/certmagic/config.go +++ b/vendor/github.com/caddyserver/certmagic/config.go @@ -23,6 +23,7 @@ import ( "crypto/x509" "crypto/x509/pkix" "encoding/asn1" + "encoding/json" "fmt" weakrand "math/rand" "net" @@ -31,7 +32,9 @@ import ( "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. @@ -54,45 +57,48 @@ type Config struct { // DefaultServerName specifies a server name // to use when choosing a certificate if the - // ClientHello's ServerName field is empty + // 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) + // TLS handshakes (or as-needed). // TODO: Can we call this feature "Reactive/Lazy/Passive TLS" instead? OnDemand *OnDemandConfig - // Add the must staple TLS extension to the CSR + // Adds the must staple TLS extension to the CSR. MustStaple bool - // The type that issues certificates; the - // default Issuer is ACMEManager - Issuer Issuer - - // The type that revokes certificates; must - // be configured in conjunction with the Issuer - // field such that both the Issuer and Revoker - // are related (because issuance information is - // required for revocation) - Revoker Revoker + // 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 + // 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 + // be used. CertSelection CertificateSelector - // The storage to access when storing or - // loading TLS assets + // 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 + // Set a logger to enable logging. Logger *zap.Logger // required pointer to the in-memory cert cache @@ -116,6 +122,9 @@ type Config struct { // 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 { @@ -153,7 +162,7 @@ func NewDefault() *Config { // 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 New() instead. +// suffice, and you should use NewDefault() instead. func New(certCache *Cache, cfg Config) *Config { if certCache == nil { panic("a certificate cache is required") @@ -196,23 +205,11 @@ func newWithCache(certCache *Cache, cfg Config) *Config { if cfg.Storage == nil { cfg.Storage = Default.Storage } - if cfg.Issuer == nil { - cfg.Issuer = Default.Issuer - if cfg.Issuer == nil { - // okay really, we need an issuer, - // that's kind of the point; most - // people would probably want ACME - cfg.Issuer = NewACMEManager(&cfg, DefaultACME) - } - // issuer and revoker go together; if user - // specifies their own issuer, we don't want - // to override their revoker, hence we only - // do this if Issuer was also nil - if cfg.Revoker == nil { - cfg.Revoker = Default.Revoker - if cfg.Revoker == nil { - cfg.Revoker = NewACMEManager(&cfg, DefaultACME) - } + 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)} } } @@ -223,7 +220,6 @@ func newWithCache(certCache *Cache, cfg Config) *Config { cfg.Storage = defaultFileStorage } - // ensure the unexported fields are valid cfg.certCache = certCache return &cfg @@ -254,6 +250,29 @@ 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 @@ -360,6 +379,28 @@ func (cfg *Config) manageOne(ctx context.Context, domainName string, async bool) 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. @@ -372,27 +413,22 @@ func (cfg *Config) manageOne(ctx context.Context, domainName string, async bool) // 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 cfg.storageHasCertResources(name) { - return nil - } - issuer, err := cfg.getPrecheckedIssuer(ctx, []string{name}, interactive) - if err != nil { - return err + if len(cfg.Issuers) == 0 { + return fmt.Errorf("no issuers configured; impossible to obtain or check for existing certificate in storage") } - if issuer == nil { + if cfg.storageHasCertResourcesAnyIssuer(name) { return nil } - return cfg.obtainWithIssuer(ctx, issuer, name, interactive) -} - -func loggerNamed(l *zap.Logger, name string) *zap.Logger { - if l == nil { - 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 l.Named(name) + return cfg.obtainCert(ctx, name, interactive) } -func (cfg *Config) obtainWithIssuer(ctx context.Context, issuer Issuer, name string, interactive bool) error { +func (cfg *Config) obtainCert(ctx context.Context, name string, interactive bool) error { log := loggerNamed(cfg.Logger, "obtain") if log != nil { @@ -400,10 +436,10 @@ func (cfg *Config) obtainWithIssuer(ctx context.Context, issuer Issuer, name str } // ensure idempotency of the obtain operation for this name - lockKey := cfg.lockKey("cert_acme", name) + lockKey := cfg.lockKey(certIssueLockOp, name) err := acquireLock(ctx, cfg.Storage, lockKey) if err != nil { - return err + return fmt.Errorf("unable to acquire lock '%s': %v", lockKey, err) } defer func() { if log != nil { @@ -424,7 +460,7 @@ func (cfg *Config) obtainWithIssuer(ctx context.Context, issuer Issuer, name str f := func(ctx context.Context) error { // check if obtain is still needed -- might have been obtained during lock - if cfg.storageHasCertResources(name) { + if cfg.storageHasCertResourcesAnyIssuer(name) { if log != nil { log.Info("certificate already exists in storage", zap.String("identifier", name)) } @@ -445,8 +481,24 @@ func (cfg *Config) obtainWithIssuer(ctx context.Context, issuer Issuer, name str return err } - issuedCert, err := issuer.Issue(ctx, csr) + // 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) } @@ -457,7 +509,7 @@ func (cfg *Config) obtainWithIssuer(ctx context.Context, issuer Issuer, name str PrivateKeyPEM: privKeyPEM, IssuerData: issuedCert.Metadata, } - err = cfg.saveCertResource(certRes) + err = cfg.saveCertResource(issuerUsed, certRes) if err != nil { return fmt.Errorf("[%s] Obtain: saving assets: %v", name, err) } @@ -480,21 +532,32 @@ func (cfg *Config) obtainWithIssuer(ctx context.Context, issuer Issuer, name str 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 { - issuer, err := cfg.getPrecheckedIssuer(ctx, []string{name}, interactive) - if err != nil { - return err + if len(cfg.Issuers) == 0 { + return fmt.Errorf("no issuers configured; impossible to renew or check existing certificate in storage") } - if issuer == nil { - 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.renewWithIssuer(ctx, issuer, name, interactive) + return cfg.renewCert(ctx, name, interactive) } -func (cfg *Config) renewWithIssuer(ctx context.Context, issuer Issuer, name string, interactive bool) error { +func (cfg *Config) renewCert(ctx context.Context, name string, interactive bool) error { log := loggerNamed(cfg.Logger, "renew") if log != nil { @@ -502,10 +565,10 @@ func (cfg *Config) renewWithIssuer(ctx context.Context, issuer Issuer, name stri } // ensure idempotency of the renew operation for this name - lockKey := cfg.lockKey("cert_acme", name) + lockKey := cfg.lockKey(certIssueLockOp, name) err := acquireLock(ctx, cfg.Storage, lockKey) if err != nil { - return err + return fmt.Errorf("unable to acquire lock '%s': %v", lockKey, err) } defer func() { if log != nil { @@ -526,7 +589,7 @@ func (cfg *Config) renewWithIssuer(ctx context.Context, issuer Issuer, name stri f := func(ctx context.Context) error { // prepare for renewal (load PEM cert, key, and meta) - certRes, err := cfg.loadCertResource(name) + certRes, err := cfg.loadCertResourceAnyIssuer(name) if err != nil { return err } @@ -556,8 +619,24 @@ func (cfg *Config) renewWithIssuer(ctx context.Context, issuer Issuer, name stri return err } - issuedCert, err := issuer.Issue(ctx, csr) + // 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) } @@ -568,7 +647,7 @@ func (cfg *Config) renewWithIssuer(ctx context.Context, issuer Issuer, name stri PrivateKeyPEM: certRes.PrivateKeyPEM, IssuerData: issuedCert.Metadata, } - err = cfg.saveCertResource(newCertRes) + err = cfg.saveCertResource(issuerUsed, newCertRes) if err != nil { return fmt.Errorf("[%s] Renew: saving assets: %v", name, err) } @@ -602,7 +681,12 @@ func (cfg *Config) generateCSR(privateKey crypto.PrivateKey, sans []string) (*x5 } else if u, err := url.Parse(name); err == nil && strings.Contains(name, "/") { csrTemplate.URIs = append(csrTemplate.URIs, u) } else { - csrTemplate.DNSNames = append(csrTemplate.DNSNames, name) + // 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) } } @@ -619,43 +703,45 @@ func (cfg *Config) generateCSR(privateKey crypto.PrivateKey, sans []string) (*x5 } // RevokeCert revokes the certificate for domain via ACME protocol. It requires -// that cfg.Issuer is properly configured with the same issuer that issued the +// 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 { - rev := cfg.Revoker - if rev == nil { - rev = Default.Revoker - } + for i, issuer := range cfg.Issuers { + issuerKey := issuer.IssuerKey() - certRes, err := cfg.loadCertResource(domain) - if err != nil { - return err - } + rev, ok := issuer.(Revoker) + if !ok { + return fmt.Errorf("issuer %d (%s) is not a Revoker", i, issuerKey) + } - issuerKey := cfg.Issuer.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) - } + 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 err - } + err = rev.Revoke(ctx, certRes, reason) + if err != nil { + return fmt.Errorf("issuer %d (%s): %v", i, issuerKey, err) + } - cfg.emit("cert_revoked", domain) + 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) + 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 @@ -692,27 +778,50 @@ func (cfg *Config) TLSConfig() *tls.Config { } } -// getPrecheckedIssuer returns an Issuer with pre-checks -// completed, if it is also a PreChecker. It also checks -// that storage is functioning. If a nil Issuer is returned -// with a nil error, that means to skip this operation -// (not an error, just a no-op). -func (cfg *Config) getPrecheckedIssuer(ctx context.Context, names []string, interactive bool) (Issuer, error) { - // 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 nil, fmt.Errorf("failed storage check: %v - storage is probably misconfigured", err) - } - if prechecker, ok := cfg.Issuer.(PreChecker); ok { - err := prechecker.PreCheck(ctx, names, interactive) - if err != nil { - return nil, err +// 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 cfg.Issuer, nil + + return Challenge{Challenge: chalInfo}, true, nil } // checkStorage tests the storage by writing random bytes @@ -758,8 +867,8 @@ func (cfg *Config) checkStorage() error { // 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(domain string) bool { - issuerKey := cfg.Issuer.IssuerKey() +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) @@ -771,18 +880,19 @@ func (cfg *Config) storageHasCertResources(domain string) bool { // 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_%s", op, domainName, cfg.Issuer.IssuerKey()) + return fmt.Sprintf("%s_%s", op, domainName) } -// managedCertNeedsRenewal returns true if certRes is -// expiring soon or already expired, or if the process -// of checking the expiration returned an error. +// 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) { - cert, err := makeCertificate(certRes.CertificatePEM, certRes.PrivateKeyPEM) + certChain, err := parseCertsFromPEMBundle(certRes.CertificatePEM) if err != nil { return 0, true } - return time.Until(cert.Leaf.NotAfter), cert.NeedsRenewal(cfg) + 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{}) { @@ -792,11 +902,40 @@ func (cfg *Config) emit(eventName string, data interface{}) { 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} diff --git a/vendor/github.com/caddyserver/certmagic/crypto.go b/vendor/github.com/caddyserver/certmagic/crypto.go index 2af3e62a00..ef60f27dc4 100644 --- a/vendor/github.com/caddyserver/certmagic/crypto.go +++ b/vendor/github.com/caddyserver/certmagic/crypto.go @@ -28,9 +28,12 @@ import ( "encoding/pem" "fmt" "hash/fnv" + "sort" "strings" "github.com/klauspost/cpuid" + "go.uber.org/zap" + "golang.org/x/net/idna" ) // encodePrivateKey marshals a EC or RSA private key into a PEM-encoded array of bytes. @@ -129,13 +132,13 @@ func fastHash(input []byte) string { // saveCertResource saves the certificate resource to disk. This // includes the certificate file itself, the private key, and the // metadata file. -func (cfg *Config) saveCertResource(cert CertificateResource) error { +func (cfg *Config) saveCertResource(issuer Issuer, cert CertificateResource) error { metaBytes, err := json.MarshalIndent(cert, "", "\t") if err != nil { return fmt.Errorf("encoding certificate metadata: %v", err) } - issuerKey := cfg.Issuer.IssuerKey() + issuerKey := issuer.IssuerKey() certKey := cert.NamesKey() all := []keyValue{ @@ -156,70 +159,101 @@ func (cfg *Config) saveCertResource(cert CertificateResource) error { return storeTx(cfg.Storage, all) } -func (cfg *Config) loadCertResource(certNamesKey string) (CertificateResource, error) { - var certRes CertificateResource - issuerKey := cfg.Issuer.IssuerKey() - certBytes, err := cfg.Storage.Load(StorageKeys.SiteCert(issuerKey, certNamesKey)) - if err != nil { - return CertificateResource{}, err +// loadCertResourceAnyIssuer loads and returns the certificate resource from any +// of the configured issuers. If multiple are found (e.g. if there are 3 issuers +// configured, and all 3 have a resource matching certNamesKey), then the newest +// (latest NotBefore date) resource will be chosen. +func (cfg *Config) loadCertResourceAnyIssuer(certNamesKey string) (CertificateResource, error) { + // we can save some extra decoding steps if there's only one issuer, since + // we don't need to compare potentially multiple available resources to + // select the best one, when there's only one choice anyway + if len(cfg.Issuers) == 1 { + return cfg.loadCertResource(cfg.Issuers[0], certNamesKey) } - certRes.CertificatePEM = certBytes - keyBytes, err := cfg.Storage.Load(StorageKeys.SitePrivateKey(issuerKey, certNamesKey)) - if err != nil { - return CertificateResource{}, err + + type decodedCertResource struct { + CertificateResource + issuer Issuer + decoded *x509.Certificate } - certRes.PrivateKeyPEM = keyBytes - metaBytes, err := cfg.Storage.Load(StorageKeys.SiteMeta(issuerKey, certNamesKey)) - if err != nil { - return CertificateResource{}, err + var certResources []decodedCertResource + var lastErr error + + // load and decode all certificate resources found with the + // configured issuers so we can sort by newest + for _, issuer := range cfg.Issuers { + certRes, err := cfg.loadCertResource(issuer, certNamesKey) + if err != nil { + if _, ok := err.(ErrNotExist); ok { + // not a problem, but we need to remember the error + // in case we end up not finding any cert resources + // since we'll need an error to return in that case + lastErr = err + continue + } + return CertificateResource{}, err + } + certs, err := parseCertsFromPEMBundle(certRes.CertificatePEM) + if err != nil { + return CertificateResource{}, err + } + certResources = append(certResources, decodedCertResource{ + CertificateResource: certRes, + issuer: issuer, + decoded: certs[0], + }) } - err = json.Unmarshal(metaBytes, &certRes) - if err != nil { - return CertificateResource{}, fmt.Errorf("decoding certificate metadata: %v", err) + if len(certResources) == 0 { + if lastErr == nil { + lastErr = fmt.Errorf("no certificate resources found") // just in case; e.g. no Issuers configured + } + return CertificateResource{}, lastErr } - // TODO: July 2020 - transition to new ACME lib and cert resource structure; - // for a while, we will need to convert old cert resources to new structure - certRes, err = cfg.transitionCertMetaToACMEzJuly2020Format(certRes, metaBytes) - if err != nil { - return certRes, fmt.Errorf("one-time certificate resource transition: %v", err) + // sort by date so the most recently issued comes first + sort.Slice(certResources, func(i, j int) bool { + return certResources[j].decoded.NotBefore.Before(certResources[i].decoded.NotBefore) + }) + + if cfg.Logger != nil { + cfg.Logger.Debug("loading managed certificate", + zap.String("domain", certNamesKey), + zap.Time("expiration", certResources[0].decoded.NotAfter), + zap.String("issuer_key", certResources[0].issuer.IssuerKey()), + zap.Any("storage", cfg.Storage), + ) } - return certRes, nil + return certResources[0].CertificateResource, nil } -// TODO: this is a temporary transition helper starting July 2020. -// It can go away when we think enough time has passed that most active assets have transitioned. -func (cfg *Config) transitionCertMetaToACMEzJuly2020Format(certRes CertificateResource, metaBytes []byte) (CertificateResource, error) { - data, ok := certRes.IssuerData.(map[string]interface{}) - if !ok { - return certRes, nil - } - if certURL, ok := data["url"].(string); ok && certURL != "" { - return certRes, nil +// loadCertResource loads a certificate resource from the given issuer's storage location. +func (cfg *Config) loadCertResource(issuer Issuer, certNamesKey string) (CertificateResource, error) { + var certRes CertificateResource + issuerKey := issuer.IssuerKey() + + normalizedName, err := idna.ToASCII(certNamesKey) + if err != nil { + return certRes, fmt.Errorf("converting '%s' to ASCII: %v", certNamesKey, err) } - var oldCertRes struct { - SANs []string `json:"sans"` - IssuerData struct { - Domain string `json:"domain"` - CertURL string `json:"certUrl"` - CertStableURL string `json:"certStableUrl"` - } `json:"issuer_data"` + certBytes, err := cfg.Storage.Load(StorageKeys.SiteCert(issuerKey, normalizedName)) + if err != nil { + return CertificateResource{}, err } - err := json.Unmarshal(metaBytes, &oldCertRes) + certRes.CertificatePEM = certBytes + keyBytes, err := cfg.Storage.Load(StorageKeys.SitePrivateKey(issuerKey, normalizedName)) if err != nil { - return certRes, fmt.Errorf("decoding into old certificate resource type: %v", err) + return CertificateResource{}, err } - - data = map[string]interface{}{ - "url": oldCertRes.IssuerData.CertURL, + certRes.PrivateKeyPEM = keyBytes + metaBytes, err := cfg.Storage.Load(StorageKeys.SiteMeta(issuerKey, normalizedName)) + if err != nil { + return CertificateResource{}, err } - certRes.IssuerData = data - - err = cfg.saveCertResource(certRes) + err = json.Unmarshal(metaBytes, &certRes) if err != nil { - return certRes, fmt.Errorf("saving converted certificate resource: %v", err) + return CertificateResource{}, fmt.Errorf("decoding certificate metadata: %v", err) } return certRes, nil diff --git a/vendor/github.com/caddyserver/certmagic/filestorage.go b/vendor/github.com/caddyserver/certmagic/filestorage.go index f3603d0747..8adc2ce75f 100644 --- a/vendor/github.com/caddyserver/certmagic/filestorage.go +++ b/vendor/github.com/caddyserver/certmagic/filestorage.go @@ -147,7 +147,7 @@ func (fs *FileStorage) Lock(ctx context.Context, key string) error { err2 := json.NewDecoder(f).Decode(&meta) f.Close() if err2 != nil { - return err2 + return fmt.Errorf("decoding lockfile contents: %w", err2) } } @@ -306,7 +306,15 @@ func updateLockfileFreshness(filename string) (bool, error) { // write updated timestamp meta.Updated = time.Now() - return false, json.NewEncoder(f).Encode(meta) + if err = json.NewEncoder(f).Encode(meta); err != nil { + return false, err + } + + // sync to device; we suspect that sometimes file systems + // (particularly AWS EFS) don't do this on their own, + // leaving the file empty when we close it; see + // https://github.com/caddyserver/caddy/issues/3954 + return false, f.Sync() } // atomicallyCreateFile atomically creates the file @@ -325,8 +333,11 @@ func atomicallyCreateFile(filename string, writeLockInfo bool) error { Created: now, Updated: now, } - err := json.NewEncoder(f).Encode(meta) - if err != nil { + if err := json.NewEncoder(f).Encode(meta); err != nil { + return err + } + // see https://github.com/caddyserver/caddy/issues/3954 + if err := f.Sync(); err != nil { return err } } diff --git a/vendor/github.com/caddyserver/certmagic/go.mod b/vendor/github.com/caddyserver/certmagic/go.mod index be29ac66e1..ff8f6b916b 100644 --- a/vendor/github.com/caddyserver/certmagic/go.mod +++ b/vendor/github.com/caddyserver/certmagic/go.mod @@ -4,9 +4,10 @@ go 1.14 require ( github.com/klauspost/cpuid v1.2.5 - github.com/libdns/libdns v0.1.0 - github.com/mholt/acmez v0.1.1 + github.com/libdns/libdns v0.2.0 + github.com/mholt/acmez v0.1.3 github.com/miekg/dns v1.1.30 go.uber.org/zap v1.15.0 golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de + golang.org/x/net v0.0.0-20200707034311-ab3426394381 ) diff --git a/vendor/github.com/caddyserver/certmagic/go.sum b/vendor/github.com/caddyserver/certmagic/go.sum index ccae0648f2..0d29850a65 100644 --- a/vendor/github.com/caddyserver/certmagic/go.sum +++ b/vendor/github.com/caddyserver/certmagic/go.sum @@ -12,10 +12,10 @@ github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORN github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= -github.com/libdns/libdns v0.1.0 h1:0ctCOrVJsVzj53mop1angHp/pE3hmAhP7KiHvR0HD04= -github.com/libdns/libdns v0.1.0/go.mod h1:yQCXzk1lEZmmCPa857bnk4TsOiqYasqpyOEeSObbb40= -github.com/mholt/acmez v0.1.1 h1:KQODCqk+hBn3O7qfCRPj6L96uG65T5BSS95FKNEqtdA= -github.com/mholt/acmez v0.1.1/go.mod h1:8qnn8QA/Ewx8E3ZSsmscqsIjhhpxuy9vqdgbX2ceceM= +github.com/libdns/libdns v0.2.0 h1:ewg3ByWrdUrxrje8ChPVMBNcotg7H9LQYg+u5De2RzI= +github.com/libdns/libdns v0.2.0/go.mod h1:yQCXzk1lEZmmCPa857bnk4TsOiqYasqpyOEeSObbb40= +github.com/mholt/acmez v0.1.3 h1:J7MmNIk4Qf9b8mAGqAh4XkNeowv3f1zW816yf4zt7Qk= +github.com/mholt/acmez v0.1.3/go.mod h1:8qnn8QA/Ewx8E3ZSsmscqsIjhhpxuy9vqdgbX2ceceM= github.com/miekg/dns v1.1.30 h1:Qww6FseFn8PRfw07jueqIXqodm0JKiiKuK0DeXSqfyo= github.com/miekg/dns v1.1.30/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM= github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= @@ -47,9 +47,7 @@ golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKG golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190620200207-3b0461eec859 h1:R/3boaszxrf1GEUWTVDzSKVwLmSJpwZ1yqXm8j0v2QI= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190923162816-aa69164e4478 h1:l5EDrHhldLYb3ZRHDUhXF7Om7MvYXnkV9/iQNo1lX6g= golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200707034311-ab3426394381 h1:VXak5I6aEWmAXeQjA+QSZzlgNrpq9mjcfDemuexIKsU= golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= @@ -57,7 +55,6 @@ golang.org/x/sync v0.0.0-20190423024810-112230192c58 h1:8gQV6CLnAEikrhgkHFbMAEha golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe h1:6fAMxZRR6sl1Uq8U61gxU+kPTs2tR8uOySCbBP7BN/M= golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd h1:xhmwyvizuTgC2qz7ZlMluP20uW+C3Rm0FD/WLDX8884= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -66,7 +63,6 @@ golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5 h1:hKsoRgsbwY1NafxrwTs+k64bikrLBkAgPir1TNCj3Zs= golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191216052735-49a3e744a425 h1:VvQyQJN0tSuecqgcIxMWnnfG5kSmgy9KZR9sW3W5QeA= golang.org/x/tools v0.0.0-20191216052735-49a3e744a425/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= diff --git a/vendor/github.com/caddyserver/certmagic/handshake.go b/vendor/github.com/caddyserver/certmagic/handshake.go index 4c3228a503..beb5cc3e9f 100644 --- a/vendor/github.com/caddyserver/certmagic/handshake.go +++ b/vendor/github.com/caddyserver/certmagic/handshake.go @@ -17,7 +17,6 @@ package certmagic import ( "context" "crypto/tls" - "encoding/json" "fmt" "net" "strings" @@ -25,7 +24,6 @@ import ( "time" "github.com/mholt/acmez" - "github.com/mholt/acmez/acme" "go.uber.org/zap" ) @@ -44,41 +42,23 @@ func (cfg *Config) GetCertificate(clientHello *tls.ClientHelloInfo) (*tls.Certif // (https://tools.ietf.org/html/draft-ietf-acme-tls-alpn-05) for _, proto := range clientHello.SupportedProtos { if proto == acmez.ACMETLS1Protocol { - cfg.certCache.mu.RLock() - challengeCert, ok := cfg.certCache.cache[tlsALPNCertKeyName(clientHello.ServerName)] - cfg.certCache.mu.RUnlock() - if !ok { - // see if this challenge was started in a cluster; try distributed challenge solver - // (note that the tls.Config's ALPN settings must include the ACME TLS-ALPN challenge - // protocol string, otherwise a valid certificate will not solve the challenge; we - // should already have taken care of that when we made the tls.Config) - challengeCert, ok, err := cfg.tryDistributedChallengeSolver(clientHello) - if err != nil { - if cfg.Logger != nil { - cfg.Logger.Error("tls-alpn challenge", - zap.String("server_name", clientHello.ServerName), - zap.Error(err)) - } - } - if ok { - if cfg.Logger != nil { - cfg.Logger.Info("served key authentication certificate", - zap.String("server_name", clientHello.ServerName), - zap.String("challenge", "tls-alpn-01"), - zap.String("remote", clientHello.Conn.RemoteAddr().String()), - zap.Bool("distributed", true)) - } - return &challengeCert.Certificate, nil + challengeCert, distributed, err := cfg.getTLSALPNChallengeCert(clientHello) + if err != nil { + if cfg.Logger != nil { + cfg.Logger.Error("tls-alpn challenge", + zap.String("server_name", clientHello.ServerName), + zap.Error(err)) } - return nil, fmt.Errorf("no certificate to complete TLS-ALPN challenge for SNI name: %s", clientHello.ServerName) + return nil, err } if cfg.Logger != nil { cfg.Logger.Info("served key authentication certificate", zap.String("server_name", clientHello.ServerName), zap.String("challenge", "tls-alpn-01"), - zap.String("remote", clientHello.Conn.RemoteAddr().String())) + zap.String("remote", clientHello.Conn.RemoteAddr().String()), + zap.Bool("distributed", distributed)) } - return &challengeCert.Certificate, nil + return challengeCert, nil } } @@ -107,16 +87,12 @@ func (cfg *Config) GetCertificate(clientHello *tls.ClientHelloInfo) (*tls.Certif // // This function is safe for concurrent use. func (cfg *Config) getCertificate(hello *tls.ClientHelloInfo) (cert Certificate, matched, defaulted bool) { - name := NormalizedName(hello.ServerName) + name := normalizedName(hello.ServerName) if name == "" { // if SNI is empty, prefer matching IP address if hello.Conn != nil { - addr := hello.Conn.LocalAddr().String() - ip, _, err := net.SplitHostPort(addr) - if err == nil { - addr = ip - } + addr := localIPFromConn(hello.Conn) cert, matched = cfg.selectCert(hello, addr) if matched { return @@ -125,7 +101,7 @@ func (cfg *Config) getCertificate(hello *tls.ClientHelloInfo) (cert Certificate, // fall back to a "default" certificate, if specified if cfg.DefaultServerName != "" { - normDefault := NormalizedName(cfg.DefaultServerName) + normDefault := normalizedName(cfg.DefaultServerName) cert, defaulted = cfg.selectCert(hello, normDefault) if defaulted { return @@ -260,6 +236,12 @@ func (cfg *Config) getCertDuringHandshake(hello *tls.ClientHelloInfo, loadIfNece if cfg.OnDemand != nil && loadIfNecessary { // Then check to see if we have one on disk loadedCert, err := cfg.CacheManagedCertificate(name) + if _, ok := err.(ErrNotExist); ok { + // If no exact match, try a wildcard variant, which is something we can still use + labels := strings.Split(name, ".") + labels[0] = "*" + loadedCert, err = cfg.CacheManagedCertificate(strings.Join(labels, ".")) + } if err == nil { loadedCert, err = cfg.handshakeMaintenance(hello, loadedCert) if err != nil { @@ -273,14 +255,6 @@ func (cfg *Config) getCertDuringHandshake(hello *tls.ClientHelloInfo, loadIfNece } if obtainIfNecessary { // By this point, we need to ask the CA for a certificate - - // Make sure the certificate should be obtained based on config - err := cfg.checkIfCertShouldBeObtained(name) - if err != nil { - return Certificate{}, err - } - - // Obtain certificate from the CA return cfg.obtainOnDemandCertificate(hello) } } @@ -347,6 +321,11 @@ func (cfg *Config) obtainOnDemandCertificate(hello *tls.ClientHelloInfo) (Certif name := cfg.getNameFromClientHello(hello) + getCertWithoutReobtaining := func() (Certificate, error) { + // very important to set the obtainIfNecessary argument to false, so we don't repeat this infinitely + return cfg.getCertDuringHandshake(hello, true, false) + } + // We must protect this process from happening concurrently, so synchronize. obtainCertWaitChansMu.Lock() wait, ok := obtainCertWaitChans[name] @@ -354,8 +333,17 @@ func (cfg *Config) obtainOnDemandCertificate(hello *tls.ClientHelloInfo) (Certif // lucky us -- another goroutine is already obtaining the certificate. // wait for it to finish obtaining the cert and then we'll use it. obtainCertWaitChansMu.Unlock() - <-wait - return cfg.getCertDuringHandshake(hello, true, false) + + // TODO: see if we can get a proper context in here, for true cancellation + timeout := time.NewTimer(2 * time.Minute) + select { + case <-timeout.C: + return Certificate{}, fmt.Errorf("timed out waiting to obtain certificate for %s", name) + case <-wait: + timeout.Stop() + } + + return getCertWithoutReobtaining() } // looks like it's up to us to do all the work and obtain the cert. @@ -364,22 +352,35 @@ func (cfg *Config) obtainOnDemandCertificate(hello *tls.ClientHelloInfo) (Certif obtainCertWaitChans[name] = wait obtainCertWaitChansMu.Unlock() - // obtain the certificate + unblockWaiters := func() { + obtainCertWaitChansMu.Lock() + close(wait) + delete(obtainCertWaitChans, name) + obtainCertWaitChansMu.Unlock() + } + + // Make sure the certificate should be obtained based on config + err := cfg.checkIfCertShouldBeObtained(name) + if err != nil { + unblockWaiters() + return Certificate{}, err + } + if log != nil { log.Info("obtaining new certificate", zap.String("server_name", name)) } + // TODO: use a proper context; we use one with timeout because retries are enabled because interactive is false ctx, cancel := context.WithTimeout(context.TODO(), 90*time.Second) defer cancel() - err := cfg.ObtainCert(ctx, name, false) + + // Obtain the certificate + err = cfg.ObtainCert(ctx, name, false) // immediately unblock anyone waiting for it; doing this in // a defer would risk deadlock because of the recursive call // to getCertDuringHandshake below when we return! - obtainCertWaitChansMu.Lock() - close(wait) - delete(obtainCertWaitChans, name) - obtainCertWaitChansMu.Unlock() + unblockWaiters() if err != nil { // shucks; failed to solve challenge on-demand @@ -388,7 +389,7 @@ func (cfg *Config) obtainOnDemandCertificate(hello *tls.ClientHelloInfo) (Certif // success; certificate was just placed on disk, so // we need only restart serving the certificate - return cfg.getCertDuringHandshake(hello, true, false) + return getCertWithoutReobtaining() } // handshakeMaintenance performs a check on cert for expiration and OCSP validity. @@ -400,13 +401,7 @@ func (cfg *Config) handshakeMaintenance(hello *tls.ClientHelloInfo, cert Certifi log := loggerNamed(cfg.Logger, "on_demand") // Check cert expiration - timeLeft := cert.Leaf.NotAfter.Sub(time.Now().UTC()) if currentlyInRenewalWindow(cert.Leaf.NotBefore, cert.Leaf.NotAfter, cfg.RenewalWindowRatio) { - if log != nil { - log.Info("certificate expires soon; attempting renewal", - zap.Strings("identifiers", cert.Names), - zap.Duration("remaining", timeLeft)) - } return cfg.renewDynamicCertificate(hello, cert) } @@ -414,7 +409,7 @@ func (cfg *Config) handshakeMaintenance(hello *tls.ClientHelloInfo, cert Certifi if cert.ocsp != nil { refreshTime := cert.ocsp.ThisUpdate.Add(cert.ocsp.NextUpdate.Sub(cert.ocsp.ThisUpdate) / 2) if time.Now().After(refreshTime) { - _, err := stapleOCSP(cfg.Storage, &cert, nil) + _, err := stapleOCSP(cfg.OCSP, cfg.Storage, &cert, nil) if err != nil { // An error with OCSP stapling is not the end of the world, and in fact, is // quite common considering not all certs have issuer URLs that support it. @@ -436,22 +431,59 @@ func (cfg *Config) handshakeMaintenance(hello *tls.ClientHelloInfo, cert Certifi // renewDynamicCertificate renews the certificate for name using cfg. It returns the // certificate to use and an error, if any. name should already be lower-cased before // calling this function. name is the name obtained directly from the handshake's -// ClientHello. +// ClientHello. If the certificate hasn't yet expired, currentCert will be returned +// and the renewal will happen in the background; otherwise this blocks until the +// certificate has been renewed, and returns the renewed certificate. // // This function is safe for use by multiple concurrent goroutines. func (cfg *Config) renewDynamicCertificate(hello *tls.ClientHelloInfo, currentCert Certificate) (Certificate, error) { log := loggerNamed(cfg.Logger, "on_demand") name := cfg.getNameFromClientHello(hello) + timeLeft := time.Until(currentCert.Leaf.NotAfter) + getCertWithoutReobtaining := func() (Certificate, error) { + // very important to set the obtainIfNecessary argument to false, so we don't repeat this infinitely + return cfg.getCertDuringHandshake(hello, true, false) + } + + // see if another goroutine is already working on this certificate obtainCertWaitChansMu.Lock() wait, ok := obtainCertWaitChans[name] if ok { - // lucky us -- another goroutine is already renewing the certificate. - // wait for it to finish, then we'll use the new one. + // lucky us -- another goroutine is already renewing the certificate obtainCertWaitChansMu.Unlock() - <-wait - return cfg.getCertDuringHandshake(hello, true, false) + + if timeLeft > 0 { + // the current certificate hasn't expired, and another goroutine is already + // renewing it, so we might as well serve what we have without blocking + if log != nil { + log.Debug("certificate expires soon but is already being renewed; serving current certificate", + zap.Strings("identifiers", currentCert.Names), + zap.Duration("remaining", timeLeft)) + } + return currentCert, nil + } + + // otherwise, we'll have to wait for the renewal to finish so we don't serve + // an expired certificate + + if log != nil { + log.Debug("certificate has expired, but is already being renewed; waiting for renewal to complete", + zap.Strings("identifiers", currentCert.Names), + zap.Time("expired", currentCert.Leaf.NotAfter)) + } + + // TODO: see if we can get a proper context in here, for true cancellation + timeout := time.NewTimer(2 * time.Minute) + select { + case <-timeout.C: + return Certificate{}, fmt.Errorf("timed out waiting for certificate renewal of %s", name) + case <-wait: + timeout.Stop() + } + + return getCertWithoutReobtaining() } // looks like it's up to us to do all the work and renew the cert @@ -459,6 +491,21 @@ func (cfg *Config) renewDynamicCertificate(hello *tls.ClientHelloInfo, currentCe obtainCertWaitChans[name] = wait obtainCertWaitChansMu.Unlock() + unblockWaiters := func() { + obtainCertWaitChansMu.Lock() + close(wait) + delete(obtainCertWaitChans, name) + obtainCertWaitChansMu.Unlock() + } + + if log != nil { + log.Info("attempting certificate renewal", + zap.String("server_name", name), + zap.Strings("identifiers", currentCert.Names), + zap.Time("expiration", currentCert.Leaf.NotAfter), + zap.Duration("remaining", timeLeft)) + } + // Make sure a certificate for this name should be obtained on-demand err := cfg.checkIfCertShouldBeObtained(name) if err != nil { @@ -466,105 +513,118 @@ func (cfg *Config) renewDynamicCertificate(hello *tls.ClientHelloInfo, currentCe cfg.certCache.mu.Lock() cfg.certCache.removeCertificate(currentCert) cfg.certCache.mu.Unlock() + unblockWaiters() return Certificate{}, err } - // renew and reload the certificate - if log != nil { - log.Info("renewing certificate", zap.String("server_name", name)) - } - // TODO: use a proper context; we use one with timeout because retries are enabled because interactive is false - ctx, cancel := context.WithTimeout(context.TODO(), 90*time.Second) - defer cancel() - err = cfg.RenewCert(ctx, name, false) - if err == nil { - // even though the recursive nature of the dynamic cert loading - // would just call this function anyway, we do it here to - // make the replacement as atomic as possible. - newCert, err := cfg.CacheManagedCertificate(name) - if err != nil { - if log != nil { - log.Error("loading renewed certificate", zap.String("server_name", name), zap.Error(err)) + // Renew and reload the certificate + renewAndReload := func(ctx context.Context, cancel context.CancelFunc) (Certificate, error) { + defer cancel() + err = cfg.RenewCert(ctx, name, false) + if err == nil { + // even though the recursive nature of the dynamic cert loading + // would just call this function anyway, we do it here to + // make the replacement as atomic as possible. + newCert, err := cfg.CacheManagedCertificate(name) + if err != nil { + if log != nil { + log.Error("loading renewed certificate", zap.String("server_name", name), zap.Error(err)) + } + } else { + // replace the old certificate with the new one + cfg.certCache.replaceCertificate(currentCert, newCert) } - } else { - // replace the old certificate with the new one - cfg.certCache.replaceCertificate(currentCert, newCert) } - } - // immediately unblock anyone waiting for it; doing this in - // a defer would risk deadlock because of the recursive call - // to getCertDuringHandshake below when we return! - obtainCertWaitChansMu.Lock() - close(wait) - delete(obtainCertWaitChans, name) - obtainCertWaitChansMu.Unlock() + // immediately unblock anyone waiting for it; doing this in + // a defer would risk deadlock because of the recursive call + // to getCertDuringHandshake below when we return! + unblockWaiters() - if err != nil { - return Certificate{}, err + if err != nil { + return Certificate{}, err + } + + return getCertWithoutReobtaining() } - return cfg.getCertDuringHandshake(hello, true, false) + // if the certificate hasn't expired, we can serve what we have and renew in the background + if timeLeft > 0 { + // TODO: get a proper context; we use one with timeout because retries are enabled because interactive is false + ctx, cancel := context.WithTimeout(context.TODO(), 5*time.Minute) + go renewAndReload(ctx, cancel) + return currentCert, nil + } + + // otherwise, we have to block while we renew an expired certificate + ctx, cancel := context.WithTimeout(context.TODO(), 90*time.Second) + return renewAndReload(ctx, cancel) } -// tryDistributedChallengeSolver is to be called when the clientHello pertains to -// a TLS-ALPN challenge and a certificate is required to solve it. This method -// checks the distributed store of challenge info files and, if a matching ServerName -// is present, it makes a certificate to solve this challenge and returns it. For -// this to succeed, it requires that cfg.Issuer is of type *ACMEManager. -// A boolean true is returned if a valid certificate is returned. -func (cfg *Config) tryDistributedChallengeSolver(clientHello *tls.ClientHelloInfo) (Certificate, bool, error) { - am, ok := cfg.Issuer.(*ACMEManager) - if !ok { - return Certificate{}, false, nil - } - tokenKey := distributedSolver{acmeManager: am, caURL: am.CA}.challengeTokensKey(clientHello.ServerName) - chalInfoBytes, err := cfg.Storage.Load(tokenKey) +// getTLSALPNChallengeCert is to be called when the clientHello pertains to +// a TLS-ALPN challenge and a certificate is required to solve it. This method gets +// the relevant challenge info and then returns the associated certificate (if any) +// or generates it anew if it's not available (as is the case when distributed +// solving). True is returned if the challenge is being solved distributed (there +// is no semantic difference with distributed solving; it is mainly for logging). +func (cfg *Config) getTLSALPNChallengeCert(clientHello *tls.ClientHelloInfo) (*tls.Certificate, bool, error) { + chalData, distributed, err := cfg.getChallengeInfo(clientHello.ServerName) if err != nil { - if _, ok := err.(ErrNotExist); ok { - return Certificate{}, false, nil - } - return Certificate{}, false, fmt.Errorf("opening distributed challenge token file %s: %v", tokenKey, err) + return nil, distributed, err } - var chalInfo acme.Challenge - err = json.Unmarshal(chalInfoBytes, &chalInfo) - if err != nil { - return Certificate{}, false, fmt.Errorf("decoding challenge token file %s (corrupted?): %v", tokenKey, err) + // fast path: we already created the certificate (this avoids having to re-create + // it at every handshake that tries to verify, e.g. multi-perspective validation) + if chalData.data != nil { + return chalData.data.(*tls.Certificate), distributed, nil } - cert, err := acmez.TLSALPN01ChallengeCert(chalInfo) + // otherwise, we can re-create the solution certificate, but it takes a few cycles + cert, err := acmez.TLSALPN01ChallengeCert(chalData.Challenge) if err != nil { - return Certificate{}, false, fmt.Errorf("making TLS-ALPN challenge certificate: %v", err) + return nil, distributed, fmt.Errorf("making TLS-ALPN challenge certificate: %v", err) } if cert == nil { - return Certificate{}, false, fmt.Errorf("got nil TLS-ALPN challenge certificate but no error") + return nil, distributed, fmt.Errorf("got nil TLS-ALPN challenge certificate but no error") } - return Certificate{Certificate: *cert}, true, nil + return cert, distributed, nil } // getNameFromClientHello returns a normalized form of hello.ServerName. // If hello.ServerName is empty (i.e. client did not use SNI), then the // associated connection's local address is used to extract an IP address. func (*Config) getNameFromClientHello(hello *tls.ClientHelloInfo) string { - name := NormalizedName(hello.ServerName) - if name != "" || hello.Conn == nil { + if name := normalizedName(hello.ServerName); name != "" { return name } + return localIPFromConn(hello.Conn) +} - // if no SNI, try using IP address on the connection - localAddr := hello.Conn.LocalAddr().String() - localAddrHost, _, err := net.SplitHostPort(localAddr) - if err == nil { - return localAddrHost +// localIPFromConn returns the host portion of c's local address +// and strips the scope ID if one exists (see RFC 4007). +func localIPFromConn(c net.Conn) string { + if c == nil { + return "" + } + localAddr := c.LocalAddr().String() + ip, _, err := net.SplitHostPort(localAddr) + if err != nil { + // OK; assume there was no port + ip = localAddr + } + // IPv6 addresses can have scope IDs, e.g. "fe80::4c3:3cff:fe4f:7e0b%eth0", + // but for our purposes, these are useless (unless a valid use case proves + // otherwise; see issue #3911) + if scopeIDStart := strings.Index(ip, "%"); scopeIDStart > -1 { + ip = ip[:scopeIDStart] } - return localAddr + return ip } -// NormalizedName returns a cleaned form of serverName that is +// normalizedName returns a cleaned form of serverName that is // used for consistency when referring to a SNI value. -func NormalizedName(serverName string) string { +func normalizedName(serverName string) string { return strings.ToLower(strings.TrimSpace(serverName)) } diff --git a/vendor/github.com/caddyserver/certmagic/httphandler.go b/vendor/github.com/caddyserver/certmagic/httphandler.go index e3897217ab..d17cfaab73 100644 --- a/vendor/github.com/caddyserver/certmagic/httphandler.go +++ b/vendor/github.com/caddyserver/certmagic/httphandler.go @@ -15,7 +15,6 @@ package certmagic import ( - "encoding/json" "net/http" "strings" @@ -71,41 +70,24 @@ func (am *ACMEManager) distributedHTTPChallengeSolver(w http.ResponseWriter, r * if am == nil { return false } - host := hostOnly(r.Host) - - tokenKey := distributedSolver{acmeManager: am, caURL: am.CA}.challengeTokensKey(host) - chalInfoBytes, err := am.config.Storage.Load(tokenKey) - if err != nil { - if _, ok := err.(ErrNotExist); !ok { - if am.Logger != nil { - am.Logger.Error("opening distributed HTTP challenge token file", - zap.String("host", host), - zap.Error(err)) - } - } - return false - } - - var challenge acme.Challenge - err = json.Unmarshal(chalInfoBytes, &challenge) + chalInfo, distributed, err := am.config.getChallengeInfo(host) if err != nil { if am.Logger != nil { - am.Logger.Error("decoding HTTP challenge token file (corrupted?)", + am.Logger.Error("looking up info for HTTP challenge", zap.String("host", host), - zap.String("token_key", tokenKey), zap.Error(err)) } return false } - - return am.answerHTTPChallenge(w, r, challenge) + return solveHTTPChallenge(am.Logger, w, r, chalInfo.Challenge, distributed) } -// answerHTTPChallenge solves the challenge with chalInfo. -// Most of this code borrowed from xenolf's built-in HTTP-01 -// challenge solver in March 2018. -func (am *ACMEManager) answerHTTPChallenge(w http.ResponseWriter, r *http.Request, challenge acme.Challenge) bool { +// solveHTTPChallenge solves the HTTP challenge using the given challenge information. +// If the challenge is being solved in a distributed fahsion, set distributed to true for logging purposes. +// It returns true the properties of the request check out in relation to the HTTP challenge. +// Most of this code borrowed from xenolf's built-in HTTP-01 challenge solver in March 2018. +func solveHTTPChallenge(logger *zap.Logger, w http.ResponseWriter, r *http.Request, challenge acme.Challenge, distributed bool) bool { challengeReqPath := challenge.HTTP01ResourcePath() if r.URL.Path == challengeReqPath && strings.EqualFold(hostOnly(r.Host), challenge.Identifier.Value) && // mitigate DNS rebinding attacks @@ -113,17 +95,26 @@ func (am *ACMEManager) answerHTTPChallenge(w http.ResponseWriter, r *http.Reques w.Header().Add("Content-Type", "text/plain") w.Write([]byte(challenge.KeyAuthorization)) r.Close = true - if am.Logger != nil { - am.Logger.Info("served key authentication", + if logger != nil { + logger.Info("served key authentication", zap.String("identifier", challenge.Identifier.Value), zap.String("challenge", "http-01"), - zap.String("remote", r.RemoteAddr)) + zap.String("remote", r.RemoteAddr), + zap.Bool("distributed", distributed)) } return true } return false } +// SolveHTTPChallenge solves the HTTP challenge. It should be used only on HTTP requests that are +// from ACME servers trying to validate an identifier (i.e. LooksLikeHTTPChallenge() == true). It +// returns true if the request criteria check out and it answered with key authentication, in which +// case no further handling of the request is necessary. +func SolveHTTPChallenge(logger *zap.Logger, w http.ResponseWriter, r *http.Request, challenge acme.Challenge) bool { + return solveHTTPChallenge(logger, w, r, challenge, false) +} + // LooksLikeHTTPChallenge returns true if r looks like an ACME // HTTP challenge request from an ACME server. func LooksLikeHTTPChallenge(r *http.Request) bool { diff --git a/vendor/github.com/caddyserver/certmagic/maintain.go b/vendor/github.com/caddyserver/certmagic/maintain.go index 0e7acc3a09..3e5a8736a7 100644 --- a/vendor/github.com/caddyserver/certmagic/maintain.go +++ b/vendor/github.com/caddyserver/certmagic/maintain.go @@ -141,6 +141,9 @@ func (certCache *Cache) RenewManagedCertificates(ctx context.Context) error { } continue } + if cfg.OnDemand != nil { + continue + } // if time is up or expires soon, we need to try to renew it if cert.NeedsRenewal(cfg) { @@ -337,8 +340,8 @@ func (certCache *Cache) updateOCSPStaples(ctx context.Context) { continue } - ocspResp, err := stapleOCSP(cfg.Storage, &cert, nil) - if err != nil { + ocspResp, err := stapleOCSP(cfg.OCSP, cfg.Storage, &cert, nil) + if err != nil || ocspResp == nil { if cert.ocsp != nil { // if there was no staple before, that's fine; otherwise we should log the error if log != nil { diff --git a/vendor/github.com/caddyserver/certmagic/ocsp.go b/vendor/github.com/caddyserver/certmagic/ocsp.go index fedc70e264..4a21546d22 100644 --- a/vendor/github.com/caddyserver/certmagic/ocsp.go +++ b/vendor/github.com/caddyserver/certmagic/ocsp.go @@ -34,11 +34,16 @@ import ( // If you don't have the PEM blocks already, just pass in nil. // // Errors here are not necessarily fatal, it could just be that the -// certificate doesn't have an issuer URL. +// certificate doesn't have an issuer URL. This function may return +// both nil values if OCSP stapling is disabled according to ocspConfig. // // If a status was received, it returns that status. Note that the // returned status is not always stapled to the certificate. -func stapleOCSP(storage Storage, cert *Certificate, pemBundle []byte) (*ocsp.Response, error) { +func stapleOCSP(ocspConfig OCSPConfig, storage Storage, cert *Certificate, pemBundle []byte) (*ocsp.Response, error) { + if ocspConfig.DisableStapling { + return nil, nil + } + if pemBundle == nil { // we need a PEM encoding only for some function calls below bundle := new(bytes.Buffer) @@ -82,7 +87,7 @@ func stapleOCSP(storage Storage, cert *Certificate, pemBundle []byte) (*ocsp.Res // If we couldn't get a fresh staple by reading the cache, // then we need to request it from the OCSP responder if ocspResp == nil || len(ocspBytes) == 0 { - ocspBytes, ocspResp, ocspErr = getOCSPForCert(pemBundle) + ocspBytes, ocspResp, ocspErr = getOCSPForCert(ocspConfig, pemBundle) if ocspErr != nil { // An error here is not a problem because a certificate may simply // not contain a link to an OCSP server. But we should log it anyway. @@ -125,7 +130,7 @@ func stapleOCSP(storage Storage, cert *Certificate, pemBundle []byte) (*ocsp.Res // values are nil, the OCSP status may be assumed OCSPUnknown. // // Borrowed from xenolf. -func getOCSPForCert(bundle []byte) ([]byte, *ocsp.Response, error) { +func getOCSPForCert(ocspConfig OCSPConfig, bundle []byte) ([]byte, *ocsp.Response, error) { // TODO: Perhaps this should be synchronized too, with a Locker? certificates, err := parseCertsFromPEMBundle(bundle) @@ -142,6 +147,18 @@ func getOCSPForCert(bundle []byte) ([]byte, *ocsp.Response, error) { if len(issuedCert.OCSPServer) == 0 { return nil, nil, fmt.Errorf("no OCSP server specified in certificate") } + + // apply override for responder URL + respURL := issuedCert.OCSPServer[0] + if len(ocspConfig.ResponderOverrides) > 0 { + if override, ok := ocspConfig.ResponderOverrides[respURL]; ok { + respURL = override + } + } + if respURL == "" { + return nil, nil, fmt.Errorf("override disables querying OCSP responder: %v", issuedCert.OCSPServer[0]) + } + if len(certificates) == 1 { if len(issuedCert.IssuingCertificateURL) == 0 { return nil, nil, fmt.Errorf("no URL to issuing certificate") @@ -176,7 +193,7 @@ func getOCSPForCert(bundle []byte) ([]byte, *ocsp.Response, error) { } reader := bytes.NewReader(ocspReq) - req, err := http.Post(issuedCert.OCSPServer[0], "application/ocsp-request", reader) + req, err := http.Post(respURL, "application/ocsp-request", reader) if err != nil { return nil, nil, fmt.Errorf("making OCSP request: %v", err) } diff --git a/vendor/github.com/caddyserver/certmagic/solvers.go b/vendor/github.com/caddyserver/certmagic/solvers.go index c0957da3e6..afd0fd28a2 100644 --- a/vendor/github.com/caddyserver/certmagic/solvers.go +++ b/vendor/github.com/caddyserver/certmagic/solvers.go @@ -123,22 +123,19 @@ type tlsALPNSolver struct { // Present adds the certificate to the certificate cache and, if // needed, starts a TLS server for answering TLS-ALPN challenges. func (s *tlsALPNSolver) Present(ctx context.Context, chal acme.Challenge) error { - // load the certificate into the cache; this isn't strictly necessary - // if we're using the distributed solver since our GetCertificate - // function will check storage for the keyAuth anyway, but it seems - // like loading it into the cache is the right thing to do + // we pre-generate the certificate for efficiency with multi-perspective + // validation, so it only has to be done once (at least, by this instance; + // distributed solving does not have that luxury, oh well) - update the + // challenge data in memory to be the generated certificate cert, err := acmez.TLSALPN01ChallengeCert(chal) if err != nil { return err } - certHash := hashCertificateChain(cert.Certificate) - s.config.certCache.mu.Lock() - s.config.certCache.cache[tlsALPNCertKeyName(chal.Identifier.Value)] = Certificate{ - Certificate: *cert, - Names: []string{chal.Identifier.Value}, - hash: certHash, // perhaps not necesssary - } - s.config.certCache.mu.Unlock() + activeChallengesMu.Lock() + chalData := activeChallenges[chal.Identifier.Value] + chalData.data = cert + activeChallenges[chal.Identifier.Value] = chalData + activeChallengesMu.Unlock() // the rest of this function increments the // challenge count for the solver at this @@ -273,13 +270,6 @@ func (s *DNS01Solver) Present(ctx context.Context, challenge acme.Challenge) err dnsName := challenge.DNS01TXTRecordName() keyAuth := challenge.DNS01KeyAuthorization() - rec := libdns.Record{ - Type: "TXT", - Name: dnsName, - Value: keyAuth, - TTL: s.TTL, - } - // multiple identifiers can have the same ACME challenge // domain (e.g. example.com and *.example.com) so we need // to ensure that we don't solve those concurrently and @@ -292,6 +282,13 @@ func (s *DNS01Solver) Present(ctx context.Context, challenge acme.Challenge) err return fmt.Errorf("could not determine zone for domain %q: %v", dnsName, err) } + rec := libdns.Record{ + Type: "TXT", + Name: libdns.RelativeName(dnsName+".", zone), + Value: keyAuth, + TTL: s.TTL, + } + results, err := s.DNSProvider.AppendRecords(ctx, zone, []libdns.Record{rec}) if err != nil { return fmt.Errorf("adding temporary record for zone %s: %w", zone, err) @@ -458,20 +455,19 @@ func (mmu *mapMutex) locked(key interface{}) (ok bool) { // sharing sync and storage, and using the facilities provided by // this package for solving the challenges. type distributedSolver struct { - // The config with a certificate cache - // with a reference to the storage to - // use which is shared among all the - // instances in the cluster - REQUIRED. - acmeManager *ACMEManager + // The storage backing the distributed solver. It must be + // the same storage configuration as what is solving the + // challenge in order to be effective. + storage Storage + + // The storage key prefix, associated with the issuer + // that is solving the challenge. + storageKeyIssuerPrefix string // Since the distributedSolver is only a // wrapper over an actual solver, place // the actual solver here. solver acmez.Solver - - // The CA endpoint URL associated with - // this solver. - caURL string } // Present invokes the underlying solver's Present method @@ -483,7 +479,7 @@ func (dhs distributedSolver) Present(ctx context.Context, chal acme.Challenge) e return err } - err = dhs.acmeManager.config.Storage.Store(dhs.challengeTokensKey(chal.Identifier.Value), infoBytes) + err = dhs.storage.Store(dhs.challengeTokensKey(chal.Identifier.Value), infoBytes) if err != nil { return err } @@ -495,10 +491,18 @@ func (dhs distributedSolver) Present(ctx context.Context, chal acme.Challenge) e return nil } +// Wait wraps the underlying solver's Wait() method, if any. Implements acmez.Waiter. +func (dhs distributedSolver) Wait(ctx context.Context, challenge acme.Challenge) error { + if waiter, ok := dhs.solver.(acmez.Waiter); ok { + return waiter.Wait(ctx, challenge) + } + return nil +} + // CleanUp invokes the underlying solver's CleanUp method // and also cleans up any assets saved to storage. func (dhs distributedSolver) CleanUp(ctx context.Context, chal acme.Challenge) error { - err := dhs.acmeManager.config.Storage.Delete(dhs.challengeTokensKey(chal.Identifier.Value)) + err := dhs.storage.Delete(dhs.challengeTokensKey(chal.Identifier.Value)) if err != nil { return err } @@ -511,7 +515,7 @@ func (dhs distributedSolver) CleanUp(ctx context.Context, chal acme.Challenge) e // challengeTokensPrefix returns the key prefix for challenge info. func (dhs distributedSolver) challengeTokensPrefix() string { - return path.Join(dhs.acmeManager.storageKeyCAPrefix(dhs.caURL), "challenge_tokens") + return path.Join(dhs.storageKeyIssuerPrefix, "challenge_tokens") } // challengeTokensKey returns the key to use to store and access @@ -607,6 +611,15 @@ func dialTCPSocket(addr string) error { return err } +// GetACMEChallenge returns an active ACME challenge for the given identifier, +// or false if no active challenge for that identifier is known. +func GetACMEChallenge(identifier string) (Challenge, bool) { + activeChallengesMu.Lock() + chalData, ok := activeChallenges[identifier] + activeChallengesMu.Unlock() + return chalData, ok +} + // The active challenge solvers, keyed by listener address, // and protected by a mutex. Note that the creation of // solver listeners and the incrementing of their counts @@ -616,8 +629,56 @@ var ( solversMu sync.Mutex ) +// activeChallenges holds information about all known, currently-active +// ACME challenges, keyed by identifier. CertMagic guarantees that +// challenges for the same identifier do not overlap, by its locking +// mechanisms; thus if a challenge comes in for a certain identifier, +// we can be confident that if this process initiated the challenge, +// the correct information to solve it is in this map. (It may have +// alternatively been initiated by another instance in a cluster, in +// which case the distributed solver will take care of that.) +var ( + activeChallenges = make(map[string]Challenge) + activeChallengesMu sync.Mutex +) + +// Challenge is an ACME challenge, but optionally paired with +// data that can make it easier or more efficient to solve. +type Challenge struct { + acme.Challenge + data interface{} +} + +// solverWrapper should be used to wrap all challenge solvers so that +// we can add the challenge info to memory; this makes challenges globally +// solvable by a single HTTP or TLS server even if multiple servers with +// different configurations/scopes need to get certificates. +type solverWrapper struct{ acmez.Solver } + +func (sw solverWrapper) Present(ctx context.Context, chal acme.Challenge) error { + activeChallengesMu.Lock() + activeChallenges[chal.Identifier.Value] = Challenge{Challenge: chal} + activeChallengesMu.Unlock() + return sw.Solver.Present(ctx, chal) +} + +func (sw solverWrapper) Wait(ctx context.Context, chal acme.Challenge) error { + if waiter, ok := sw.Solver.(acmez.Waiter); ok { + return waiter.Wait(ctx, chal) + } + return nil +} + +func (sw solverWrapper) CleanUp(ctx context.Context, chal acme.Challenge) error { + activeChallengesMu.Lock() + delete(activeChallenges, chal.Identifier.Value) + activeChallengesMu.Unlock() + return sw.Solver.CleanUp(ctx, chal) +} + // Interface guards var ( - _ acmez.Solver = (*DNS01Solver)(nil) - _ acmez.Waiter = (*DNS01Solver)(nil) + _ acmez.Solver = (*solverWrapper)(nil) + _ acmez.Waiter = (*solverWrapper)(nil) + _ acmez.Waiter = (*distributedSolver)(nil) ) diff --git a/vendor/github.com/caddyserver/certmagic/storage.go b/vendor/github.com/caddyserver/certmagic/storage.go index f5045a9726..804a474087 100644 --- a/vendor/github.com/caddyserver/certmagic/storage.go +++ b/vendor/github.com/caddyserver/certmagic/storage.go @@ -16,12 +16,13 @@ package certmagic import ( "context" - "log" "path" "regexp" "strings" "sync" "time" + + "go.uber.org/zap" ) // Storage is a type that implements a key-value store. @@ -213,16 +214,20 @@ func (keys KeyBuilder) Safe(str string) string { // this does not cancel the operations that // the locks are synchronizing, this should be // called only immediately before process exit. -func CleanUpOwnLocks() { +// Errors are only reported if a logger is given. +func CleanUpOwnLocks(logger *zap.Logger) { locksMu.Lock() defer locksMu.Unlock() for lockKey, storage := range locks { err := storage.Unlock(lockKey) if err == nil { delete(locks, lockKey) - } else { - log.Printf("[ERROR] Unable to clean up lock: %v (lock=%s storage=%s)", - err, lockKey, storage) + } else if logger != nil { + logger.Error("unable to clean up lock in storage backend", + zap.Any("storage", storage), + zap.String("lock_key", lockKey), + zap.Error(err), + ) } } } @@ -272,6 +277,7 @@ var safeKeyRE = regexp.MustCompile(`[^\w@.-]`) // ErrNotExist is returned by Storage implementations when // a resource is not found. It is similar to os.IsNotExist // except this is a type, not a variable. +// TODO: use new Go error wrapping conventions type ErrNotExist interface { error } diff --git a/vendor/github.com/miekg/dns/Makefile.release b/vendor/github.com/miekg/dns/Makefile.release index 8fb748e8aa..a0ce9b712d 100644 --- a/vendor/github.com/miekg/dns/Makefile.release +++ b/vendor/github.com/miekg/dns/Makefile.release @@ -1,7 +1,7 @@ # Makefile for releasing. # # The release is controlled from version.go. The version found there is -# used to tag the git repo, we're not building any artifects so there is nothing +# used to tag the git repo, we're not building any artifacts so there is nothing # to upload to github. # # * Up the version in version.go diff --git a/vendor/github.com/miekg/dns/client.go b/vendor/github.com/miekg/dns/client.go index 000dc013cf..f907698b5d 100644 --- a/vendor/github.com/miekg/dns/client.go +++ b/vendor/github.com/miekg/dns/client.go @@ -379,7 +379,7 @@ func Dial(network, address string) (conn *Conn, err error) { func ExchangeContext(ctx context.Context, m *Msg, a string) (r *Msg, err error) { client := Client{Net: "udp"} r, _, err = client.ExchangeContext(ctx, m, a) - // ignorint rtt to leave the original ExchangeContext API unchanged, but + // ignoring rtt to leave the original ExchangeContext API unchanged, but // this function will go away return r, err } diff --git a/vendor/github.com/miekg/dns/defaults.go b/vendor/github.com/miekg/dns/defaults.go index d874e3008c..d47b0b1f2b 100644 --- a/vendor/github.com/miekg/dns/defaults.go +++ b/vendor/github.com/miekg/dns/defaults.go @@ -349,10 +349,7 @@ func ReverseAddr(addr string) (arpa string, err error) { // Add it, in reverse, to the buffer for i := len(ip) - 1; i >= 0; i-- { v := ip[i] - buf = append(buf, hexDigit[v&0xF]) - buf = append(buf, '.') - buf = append(buf, hexDigit[v>>4]) - buf = append(buf, '.') + buf = append(buf, hexDigit[v&0xF], '.', hexDigit[v>>4], '.') } // Append "ip6.arpa." and return (buf already has the final .) buf = append(buf, "ip6.arpa."...) diff --git a/vendor/github.com/miekg/dns/dnssec.go b/vendor/github.com/miekg/dns/dnssec.go index 900f6e059d..80d2be5a89 100644 --- a/vendor/github.com/miekg/dns/dnssec.go +++ b/vendor/github.com/miekg/dns/dnssec.go @@ -4,6 +4,7 @@ import ( "bytes" "crypto" "crypto/ecdsa" + "crypto/ed25519" "crypto/elliptic" "crypto/rand" "crypto/rsa" @@ -17,8 +18,6 @@ import ( "sort" "strings" "time" - - "golang.org/x/crypto/ed25519" ) // DNSSEC encryption algorithm codes. @@ -500,7 +499,7 @@ func (rr *RRSIG) ValidityPeriod(t time.Time) bool { return ti <= utc && utc <= te } -// Return the signatures base64 encodedig sigdata as a byte slice. +// Return the signatures base64 encoding sigdata as a byte slice. func (rr *RRSIG) sigBuf() []byte { sigbuf, err := fromBase64([]byte(rr.Signature)) if err != nil { diff --git a/vendor/github.com/miekg/dns/dnssec_keygen.go b/vendor/github.com/miekg/dns/dnssec_keygen.go index 2ab7b6d73b..b8124b5618 100644 --- a/vendor/github.com/miekg/dns/dnssec_keygen.go +++ b/vendor/github.com/miekg/dns/dnssec_keygen.go @@ -3,12 +3,11 @@ package dns import ( "crypto" "crypto/ecdsa" + "crypto/ed25519" "crypto/elliptic" "crypto/rand" "crypto/rsa" "math/big" - - "golang.org/x/crypto/ed25519" ) // Generate generates a DNSKEY of the given bit size. diff --git a/vendor/github.com/miekg/dns/dnssec_keyscan.go b/vendor/github.com/miekg/dns/dnssec_keyscan.go index 6cbc28483f..f79658169f 100644 --- a/vendor/github.com/miekg/dns/dnssec_keyscan.go +++ b/vendor/github.com/miekg/dns/dnssec_keyscan.go @@ -4,13 +4,12 @@ import ( "bufio" "crypto" "crypto/ecdsa" + "crypto/ed25519" "crypto/rsa" "io" "math/big" "strconv" "strings" - - "golang.org/x/crypto/ed25519" ) // NewPrivateKey returns a PrivateKey by parsing the string s. diff --git a/vendor/github.com/miekg/dns/dnssec_privkey.go b/vendor/github.com/miekg/dns/dnssec_privkey.go index 072e445dad..f160772964 100644 --- a/vendor/github.com/miekg/dns/dnssec_privkey.go +++ b/vendor/github.com/miekg/dns/dnssec_privkey.go @@ -3,11 +3,10 @@ package dns import ( "crypto" "crypto/ecdsa" + "crypto/ed25519" "crypto/rsa" "math/big" "strconv" - - "golang.org/x/crypto/ed25519" ) const format = "Private-key-format: v1.3\n" diff --git a/vendor/github.com/miekg/dns/edns.go b/vendor/github.com/miekg/dns/edns.go index f3fb1c6849..1a87f4cb93 100644 --- a/vendor/github.com/miekg/dns/edns.go +++ b/vendor/github.com/miekg/dns/edns.go @@ -525,7 +525,7 @@ func (e *EDNS0_N3U) String() string { } func (e *EDNS0_N3U) copy() EDNS0 { return &EDNS0_N3U{e.Code, e.AlgCode} } -// EDNS0_EXPIRE implementes the EDNS0 option as described in RFC 7314. +// EDNS0_EXPIRE implements the EDNS0 option as described in RFC 7314. type EDNS0_EXPIRE struct { Code uint16 // Always EDNS0EXPIRE Expire uint32 diff --git a/vendor/github.com/miekg/dns/go.mod b/vendor/github.com/miekg/dns/go.mod index 6003d0573c..51619b7842 100644 --- a/vendor/github.com/miekg/dns/go.mod +++ b/vendor/github.com/miekg/dns/go.mod @@ -1,11 +1,9 @@ module github.com/miekg/dns -go 1.12 +go 1.13 require ( - golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550 - golang.org/x/net v0.0.0-20190923162816-aa69164e4478 - golang.org/x/sync v0.0.0-20190423024810-112230192c58 - golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe - golang.org/x/tools v0.0.0-20191216052735-49a3e744a425 // indirect + golang.org/x/net v0.0.0-20210226172049-e18ecbb05110 + golang.org/x/sync v0.0.0-20210220032951-036812b2e83c + golang.org/x/sys v0.0.0-20210303074136-134d130e1a04 ) diff --git a/vendor/github.com/miekg/dns/go.sum b/vendor/github.com/miekg/dns/go.sum index 96bda3a941..3359ebea4e 100644 --- a/vendor/github.com/miekg/dns/go.sum +++ b/vendor/github.com/miekg/dns/go.sum @@ -1,39 +1,10 @@ -golang.org/x/crypto v0.0.0-20181001203147-e3636079e1a4 h1:Vk3wNqEZwyGyei9yq5ekj7frek2u7HUfffJ1/opblzc= -golang.org/x/crypto v0.0.0-20181001203147-e3636079e1a4/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= -golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20190829043050-9756ffdc2472 h1:Gv7RPwsi3eZ2Fgewe3CBsuOebPwO27PoXzRpJPsvSSM= -golang.org/x/crypto v0.0.0-20190829043050-9756ffdc2472/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392 h1:ACG4HJsFiNMf47Y4PeRoebLNy/2lXT9EtprMuTFWt1M= -golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY= -golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550 h1:ObdrDkeb4kJdCP557AjRjq69pTHfNouLtWZG7j9rPN8= -golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= -golang.org/x/net v0.0.0-20180926154720-4dfa2610cdf3 h1:dgd4x4kJt7G4k4m93AYLzM8Ni6h2qLTfh9n9vXJT3/0= -golang.org/x/net v0.0.0-20180926154720-4dfa2610cdf3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297 h1:k7pJ2yAPLPgbskkFdhRCsA77k2fySZ1zf2zCjvQCiIM= -golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190923162816-aa69164e4478 h1:l5EDrHhldLYb3ZRHDUhXF7Om7MvYXnkV9/iQNo1lX6g= -golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f h1:wMNYb4v58l5UBM7MYRLPG6ZhfOqbKu7X5eyFl8ZhKvA= -golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190423024810-112230192c58 h1:8gQV6CLnAEikrhgkHFbMAEhagSSnXWGV915qUMm9mrU= -golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sys v0.0.0-20180928133829-e4b3c5e90611 h1:O33LKL7WyJgjN9CvxfTIomjIClbd/Kq86/iipowHQU0= -golang.org/x/sys v0.0.0-20180928133829-e4b3c5e90611/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190904154756-749cb33beabd h1:DBH9mDw0zluJT/R+nGuV3jWFWLFaHyYZWD4tOT+cjn0= -golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190922100055-0a153f010e69/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe h1:6fAMxZRR6sl1Uq8U61gxU+kPTs2tR8uOySCbBP7BN/M= -golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110 h1:qWPm9rbaAMKs8Bq/9LRpbMqxWRVUAQwMI9fVrssnTfw= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210303074136-134d130e1a04 h1:cEhElsAv9LUt9ZUUocxzWe05oFLVd+AA2nstydTeI8g= +golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190907020128-2ca718005c18/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191216052735-49a3e744a425 h1:VvQyQJN0tSuecqgcIxMWnnfG5kSmgy9KZR9sW3W5QeA= -golang.org/x/tools v0.0.0-20191216052735-49a3e744a425/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/vendor/github.com/miekg/dns/labels.go b/vendor/github.com/miekg/dns/labels.go index df1675dfd2..f9faacfeb4 100644 --- a/vendor/github.com/miekg/dns/labels.go +++ b/vendor/github.com/miekg/dns/labels.go @@ -10,7 +10,7 @@ package dns // escaped dots (\.) for instance. // s must be a syntactically valid domain name, see IsDomainName. func SplitDomainName(s string) (labels []string) { - if len(s) == 0 { + if s == "" { return nil } fqdnEnd := 0 // offset of the final '.' or the length of the name diff --git a/vendor/github.com/miekg/dns/msg.go b/vendor/github.com/miekg/dns/msg.go index 1728a98b7d..ead4b6931d 100644 --- a/vendor/github.com/miekg/dns/msg.go +++ b/vendor/github.com/miekg/dns/msg.go @@ -742,7 +742,7 @@ func (dns *Msg) packBufferWithCompressionMap(buf []byte, compression compression } // Set extended rcode unconditionally if we have an opt, this will allow - // reseting the extended rcode bits if they need to. + // resetting the extended rcode bits if they need to. if opt := dns.IsEdns0(); opt != nil { opt.SetExtendedRcode(uint16(dns.Rcode)) } else if dns.Rcode > 0xF { diff --git a/vendor/github.com/miekg/dns/privaterr.go b/vendor/github.com/miekg/dns/privaterr.go index cda6cae31e..45c7f26d85 100644 --- a/vendor/github.com/miekg/dns/privaterr.go +++ b/vendor/github.com/miekg/dns/privaterr.go @@ -6,7 +6,7 @@ import "strings" // RFC 6895. This allows one to experiment with new RR types, without requesting an // official type code. Also see dns.PrivateHandle and dns.PrivateHandleRemove. type PrivateRdata interface { - // String returns the text presentaton of the Rdata of the Private RR. + // String returns the text presentation of the Rdata of the Private RR. String() string // Parse parses the Rdata of the private RR. Parse([]string) error diff --git a/vendor/github.com/miekg/dns/scan.go b/vendor/github.com/miekg/dns/scan.go index 67161de29c..39055bde31 100644 --- a/vendor/github.com/miekg/dns/scan.go +++ b/vendor/github.com/miekg/dns/scan.go @@ -1233,7 +1233,7 @@ func stringToCm(token string) (e, m uint8, ok bool) { // 'nn.1' must be treated as 'nn-meters and 10cm, not 1cm. cmeters *= 10 } - if len(s[0]) == 0 { + if s[0] == "" { // This will allow omitting the 'meter' part, like .01 (meaning 0.01m = 1cm). break } @@ -1352,7 +1352,7 @@ func stringToNodeID(l lex) (uint64, *ParseError) { if len(l.token) < 19 { return 0, &ParseError{l.token, "bad NID/L64 NodeID/Locator64", l} } - // There must be three colons at fixes postitions, if not its a parse error + // There must be three colons at fixes positions, if not its a parse error if l.token[4] != ':' && l.token[9] != ':' && l.token[14] != ':' { return 0, &ParseError{l.token, "bad NID/L64 NodeID/Locator64", l} } diff --git a/vendor/github.com/miekg/dns/scan_rr.go b/vendor/github.com/miekg/dns/scan_rr.go index 23b4043bcd..05765aed87 100644 --- a/vendor/github.com/miekg/dns/scan_rr.go +++ b/vendor/github.com/miekg/dns/scan_rr.go @@ -609,7 +609,7 @@ func (rr *LOC) parse(c *zlexer, o string) *ParseError { c.Next() // zBlank l, _ = c.Next() - if i, err := strconv.ParseFloat(l.token, 32); err != nil || l.err || i < 0 || i >= 60 { + if i, err := strconv.ParseFloat(l.token, 64); err != nil || l.err || i < 0 || i >= 60 { return &ParseError{"", "bad LOC Latitude seconds", l} } else { rr.Latitude += uint32(1000 * i) @@ -645,7 +645,7 @@ East: } c.Next() // zBlank l, _ = c.Next() - if i, err := strconv.ParseFloat(l.token, 32); err != nil || l.err || i < 0 || i >= 60 { + if i, err := strconv.ParseFloat(l.token, 64); err != nil || l.err || i < 0 || i >= 60 { return &ParseError{"", "bad LOC Longitude seconds", l} } else { rr.Longitude += uint32(1000 * i) @@ -662,7 +662,7 @@ East: Altitude: c.Next() // zBlank l, _ = c.Next() - if len(l.token) == 0 || l.err { + if l.token == "" || l.err { return &ParseError{"", "bad LOC Altitude", l} } if l.token[len(l.token)-1] == 'M' || l.token[len(l.token)-1] == 'm' { @@ -722,7 +722,7 @@ func (rr *HIP) parse(c *zlexer, o string) *ParseError { c.Next() // zBlank l, _ = c.Next() // zString - if len(l.token) == 0 || l.err { + if l.token == "" || l.err { return &ParseError{"", "bad HIP Hit", l} } rr.Hit = l.token // This can not contain spaces, see RFC 5205 Section 6. @@ -730,7 +730,7 @@ func (rr *HIP) parse(c *zlexer, o string) *ParseError { c.Next() // zBlank l, _ = c.Next() // zString - if len(l.token) == 0 || l.err { + if l.token == "" || l.err { return &ParseError{"", "bad HIP PublicKey", l} } rr.PublicKey = l.token // This cannot contain spaces @@ -846,6 +846,38 @@ func (rr *CSYNC) parse(c *zlexer, o string) *ParseError { return nil } +func (rr *ZONEMD) parse(c *zlexer, o string) *ParseError { + l, _ := c.Next() + i, e := strconv.ParseUint(l.token, 10, 32) + if e != nil || l.err { + return &ParseError{"", "bad ZONEMD Serial", l} + } + rr.Serial = uint32(i) + + c.Next() // zBlank + l, _ = c.Next() + i, e1 := strconv.ParseUint(l.token, 10, 8) + if e1 != nil || l.err { + return &ParseError{"", "bad ZONEMD Scheme", l} + } + rr.Scheme = uint8(i) + + c.Next() // zBlank + l, _ = c.Next() + i, err := strconv.ParseUint(l.token, 10, 8) + if err != nil || l.err { + return &ParseError{"", "bad ZONEMD Hash Algorithm", l} + } + rr.Hash = uint8(i) + + s, e2 := endingToString(c, "bad ZONEMD Digest") + if e2 != nil { + return e2 + } + rr.Digest = s + return nil +} + func (rr *SIG) parse(c *zlexer, o string) *ParseError { return rr.RRSIG.parse(c, o) } func (rr *RRSIG) parse(c *zlexer, o string) *ParseError { @@ -997,7 +1029,7 @@ func (rr *NSEC3) parse(c *zlexer, o string) *ParseError { rr.Iterations = uint16(i) c.Next() l, _ = c.Next() - if len(l.token) == 0 || l.err { + if l.token == "" || l.err { return &ParseError{"", "bad NSEC3 Salt", l} } if l.token != "-" { @@ -1007,7 +1039,7 @@ func (rr *NSEC3) parse(c *zlexer, o string) *ParseError { c.Next() l, _ = c.Next() - if len(l.token) == 0 || l.err { + if l.token == "" || l.err { return &ParseError{"", "bad NSEC3 NextDomain", l} } rr.HashLength = 20 // Fix for NSEC3 (sha1 160 bits) diff --git a/vendor/github.com/miekg/dns/sig0.go b/vendor/github.com/miekg/dns/sig0.go index 9ef13ccf39..e781c9bb6c 100644 --- a/vendor/github.com/miekg/dns/sig0.go +++ b/vendor/github.com/miekg/dns/sig0.go @@ -17,7 +17,7 @@ func (rr *SIG) Sign(k crypto.Signer, m *Msg) ([]byte, error) { if k == nil { return nil, ErrPrivKey } - if rr.KeyTag == 0 || len(rr.SignerName) == 0 || rr.Algorithm == 0 { + if rr.KeyTag == 0 || rr.SignerName == "" || rr.Algorithm == 0 { return nil, ErrKey } @@ -78,7 +78,7 @@ func (rr *SIG) Verify(k *KEY, buf []byte) error { if k == nil { return ErrKey } - if rr.KeyTag == 0 || len(rr.SignerName) == 0 || rr.Algorithm == 0 { + if rr.KeyTag == 0 || rr.SignerName == "" || rr.Algorithm == 0 { return ErrKey } diff --git a/vendor/github.com/miekg/dns/svcb.go b/vendor/github.com/miekg/dns/svcb.go index 1373fe21b7..ec0a76f4cf 100644 --- a/vendor/github.com/miekg/dns/svcb.go +++ b/vendor/github.com/miekg/dns/svcb.go @@ -321,7 +321,7 @@ func (s *SVCBAlpn) pack() ([]byte, error) { // Liberally estimate the size of an alpn as 10 octets b := make([]byte, 0, 10*len(s.Alpn)) for _, e := range s.Alpn { - if len(e) == 0 { + if e == "" { return nil, errors.New("dns: svcbalpn: empty alpn-id") } if len(e) > 255 { @@ -390,7 +390,7 @@ func (*SVCBNoDefaultAlpn) unpack(b []byte) error { } func (*SVCBNoDefaultAlpn) parse(b string) error { - if len(b) != 0 { + if b != "" { return errors.New("dns: svcbnodefaultalpn: no_default_alpn must have no value") } return nil diff --git a/vendor/github.com/miekg/dns/types.go b/vendor/github.com/miekg/dns/types.go index 9e379eb351..99dd315bf1 100644 --- a/vendor/github.com/miekg/dns/types.go +++ b/vendor/github.com/miekg/dns/types.go @@ -81,6 +81,7 @@ const ( TypeCDNSKEY uint16 = 60 TypeOPENPGPKEY uint16 = 61 TypeCSYNC uint16 = 62 + TypeZONEMD uint16 = 63 TypeSVCB uint16 = 64 TypeHTTPS uint16 = 65 TypeSPF uint16 = 99 @@ -150,6 +151,17 @@ const ( OpcodeUpdate = 5 ) +// Used in ZONEMD https://tools.ietf.org/html/rfc8976 + +const ( + // ZoneMD Accepted Schemes + ZoneMDSchemeSimple = 1 + + // ZoneMD Hash Algorithms + ZoneMDHashAlgSHA384 = 1 + ZoneMDHashAlgSHA512 = 2 +) + // Header is the wire format for the DNS packet header. type Header struct { Id uint16 @@ -1361,6 +1373,23 @@ func (rr *CSYNC) len(off int, compression map[string]struct{}) int { return l } +// ZONEMD RR, from draft-ietf-dnsop-dns-zone-digest +type ZONEMD struct { + Hdr RR_Header + Serial uint32 + Scheme uint8 + Hash uint8 + Digest string `dns:"hex"` +} + +func (rr *ZONEMD) String() string { + return rr.Hdr.String() + + strconv.Itoa(int(rr.Serial)) + + " " + strconv.Itoa(int(rr.Scheme)) + + " " + strconv.Itoa(int(rr.Hash)) + + " " + rr.Digest +} + // APL RR. See RFC 3123. type APL struct { Hdr RR_Header @@ -1472,7 +1501,7 @@ func StringToTime(s string) (uint32, error) { // saltToString converts a NSECX salt to uppercase and returns "-" when it is empty. func saltToString(s string) string { - if len(s) == 0 { + if s == "" { return "-" } return strings.ToUpper(s) diff --git a/vendor/github.com/miekg/dns/version.go b/vendor/github.com/miekg/dns/version.go index 8f7cf76881..5a358acc94 100644 --- a/vendor/github.com/miekg/dns/version.go +++ b/vendor/github.com/miekg/dns/version.go @@ -3,7 +3,7 @@ package dns import "fmt" // Version is current version of this library. -var Version = v{1, 1, 40} +var Version = v{1, 1, 41} // v holds the version of this library. type v struct { diff --git a/vendor/github.com/miekg/dns/zduplicate.go b/vendor/github.com/miekg/dns/zduplicate.go index 0d3b34bd9b..9eb1dac299 100644 --- a/vendor/github.com/miekg/dns/zduplicate.go +++ b/vendor/github.com/miekg/dns/zduplicate.go @@ -1317,3 +1317,24 @@ func (r1 *X25) isDuplicate(_r2 RR) bool { } return true } + +func (r1 *ZONEMD) isDuplicate(_r2 RR) bool { + r2, ok := _r2.(*ZONEMD) + if !ok { + return false + } + _ = r2 + if r1.Serial != r2.Serial { + return false + } + if r1.Scheme != r2.Scheme { + return false + } + if r1.Hash != r2.Hash { + return false + } + if r1.Digest != r2.Digest { + return false + } + return true +} diff --git a/vendor/github.com/miekg/dns/zmsg.go b/vendor/github.com/miekg/dns/zmsg.go index d24a10fa24..fc0822f982 100644 --- a/vendor/github.com/miekg/dns/zmsg.go +++ b/vendor/github.com/miekg/dns/zmsg.go @@ -1118,6 +1118,26 @@ func (rr *X25) pack(msg []byte, off int, compression compressionMap, compress bo return off, nil } +func (rr *ZONEMD) pack(msg []byte, off int, compression compressionMap, compress bool) (off1 int, err error) { + off, err = packUint32(rr.Serial, msg, off) + if err != nil { + return off, err + } + off, err = packUint8(rr.Scheme, msg, off) + if err != nil { + return off, err + } + off, err = packUint8(rr.Hash, msg, off) + if err != nil { + return off, err + } + off, err = packStringHex(rr.Digest, msg, off) + if err != nil { + return off, err + } + return off, nil +} + // unpack*() functions func (rr *A) unpack(msg []byte, off int) (off1 int, err error) { @@ -2821,3 +2841,35 @@ func (rr *X25) unpack(msg []byte, off int) (off1 int, err error) { } return off, nil } + +func (rr *ZONEMD) unpack(msg []byte, off int) (off1 int, err error) { + rdStart := off + _ = rdStart + + rr.Serial, off, err = unpackUint32(msg, off) + if err != nil { + return off, err + } + if off == len(msg) { + return off, nil + } + rr.Scheme, off, err = unpackUint8(msg, off) + if err != nil { + return off, err + } + if off == len(msg) { + return off, nil + } + rr.Hash, off, err = unpackUint8(msg, off) + if err != nil { + return off, err + } + if off == len(msg) { + return off, nil + } + rr.Digest, off, err = unpackStringHex(msg, off, rdStart+int(rr.Hdr.Rdlength)) + if err != nil { + return off, err + } + return off, nil +} diff --git a/vendor/github.com/miekg/dns/ztypes.go b/vendor/github.com/miekg/dns/ztypes.go index 11b51bf217..5d060cfee1 100644 --- a/vendor/github.com/miekg/dns/ztypes.go +++ b/vendor/github.com/miekg/dns/ztypes.go @@ -82,6 +82,7 @@ var TypeToRR = map[uint16]func() RR{ TypeUINFO: func() RR { return new(UINFO) }, TypeURI: func() RR { return new(URI) }, TypeX25: func() RR { return new(X25) }, + TypeZONEMD: func() RR { return new(ZONEMD) }, } // TypeToString is a map of strings for each RR type. @@ -168,6 +169,7 @@ var TypeToString = map[uint16]string{ TypeUNSPEC: "UNSPEC", TypeURI: "URI", TypeX25: "X25", + TypeZONEMD: "ZONEMD", TypeNSAPPTR: "NSAP-PTR", } @@ -245,6 +247,7 @@ func (rr *UID) Header() *RR_Header { return &rr.Hdr } func (rr *UINFO) Header() *RR_Header { return &rr.Hdr } func (rr *URI) Header() *RR_Header { return &rr.Hdr } func (rr *X25) Header() *RR_Header { return &rr.Hdr } +func (rr *ZONEMD) Header() *RR_Header { return &rr.Hdr } // len() functions func (rr *A) len(off int, compression map[string]struct{}) int { @@ -684,6 +687,14 @@ func (rr *X25) len(off int, compression map[string]struct{}) int { l += len(rr.PSDNAddress) + 1 return l } +func (rr *ZONEMD) len(off int, compression map[string]struct{}) int { + l := rr.Hdr.len(off, compression) + l += 4 // Serial + l++ // Scheme + l++ // Hash + l += len(rr.Digest) / 2 + return l +} // copy() functions func (rr *A) copy() RR { @@ -936,3 +947,6 @@ func (rr *URI) copy() RR { func (rr *X25) copy() RR { return &X25{rr.Hdr, rr.PSDNAddress} } +func (rr *ZONEMD) copy() RR { + return &ZONEMD{rr.Hdr, rr.Serial, rr.Scheme, rr.Hash, rr.Digest} +} |