2018-12-10 13:15:26 +10:00
|
|
|
// Copyright 2015 Matthew Holt
|
|
|
|
//
|
|
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
// you may not use this file except in compliance with the License.
|
|
|
|
// You may obtain a copy of the License at
|
|
|
|
//
|
|
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
//
|
|
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
// See the License for the specific language governing permissions and
|
|
|
|
// limitations under the License.
|
|
|
|
|
|
|
|
package certmagic
|
|
|
|
|
|
|
|
import (
|
|
|
|
"bufio"
|
2021-01-21 09:01:53 +10:00
|
|
|
"bytes"
|
2021-01-21 07:52:08 +10:00
|
|
|
"context"
|
2018-12-10 13:15:26 +10:00
|
|
|
"crypto/ecdsa"
|
|
|
|
"crypto/elliptic"
|
|
|
|
"crypto/rand"
|
|
|
|
"encoding/json"
|
2022-03-08 04:11:20 +10:00
|
|
|
"errors"
|
2018-12-10 13:15:26 +10:00
|
|
|
"fmt"
|
|
|
|
"io"
|
2022-03-08 04:11:20 +10:00
|
|
|
"io/fs"
|
2018-12-10 13:15:26 +10:00
|
|
|
"os"
|
2018-12-13 07:47:22 +10:00
|
|
|
"path"
|
2018-12-10 13:15:26 +10:00
|
|
|
"sort"
|
|
|
|
"strings"
|
2021-04-13 03:57:11 +10:00
|
|
|
"sync"
|
2018-12-10 13:15:26 +10:00
|
|
|
|
2024-04-09 06:05:43 +10:00
|
|
|
"github.com/mholt/acmez/v2/acme"
|
2024-06-04 11:47:29 +10:00
|
|
|
"go.uber.org/zap"
|
2018-12-10 13:15:26 +10:00
|
|
|
)
|
|
|
|
|
2020-07-28 08:50:41 +10:00
|
|
|
// getAccount either loads or creates a new account, depending on if
|
|
|
|
// an account can be found in storage for the given CA + email combo.
|
2022-03-25 03:34:31 +10:00
|
|
|
func (am *ACMEIssuer) getAccount(ctx context.Context, ca, email string) (acme.Account, error) {
|
2022-03-08 05:26:52 +10:00
|
|
|
acct, err := am.loadAccount(ctx, ca, email)
|
2022-03-08 04:11:20 +10:00
|
|
|
if errors.Is(err, fs.ErrNotExist) {
|
2024-06-04 11:47:29 +10:00
|
|
|
am.Logger.Info("creating new account because no account for configured email is known to us",
|
|
|
|
zap.String("email", email),
|
|
|
|
zap.String("ca", ca),
|
|
|
|
zap.Error(err))
|
2022-03-08 04:11:20 +10:00
|
|
|
return am.newAccount(email)
|
2021-01-21 09:01:53 +10:00
|
|
|
}
|
2024-06-04 11:47:29 +10:00
|
|
|
am.Logger.Debug("using existing ACME account because key found in storage associated with email",
|
|
|
|
zap.String("email", email),
|
|
|
|
zap.String("ca", ca))
|
2021-01-21 09:01:53 +10:00
|
|
|
return acct, err
|
|
|
|
}
|
|
|
|
|
|
|
|
// loadAccount loads an account from storage, but does not create a new one.
|
2022-03-25 03:34:31 +10:00
|
|
|
func (am *ACMEIssuer) loadAccount(ctx context.Context, ca, email string) (acme.Account, error) {
|
2022-03-08 05:26:52 +10:00
|
|
|
regBytes, err := am.config.Storage.Load(ctx, am.storageKeyUserReg(ca, email))
|
2021-01-21 09:01:53 +10:00
|
|
|
if err != nil {
|
2020-07-28 08:50:41 +10:00
|
|
|
return acme.Account{}, err
|
|
|
|
}
|
2022-03-08 05:26:52 +10:00
|
|
|
keyBytes, err := am.config.Storage.Load(ctx, am.storageKeyUserPrivateKey(ca, email))
|
2020-07-28 08:50:41 +10:00
|
|
|
if err != nil {
|
|
|
|
return acme.Account{}, err
|
|
|
|
}
|
2018-12-10 13:15:26 +10:00
|
|
|
|
2020-07-28 08:50:41 +10:00
|
|
|
var acct acme.Account
|
|
|
|
err = json.Unmarshal(regBytes, &acct)
|
|
|
|
if err != nil {
|
|
|
|
return acct, err
|
|
|
|
}
|
2022-02-18 07:37:50 +10:00
|
|
|
acct.PrivateKey, err = PEMDecodePrivateKey(keyBytes)
|
2020-08-27 04:54:29 +10:00
|
|
|
if err != nil {
|
|
|
|
return acct, fmt.Errorf("could not decode account's private key: %v", err)
|
|
|
|
}
|
2020-07-31 04:13:45 +10:00
|
|
|
|
|
|
|
return acct, nil
|
|
|
|
}
|
|
|
|
|
2020-07-28 08:50:41 +10:00
|
|
|
// newAccount generates a new private key for a new ACME account, but
|
|
|
|
// it does not register or save the account.
|
2022-03-25 03:34:31 +10:00
|
|
|
func (*ACMEIssuer) newAccount(email string) (acme.Account, error) {
|
2020-07-28 08:50:41 +10:00
|
|
|
var acct acme.Account
|
|
|
|
if email != "" {
|
|
|
|
acct.Contact = []string{"mailto:" + email} // TODO: should we abstract the contact scheme?
|
|
|
|
}
|
|
|
|
privateKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
|
|
|
if err != nil {
|
|
|
|
return acct, fmt.Errorf("generating private key: %v", err)
|
|
|
|
}
|
|
|
|
acct.PrivateKey = privateKey
|
|
|
|
return acct, nil
|
2018-12-10 13:15:26 +10:00
|
|
|
}
|
|
|
|
|
2021-01-21 09:01:53 +10:00
|
|
|
// 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.
|
2022-03-25 03:34:31 +10:00
|
|
|
func (am *ACMEIssuer) GetAccount(ctx context.Context, privateKeyPEM []byte) (acme.Account, error) {
|
2024-04-19 05:42:33 +10:00
|
|
|
email := am.getEmail()
|
|
|
|
if email == "" {
|
|
|
|
if account, err := am.loadAccountByKey(ctx, privateKeyPEM); err == nil {
|
|
|
|
return account, nil
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
keyBytes, err := am.config.Storage.Load(ctx, am.storageKeyUserPrivateKey(am.CA, email))
|
|
|
|
if err == nil && bytes.Equal(bytes.TrimSpace(keyBytes), bytes.TrimSpace(privateKeyPEM)) {
|
|
|
|
return am.loadAccount(ctx, am.CA, email)
|
|
|
|
}
|
2021-01-21 09:01:53 +10:00
|
|
|
}
|
2024-04-19 05:42:33 +10:00
|
|
|
return am.lookUpAccount(ctx, privateKeyPEM)
|
2021-01-21 09:01:53 +10:00
|
|
|
}
|
|
|
|
|
|
|
|
// loadAccountByKey loads the account with the given private key from storage, if it exists.
|
2022-03-08 04:11:20 +10:00
|
|
|
// If it does not exist, an error of type fs.ErrNotExist is returned. This is not very efficient
|
2021-01-21 09:01:53 +10:00
|
|
|
// for lots of accounts.
|
2022-03-25 03:34:31 +10:00
|
|
|
func (am *ACMEIssuer) loadAccountByKey(ctx context.Context, privateKeyPEM []byte) (acme.Account, error) {
|
2022-03-08 05:26:52 +10:00
|
|
|
accountList, err := am.config.Storage.List(ctx, am.storageKeyUsersPrefix(am.CA), false)
|
2021-01-21 09:01:53 +10:00
|
|
|
if err != nil {
|
|
|
|
return acme.Account{}, err
|
|
|
|
}
|
|
|
|
for _, accountFolderKey := range accountList {
|
|
|
|
email := path.Base(accountFolderKey)
|
2022-03-08 05:26:52 +10:00
|
|
|
keyBytes, err := am.config.Storage.Load(ctx, am.storageKeyUserPrivateKey(am.CA, email))
|
2021-01-21 09:01:53 +10:00
|
|
|
if err != nil {
|
2024-04-19 05:42:33 +10:00
|
|
|
// Try the next account: This one is missing its private key, if it turns out to be the one we're looking
|
|
|
|
// for we will try to save it again after confirming with the ACME server.
|
|
|
|
continue
|
2021-01-21 09:01:53 +10:00
|
|
|
}
|
|
|
|
if bytes.Equal(bytes.TrimSpace(keyBytes), bytes.TrimSpace(privateKeyPEM)) {
|
2024-04-19 05:42:33 +10:00
|
|
|
// Found the account with the correct private key, try loading it. If this fails we we will follow
|
|
|
|
// the same procedure as if the private key was not found and confirm with the ACME server before saving
|
|
|
|
// it again.
|
2022-03-08 05:26:52 +10:00
|
|
|
return am.loadAccount(ctx, am.CA, email)
|
2021-01-21 09:01:53 +10:00
|
|
|
}
|
|
|
|
}
|
2022-03-08 04:11:20 +10:00
|
|
|
return acme.Account{}, fs.ErrNotExist
|
2021-01-21 09:01:53 +10:00
|
|
|
}
|
|
|
|
|
|
|
|
// 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.
|
2022-03-25 03:34:31 +10:00
|
|
|
func (am *ACMEIssuer) lookUpAccount(ctx context.Context, privateKeyPEM []byte) (acme.Account, error) {
|
2021-01-21 07:52:08 +10:00
|
|
|
client, err := am.newACMEClient(false)
|
|
|
|
if err != nil {
|
|
|
|
return acme.Account{}, fmt.Errorf("creating ACME client: %v", err)
|
|
|
|
}
|
|
|
|
|
2022-02-18 07:37:50 +10:00
|
|
|
privateKey, err := PEMDecodePrivateKey([]byte(privateKeyPEM))
|
2021-01-21 07:52:08 +10:00
|
|
|
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
|
2022-03-08 05:26:52 +10:00
|
|
|
err = am.saveAccount(ctx, client.Directory, account)
|
2021-01-21 07:52:08 +10:00
|
|
|
if err != nil {
|
|
|
|
return account, fmt.Errorf("could not save account to storage: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
return account, nil
|
|
|
|
}
|
|
|
|
|
2020-07-28 08:50:41 +10:00
|
|
|
// saveAccount persists an ACME account's info and private key to storage.
|
|
|
|
// It does NOT register the account via ACME or prompt the user.
|
2022-03-25 03:34:31 +10:00
|
|
|
func (am *ACMEIssuer) saveAccount(ctx context.Context, ca string, account acme.Account) error {
|
2020-07-28 08:50:41 +10:00
|
|
|
regBytes, err := json.MarshalIndent(account, "", "\t")
|
2018-12-10 13:15:26 +10:00
|
|
|
if err != nil {
|
2020-07-28 08:50:41 +10:00
|
|
|
return err
|
2018-12-10 13:15:26 +10:00
|
|
|
}
|
2022-02-18 07:37:50 +10:00
|
|
|
keyBytes, err := PEMEncodePrivateKey(account.PrivateKey)
|
2020-07-28 08:50:41 +10:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
// extract primary contact (email), without scheme (e.g. "mailto:")
|
|
|
|
primaryContact := getPrimaryContact(account)
|
|
|
|
all := []keyValue{
|
|
|
|
{
|
|
|
|
key: am.storageKeyUserReg(ca, primaryContact),
|
|
|
|
value: regBytes,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
key: am.storageKeyUserPrivateKey(ca, primaryContact),
|
|
|
|
value: keyBytes,
|
|
|
|
},
|
|
|
|
}
|
2022-03-08 05:26:52 +10:00
|
|
|
return storeTx(ctx, am.config.Storage, all)
|
2018-12-10 13:15:26 +10:00
|
|
|
}
|
|
|
|
|
2024-03-15 07:35:35 +10:00
|
|
|
// deleteAccountLocally deletes the registration info and private key of the account
|
|
|
|
// for the given CA from storage.
|
|
|
|
func (am *ACMEIssuer) deleteAccountLocally(ctx context.Context, ca string, account acme.Account) error {
|
|
|
|
primaryContact := getPrimaryContact(account)
|
|
|
|
if err := am.config.Storage.Delete(ctx, am.storageKeyUserReg(ca, primaryContact)); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
return am.config.Storage.Delete(ctx, am.storageKeyUserPrivateKey(ca, primaryContact))
|
|
|
|
}
|
|
|
|
|
2022-07-06 08:56:22 +10:00
|
|
|
// setEmail does everything it can to obtain an email address
|
2018-12-10 13:15:26 +10:00
|
|
|
// from the user within the scope of memory and storage to use
|
2019-01-22 04:31:57 +10:00
|
|
|
// for ACME TLS. If it cannot get an email address, it does nothing
|
|
|
|
// (If user is prompted, it will warn the user of
|
2018-12-10 13:15:26 +10:00
|
|
|
// the consequences of an empty email.) This function MAY prompt
|
2019-01-22 04:31:57 +10:00
|
|
|
// the user for input. If allowPrompts is false, the user
|
2018-12-10 13:15:26 +10:00
|
|
|
// will NOT be prompted and an empty email may be returned.
|
2022-07-06 08:56:22 +10:00
|
|
|
func (am *ACMEIssuer) setEmail(ctx context.Context, allowPrompts bool) error {
|
Major refactor to improve performance, correctness, and extensibility
Breaking changes; thank goodness we're not 1.0 yet 😅 - read on!
This change completely separates ACME-specific code from the rest of the
certificate management process, allowing pluggable sources for certs
that aren't ACME.
Notably, most of Config was spliced into ACMEManager. Similarly, there's
now Default and DefaultACME.
Storage structure had to be reconfigured. Certificates are no longer in
the acme/ subfolder since they can be obtained by ways other than ACME!
Certificates moved to a new certificates/ subfolder. The subfolders in
that folder use the path of the ACME endpoint instead of just the host,
so that also changed. Be aware that unless you move your certs over,
CertMagic will not find them and will attempt to get new ones. That is
usually fine for most users, but for extremely large deployments, you
will want to move them over first.
Old certs path:
acme/acme-staging-v02.api.letsencrypt.org/...
New certs path:
certificates/acme-staging-v02.api.letsencrypt.org-directory/...
That's all for significant storage changes!
But this refactor also vastly improves performance, especially at scale,
and makes CertMagic way more resilient to errors. Retries are done on
the staging endpoint by default, so they won't count against your rate
limit. If your hardware can handle it, I'm now pretty confident that you
can give CertMagic a million domain names and it will gracefully manage
them, as fast as it can within internal and external rate limits, even
in the presence of errors. Errors will of course slow some things down,
but you should be good to go if you're monitoring logs and can fix any
misconfigurations or other external errors!
Several other mostly-minor enhancements fix bugs, especially at scale.
For example, duplicated renewal tasks (that continuously fail) will not
pile up on each other: only one will operate, under exponential backoff.
Closes #50 and fixes #55
2020-02-22 07:32:57 +10:00
|
|
|
leEmail := am.Email
|
2019-04-30 00:35:53 +10:00
|
|
|
|
2021-04-13 03:57:11 +10:00
|
|
|
// First try package default email, or a discovered email address
|
2018-12-11 02:59:03 +10:00
|
|
|
if leEmail == "" {
|
2021-04-13 03:57:11 +10:00
|
|
|
leEmail = DefaultACME.Email
|
|
|
|
}
|
|
|
|
if leEmail == "" {
|
|
|
|
discoveredEmailMu.Lock()
|
|
|
|
leEmail = discoveredEmail
|
|
|
|
discoveredEmailMu.Unlock()
|
2018-12-11 02:59:03 +10:00
|
|
|
}
|
2019-04-30 00:35:53 +10:00
|
|
|
|
2018-12-10 13:15:26 +10:00
|
|
|
// Then try to get most recent user email from storage
|
2019-02-25 16:09:13 +10:00
|
|
|
var gotRecentEmail bool
|
2018-12-10 13:15:26 +10:00
|
|
|
if leEmail == "" {
|
2022-03-08 05:26:52 +10:00
|
|
|
leEmail, gotRecentEmail = am.mostRecentAccountEmail(ctx, am.CA)
|
2018-12-10 13:15:26 +10:00
|
|
|
}
|
2019-02-25 16:09:13 +10:00
|
|
|
if !gotRecentEmail && leEmail == "" && allowPrompts {
|
2019-01-22 04:31:57 +10:00
|
|
|
// Looks like there is no email address readily available,
|
|
|
|
// so we will have to ask the user if we can.
|
|
|
|
var err error
|
Major refactor to improve performance, correctness, and extensibility
Breaking changes; thank goodness we're not 1.0 yet 😅 - read on!
This change completely separates ACME-specific code from the rest of the
certificate management process, allowing pluggable sources for certs
that aren't ACME.
Notably, most of Config was spliced into ACMEManager. Similarly, there's
now Default and DefaultACME.
Storage structure had to be reconfigured. Certificates are no longer in
the acme/ subfolder since they can be obtained by ways other than ACME!
Certificates moved to a new certificates/ subfolder. The subfolders in
that folder use the path of the ACME endpoint instead of just the host,
so that also changed. Be aware that unless you move your certs over,
CertMagic will not find them and will attempt to get new ones. That is
usually fine for most users, but for extremely large deployments, you
will want to move them over first.
Old certs path:
acme/acme-staging-v02.api.letsencrypt.org/...
New certs path:
certificates/acme-staging-v02.api.letsencrypt.org-directory/...
That's all for significant storage changes!
But this refactor also vastly improves performance, especially at scale,
and makes CertMagic way more resilient to errors. Retries are done on
the staging endpoint by default, so they won't count against your rate
limit. If your hardware can handle it, I'm now pretty confident that you
can give CertMagic a million domain names and it will gracefully manage
them, as fast as it can within internal and external rate limits, even
in the presence of errors. Errors will of course slow some things down,
but you should be good to go if you're monitoring logs and can fix any
misconfigurations or other external errors!
Several other mostly-minor enhancements fix bugs, especially at scale.
For example, duplicated renewal tasks (that continuously fail) will not
pile up on each other: only one will operate, under exponential backoff.
Closes #50 and fixes #55
2020-02-22 07:32:57 +10:00
|
|
|
leEmail, err = am.promptUserForEmail()
|
2018-12-10 13:15:26 +10:00
|
|
|
if err != nil {
|
2019-01-22 04:31:57 +10:00
|
|
|
return err
|
2018-12-10 13:15:26 +10:00
|
|
|
}
|
2019-04-30 00:35:53 +10:00
|
|
|
|
|
|
|
// User might have just signified their agreement
|
2022-08-02 15:04:11 +10:00
|
|
|
am.mu.Lock()
|
|
|
|
am.agreed = DefaultACME.Agreed
|
|
|
|
am.mu.Unlock()
|
2019-01-22 04:31:57 +10:00
|
|
|
}
|
2019-02-25 16:09:13 +10:00
|
|
|
|
2022-08-02 15:04:11 +10:00
|
|
|
// Save the email for later and ensure it is consistent
|
2019-04-30 00:35:53 +10:00
|
|
|
// for repeated use; then update cfg with the email
|
2021-04-13 03:57:11 +10:00
|
|
|
leEmail = strings.TrimSpace(strings.ToLower(leEmail))
|
|
|
|
discoveredEmailMu.Lock()
|
|
|
|
if discoveredEmail == "" {
|
|
|
|
discoveredEmail = leEmail
|
|
|
|
}
|
|
|
|
discoveredEmailMu.Unlock()
|
2022-08-02 15:04:11 +10:00
|
|
|
|
|
|
|
// The unexported email field is the one we use
|
|
|
|
// because we have thread-safe control over it
|
|
|
|
am.mu.Lock()
|
|
|
|
am.email = leEmail
|
|
|
|
am.mu.Unlock()
|
2019-02-25 16:09:13 +10:00
|
|
|
|
2019-01-22 04:31:57 +10:00
|
|
|
return nil
|
|
|
|
}
|
2018-12-10 13:15:26 +10:00
|
|
|
|
2019-02-25 16:09:13 +10:00
|
|
|
// promptUserForEmail prompts the user for an email address
|
|
|
|
// and returns the email address they entered (which could
|
|
|
|
// be the empty string). If no error is returned, then Agreed
|
|
|
|
// will also be set to true, since continuing through the
|
|
|
|
// prompt signifies agreement.
|
2022-03-25 03:34:31 +10:00
|
|
|
func (am *ACMEIssuer) promptUserForEmail() (string, error) {
|
2019-01-22 04:31:57 +10:00
|
|
|
// prompt the user for an email address and terms agreement
|
|
|
|
reader := bufio.NewReader(stdin)
|
2020-07-28 08:50:41 +10:00
|
|
|
am.promptUserAgreement("")
|
2019-01-22 04:31:57 +10:00
|
|
|
fmt.Println("Please enter your email address to signify agreement and to be notified")
|
|
|
|
fmt.Println("in case of issues. You can leave it blank, but we don't recommend it.")
|
|
|
|
fmt.Print(" Email address: ")
|
|
|
|
leEmail, err := reader.ReadString('\n')
|
|
|
|
if err != nil && err != io.EOF {
|
|
|
|
return "", fmt.Errorf("reading email address: %v", err)
|
|
|
|
}
|
|
|
|
leEmail = strings.TrimSpace(leEmail)
|
Major refactor to improve performance, correctness, and extensibility
Breaking changes; thank goodness we're not 1.0 yet 😅 - read on!
This change completely separates ACME-specific code from the rest of the
certificate management process, allowing pluggable sources for certs
that aren't ACME.
Notably, most of Config was spliced into ACMEManager. Similarly, there's
now Default and DefaultACME.
Storage structure had to be reconfigured. Certificates are no longer in
the acme/ subfolder since they can be obtained by ways other than ACME!
Certificates moved to a new certificates/ subfolder. The subfolders in
that folder use the path of the ACME endpoint instead of just the host,
so that also changed. Be aware that unless you move your certs over,
CertMagic will not find them and will attempt to get new ones. That is
usually fine for most users, but for extremely large deployments, you
will want to move them over first.
Old certs path:
acme/acme-staging-v02.api.letsencrypt.org/...
New certs path:
certificates/acme-staging-v02.api.letsencrypt.org-directory/...
That's all for significant storage changes!
But this refactor also vastly improves performance, especially at scale,
and makes CertMagic way more resilient to errors. Retries are done on
the staging endpoint by default, so they won't count against your rate
limit. If your hardware can handle it, I'm now pretty confident that you
can give CertMagic a million domain names and it will gracefully manage
them, as fast as it can within internal and external rate limits, even
in the presence of errors. Errors will of course slow some things down,
but you should be good to go if you're monitoring logs and can fix any
misconfigurations or other external errors!
Several other mostly-minor enhancements fix bugs, especially at scale.
For example, duplicated renewal tasks (that continuously fail) will not
pile up on each other: only one will operate, under exponential backoff.
Closes #50 and fixes #55
2020-02-22 07:32:57 +10:00
|
|
|
DefaultACME.Agreed = true
|
2019-01-22 04:31:57 +10:00
|
|
|
return leEmail, nil
|
2018-12-10 13:15:26 +10:00
|
|
|
}
|
|
|
|
|
|
|
|
// promptUserAgreement simply outputs the standard user
|
|
|
|
// agreement prompt with the given agreement URL.
|
|
|
|
// It outputs a newline after the message.
|
2022-03-25 03:34:31 +10:00
|
|
|
func (am *ACMEIssuer) promptUserAgreement(agreementURL string) {
|
2020-07-28 08:50:41 +10:00
|
|
|
userAgreementPrompt := `Your sites will be served over HTTPS automatically using an automated CA.
|
|
|
|
By continuing, you agree to the CA's terms of service`
|
|
|
|
if agreementURL == "" {
|
|
|
|
fmt.Printf("\n\n%s.\n", userAgreementPrompt)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
fmt.Printf("\n\n%s at:\n %s\n", userAgreementPrompt, agreementURL)
|
2018-12-10 13:15:26 +10:00
|
|
|
}
|
|
|
|
|
|
|
|
// askUserAgreement prompts the user to agree to the agreement
|
|
|
|
// at the given agreement URL via stdin. It returns whether the
|
|
|
|
// user agreed or not.
|
2022-03-25 03:34:31 +10:00
|
|
|
func (am *ACMEIssuer) askUserAgreement(agreementURL string) bool {
|
Major refactor to improve performance, correctness, and extensibility
Breaking changes; thank goodness we're not 1.0 yet 😅 - read on!
This change completely separates ACME-specific code from the rest of the
certificate management process, allowing pluggable sources for certs
that aren't ACME.
Notably, most of Config was spliced into ACMEManager. Similarly, there's
now Default and DefaultACME.
Storage structure had to be reconfigured. Certificates are no longer in
the acme/ subfolder since they can be obtained by ways other than ACME!
Certificates moved to a new certificates/ subfolder. The subfolders in
that folder use the path of the ACME endpoint instead of just the host,
so that also changed. Be aware that unless you move your certs over,
CertMagic will not find them and will attempt to get new ones. That is
usually fine for most users, but for extremely large deployments, you
will want to move them over first.
Old certs path:
acme/acme-staging-v02.api.letsencrypt.org/...
New certs path:
certificates/acme-staging-v02.api.letsencrypt.org-directory/...
That's all for significant storage changes!
But this refactor also vastly improves performance, especially at scale,
and makes CertMagic way more resilient to errors. Retries are done on
the staging endpoint by default, so they won't count against your rate
limit. If your hardware can handle it, I'm now pretty confident that you
can give CertMagic a million domain names and it will gracefully manage
them, as fast as it can within internal and external rate limits, even
in the presence of errors. Errors will of course slow some things down,
but you should be good to go if you're monitoring logs and can fix any
misconfigurations or other external errors!
Several other mostly-minor enhancements fix bugs, especially at scale.
For example, duplicated renewal tasks (that continuously fail) will not
pile up on each other: only one will operate, under exponential backoff.
Closes #50 and fixes #55
2020-02-22 07:32:57 +10:00
|
|
|
am.promptUserAgreement(agreementURL)
|
2018-12-10 13:15:26 +10:00
|
|
|
fmt.Print("Do you agree to the terms? (y/n): ")
|
|
|
|
|
|
|
|
reader := bufio.NewReader(stdin)
|
|
|
|
answer, err := reader.ReadString('\n')
|
|
|
|
if err != nil {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
answer = strings.ToLower(strings.TrimSpace(answer))
|
|
|
|
|
|
|
|
return answer == "y" || answer == "yes"
|
|
|
|
}
|
|
|
|
|
2020-11-17 03:53:41 +10:00
|
|
|
func storageKeyACMECAPrefix(issuerKey string) string {
|
|
|
|
return path.Join(prefixACME, StorageKeys.Safe(issuerKey))
|
|
|
|
}
|
|
|
|
|
2022-03-25 03:34:31 +10:00
|
|
|
func (am *ACMEIssuer) storageKeyCAPrefix(caURL string) string {
|
2020-11-17 03:53:41 +10:00
|
|
|
return storageKeyACMECAPrefix(am.issuerKey(caURL))
|
Major refactor to improve performance, correctness, and extensibility
Breaking changes; thank goodness we're not 1.0 yet 😅 - read on!
This change completely separates ACME-specific code from the rest of the
certificate management process, allowing pluggable sources for certs
that aren't ACME.
Notably, most of Config was spliced into ACMEManager. Similarly, there's
now Default and DefaultACME.
Storage structure had to be reconfigured. Certificates are no longer in
the acme/ subfolder since they can be obtained by ways other than ACME!
Certificates moved to a new certificates/ subfolder. The subfolders in
that folder use the path of the ACME endpoint instead of just the host,
so that also changed. Be aware that unless you move your certs over,
CertMagic will not find them and will attempt to get new ones. That is
usually fine for most users, but for extremely large deployments, you
will want to move them over first.
Old certs path:
acme/acme-staging-v02.api.letsencrypt.org/...
New certs path:
certificates/acme-staging-v02.api.letsencrypt.org-directory/...
That's all for significant storage changes!
But this refactor also vastly improves performance, especially at scale,
and makes CertMagic way more resilient to errors. Retries are done on
the staging endpoint by default, so they won't count against your rate
limit. If your hardware can handle it, I'm now pretty confident that you
can give CertMagic a million domain names and it will gracefully manage
them, as fast as it can within internal and external rate limits, even
in the presence of errors. Errors will of course slow some things down,
but you should be good to go if you're monitoring logs and can fix any
misconfigurations or other external errors!
Several other mostly-minor enhancements fix bugs, especially at scale.
For example, duplicated renewal tasks (that continuously fail) will not
pile up on each other: only one will operate, under exponential backoff.
Closes #50 and fixes #55
2020-02-22 07:32:57 +10:00
|
|
|
}
|
|
|
|
|
2022-03-25 03:34:31 +10:00
|
|
|
func (am *ACMEIssuer) storageKeyUsersPrefix(caURL string) string {
|
Major refactor to improve performance, correctness, and extensibility
Breaking changes; thank goodness we're not 1.0 yet 😅 - read on!
This change completely separates ACME-specific code from the rest of the
certificate management process, allowing pluggable sources for certs
that aren't ACME.
Notably, most of Config was spliced into ACMEManager. Similarly, there's
now Default and DefaultACME.
Storage structure had to be reconfigured. Certificates are no longer in
the acme/ subfolder since they can be obtained by ways other than ACME!
Certificates moved to a new certificates/ subfolder. The subfolders in
that folder use the path of the ACME endpoint instead of just the host,
so that also changed. Be aware that unless you move your certs over,
CertMagic will not find them and will attempt to get new ones. That is
usually fine for most users, but for extremely large deployments, you
will want to move them over first.
Old certs path:
acme/acme-staging-v02.api.letsencrypt.org/...
New certs path:
certificates/acme-staging-v02.api.letsencrypt.org-directory/...
That's all for significant storage changes!
But this refactor also vastly improves performance, especially at scale,
and makes CertMagic way more resilient to errors. Retries are done on
the staging endpoint by default, so they won't count against your rate
limit. If your hardware can handle it, I'm now pretty confident that you
can give CertMagic a million domain names and it will gracefully manage
them, as fast as it can within internal and external rate limits, even
in the presence of errors. Errors will of course slow some things down,
but you should be good to go if you're monitoring logs and can fix any
misconfigurations or other external errors!
Several other mostly-minor enhancements fix bugs, especially at scale.
For example, duplicated renewal tasks (that continuously fail) will not
pile up on each other: only one will operate, under exponential backoff.
Closes #50 and fixes #55
2020-02-22 07:32:57 +10:00
|
|
|
return path.Join(am.storageKeyCAPrefix(caURL), "users")
|
|
|
|
}
|
|
|
|
|
2022-03-25 03:34:31 +10:00
|
|
|
func (am *ACMEIssuer) storageKeyUserPrefix(caURL, email string) string {
|
Major refactor to improve performance, correctness, and extensibility
Breaking changes; thank goodness we're not 1.0 yet 😅 - read on!
This change completely separates ACME-specific code from the rest of the
certificate management process, allowing pluggable sources for certs
that aren't ACME.
Notably, most of Config was spliced into ACMEManager. Similarly, there's
now Default and DefaultACME.
Storage structure had to be reconfigured. Certificates are no longer in
the acme/ subfolder since they can be obtained by ways other than ACME!
Certificates moved to a new certificates/ subfolder. The subfolders in
that folder use the path of the ACME endpoint instead of just the host,
so that also changed. Be aware that unless you move your certs over,
CertMagic will not find them and will attempt to get new ones. That is
usually fine for most users, but for extremely large deployments, you
will want to move them over first.
Old certs path:
acme/acme-staging-v02.api.letsencrypt.org/...
New certs path:
certificates/acme-staging-v02.api.letsencrypt.org-directory/...
That's all for significant storage changes!
But this refactor also vastly improves performance, especially at scale,
and makes CertMagic way more resilient to errors. Retries are done on
the staging endpoint by default, so they won't count against your rate
limit. If your hardware can handle it, I'm now pretty confident that you
can give CertMagic a million domain names and it will gracefully manage
them, as fast as it can within internal and external rate limits, even
in the presence of errors. Errors will of course slow some things down,
but you should be good to go if you're monitoring logs and can fix any
misconfigurations or other external errors!
Several other mostly-minor enhancements fix bugs, especially at scale.
For example, duplicated renewal tasks (that continuously fail) will not
pile up on each other: only one will operate, under exponential backoff.
Closes #50 and fixes #55
2020-02-22 07:32:57 +10:00
|
|
|
if email == "" {
|
|
|
|
email = emptyEmail
|
|
|
|
}
|
|
|
|
return path.Join(am.storageKeyUsersPrefix(caURL), StorageKeys.Safe(email))
|
|
|
|
}
|
|
|
|
|
2022-03-25 03:34:31 +10:00
|
|
|
func (am *ACMEIssuer) storageKeyUserReg(caURL, email string) string {
|
Major refactor to improve performance, correctness, and extensibility
Breaking changes; thank goodness we're not 1.0 yet 😅 - read on!
This change completely separates ACME-specific code from the rest of the
certificate management process, allowing pluggable sources for certs
that aren't ACME.
Notably, most of Config was spliced into ACMEManager. Similarly, there's
now Default and DefaultACME.
Storage structure had to be reconfigured. Certificates are no longer in
the acme/ subfolder since they can be obtained by ways other than ACME!
Certificates moved to a new certificates/ subfolder. The subfolders in
that folder use the path of the ACME endpoint instead of just the host,
so that also changed. Be aware that unless you move your certs over,
CertMagic will not find them and will attempt to get new ones. That is
usually fine for most users, but for extremely large deployments, you
will want to move them over first.
Old certs path:
acme/acme-staging-v02.api.letsencrypt.org/...
New certs path:
certificates/acme-staging-v02.api.letsencrypt.org-directory/...
That's all for significant storage changes!
But this refactor also vastly improves performance, especially at scale,
and makes CertMagic way more resilient to errors. Retries are done on
the staging endpoint by default, so they won't count against your rate
limit. If your hardware can handle it, I'm now pretty confident that you
can give CertMagic a million domain names and it will gracefully manage
them, as fast as it can within internal and external rate limits, even
in the presence of errors. Errors will of course slow some things down,
but you should be good to go if you're monitoring logs and can fix any
misconfigurations or other external errors!
Several other mostly-minor enhancements fix bugs, especially at scale.
For example, duplicated renewal tasks (that continuously fail) will not
pile up on each other: only one will operate, under exponential backoff.
Closes #50 and fixes #55
2020-02-22 07:32:57 +10:00
|
|
|
return am.storageSafeUserKey(caURL, email, "registration", ".json")
|
|
|
|
}
|
|
|
|
|
2022-03-25 03:34:31 +10:00
|
|
|
func (am *ACMEIssuer) storageKeyUserPrivateKey(caURL, email string) string {
|
Major refactor to improve performance, correctness, and extensibility
Breaking changes; thank goodness we're not 1.0 yet 😅 - read on!
This change completely separates ACME-specific code from the rest of the
certificate management process, allowing pluggable sources for certs
that aren't ACME.
Notably, most of Config was spliced into ACMEManager. Similarly, there's
now Default and DefaultACME.
Storage structure had to be reconfigured. Certificates are no longer in
the acme/ subfolder since they can be obtained by ways other than ACME!
Certificates moved to a new certificates/ subfolder. The subfolders in
that folder use the path of the ACME endpoint instead of just the host,
so that also changed. Be aware that unless you move your certs over,
CertMagic will not find them and will attempt to get new ones. That is
usually fine for most users, but for extremely large deployments, you
will want to move them over first.
Old certs path:
acme/acme-staging-v02.api.letsencrypt.org/...
New certs path:
certificates/acme-staging-v02.api.letsencrypt.org-directory/...
That's all for significant storage changes!
But this refactor also vastly improves performance, especially at scale,
and makes CertMagic way more resilient to errors. Retries are done on
the staging endpoint by default, so they won't count against your rate
limit. If your hardware can handle it, I'm now pretty confident that you
can give CertMagic a million domain names and it will gracefully manage
them, as fast as it can within internal and external rate limits, even
in the presence of errors. Errors will of course slow some things down,
but you should be good to go if you're monitoring logs and can fix any
misconfigurations or other external errors!
Several other mostly-minor enhancements fix bugs, especially at scale.
For example, duplicated renewal tasks (that continuously fail) will not
pile up on each other: only one will operate, under exponential backoff.
Closes #50 and fixes #55
2020-02-22 07:32:57 +10:00
|
|
|
return am.storageSafeUserKey(caURL, email, "private", ".key")
|
|
|
|
}
|
|
|
|
|
|
|
|
// storageSafeUserKey returns a key for the given email, with the default
|
|
|
|
// filename, and the filename ending in the given extension.
|
2022-03-25 03:34:31 +10:00
|
|
|
func (am *ACMEIssuer) storageSafeUserKey(ca, email, defaultFilename, extension string) string {
|
Major refactor to improve performance, correctness, and extensibility
Breaking changes; thank goodness we're not 1.0 yet 😅 - read on!
This change completely separates ACME-specific code from the rest of the
certificate management process, allowing pluggable sources for certs
that aren't ACME.
Notably, most of Config was spliced into ACMEManager. Similarly, there's
now Default and DefaultACME.
Storage structure had to be reconfigured. Certificates are no longer in
the acme/ subfolder since they can be obtained by ways other than ACME!
Certificates moved to a new certificates/ subfolder. The subfolders in
that folder use the path of the ACME endpoint instead of just the host,
so that also changed. Be aware that unless you move your certs over,
CertMagic will not find them and will attempt to get new ones. That is
usually fine for most users, but for extremely large deployments, you
will want to move them over first.
Old certs path:
acme/acme-staging-v02.api.letsencrypt.org/...
New certs path:
certificates/acme-staging-v02.api.letsencrypt.org-directory/...
That's all for significant storage changes!
But this refactor also vastly improves performance, especially at scale,
and makes CertMagic way more resilient to errors. Retries are done on
the staging endpoint by default, so they won't count against your rate
limit. If your hardware can handle it, I'm now pretty confident that you
can give CertMagic a million domain names and it will gracefully manage
them, as fast as it can within internal and external rate limits, even
in the presence of errors. Errors will of course slow some things down,
but you should be good to go if you're monitoring logs and can fix any
misconfigurations or other external errors!
Several other mostly-minor enhancements fix bugs, especially at scale.
For example, duplicated renewal tasks (that continuously fail) will not
pile up on each other: only one will operate, under exponential backoff.
Closes #50 and fixes #55
2020-02-22 07:32:57 +10:00
|
|
|
if email == "" {
|
|
|
|
email = emptyEmail
|
|
|
|
}
|
|
|
|
email = strings.ToLower(email)
|
|
|
|
filename := am.emailUsername(email)
|
|
|
|
if filename == "" {
|
|
|
|
filename = defaultFilename
|
|
|
|
}
|
|
|
|
filename = StorageKeys.Safe(filename)
|
|
|
|
return path.Join(am.storageKeyUserPrefix(ca, email), filename+extension)
|
|
|
|
}
|
|
|
|
|
|
|
|
// emailUsername returns the username portion of an email address (part before
|
|
|
|
// '@') or the original input if it can't find the "@" symbol.
|
2022-03-25 03:34:31 +10:00
|
|
|
func (*ACMEIssuer) emailUsername(email string) string {
|
Major refactor to improve performance, correctness, and extensibility
Breaking changes; thank goodness we're not 1.0 yet 😅 - read on!
This change completely separates ACME-specific code from the rest of the
certificate management process, allowing pluggable sources for certs
that aren't ACME.
Notably, most of Config was spliced into ACMEManager. Similarly, there's
now Default and DefaultACME.
Storage structure had to be reconfigured. Certificates are no longer in
the acme/ subfolder since they can be obtained by ways other than ACME!
Certificates moved to a new certificates/ subfolder. The subfolders in
that folder use the path of the ACME endpoint instead of just the host,
so that also changed. Be aware that unless you move your certs over,
CertMagic will not find them and will attempt to get new ones. That is
usually fine for most users, but for extremely large deployments, you
will want to move them over first.
Old certs path:
acme/acme-staging-v02.api.letsencrypt.org/...
New certs path:
certificates/acme-staging-v02.api.letsencrypt.org-directory/...
That's all for significant storage changes!
But this refactor also vastly improves performance, especially at scale,
and makes CertMagic way more resilient to errors. Retries are done on
the staging endpoint by default, so they won't count against your rate
limit. If your hardware can handle it, I'm now pretty confident that you
can give CertMagic a million domain names and it will gracefully manage
them, as fast as it can within internal and external rate limits, even
in the presence of errors. Errors will of course slow some things down,
but you should be good to go if you're monitoring logs and can fix any
misconfigurations or other external errors!
Several other mostly-minor enhancements fix bugs, especially at scale.
For example, duplicated renewal tasks (that continuously fail) will not
pile up on each other: only one will operate, under exponential backoff.
Closes #50 and fixes #55
2020-02-22 07:32:57 +10:00
|
|
|
at := strings.Index(email, "@")
|
|
|
|
if at == -1 {
|
|
|
|
return email
|
|
|
|
} else if at == 0 {
|
|
|
|
return email[1:]
|
|
|
|
}
|
|
|
|
return email[:at]
|
|
|
|
}
|
|
|
|
|
2020-07-28 08:50:41 +10:00
|
|
|
// mostRecentAccountEmail finds the most recently-written account file
|
Major refactor to improve performance, correctness, and extensibility
Breaking changes; thank goodness we're not 1.0 yet 😅 - read on!
This change completely separates ACME-specific code from the rest of the
certificate management process, allowing pluggable sources for certs
that aren't ACME.
Notably, most of Config was spliced into ACMEManager. Similarly, there's
now Default and DefaultACME.
Storage structure had to be reconfigured. Certificates are no longer in
the acme/ subfolder since they can be obtained by ways other than ACME!
Certificates moved to a new certificates/ subfolder. The subfolders in
that folder use the path of the ACME endpoint instead of just the host,
so that also changed. Be aware that unless you move your certs over,
CertMagic will not find them and will attempt to get new ones. That is
usually fine for most users, but for extremely large deployments, you
will want to move them over first.
Old certs path:
acme/acme-staging-v02.api.letsencrypt.org/...
New certs path:
certificates/acme-staging-v02.api.letsencrypt.org-directory/...
That's all for significant storage changes!
But this refactor also vastly improves performance, especially at scale,
and makes CertMagic way more resilient to errors. Retries are done on
the staging endpoint by default, so they won't count against your rate
limit. If your hardware can handle it, I'm now pretty confident that you
can give CertMagic a million domain names and it will gracefully manage
them, as fast as it can within internal and external rate limits, even
in the presence of errors. Errors will of course slow some things down,
but you should be good to go if you're monitoring logs and can fix any
misconfigurations or other external errors!
Several other mostly-minor enhancements fix bugs, especially at scale.
For example, duplicated renewal tasks (that continuously fail) will not
pile up on each other: only one will operate, under exponential backoff.
Closes #50 and fixes #55
2020-02-22 07:32:57 +10:00
|
|
|
// in storage. Since this is part of a complex sequence to get a user
|
2018-12-10 13:15:26 +10:00
|
|
|
// account, errors here are discarded to simplify code flow in
|
|
|
|
// the caller, and errors are not important here anyway.
|
2022-03-25 03:34:31 +10:00
|
|
|
func (am *ACMEIssuer) mostRecentAccountEmail(ctx context.Context, caURL string) (string, bool) {
|
2022-03-08 05:26:52 +10:00
|
|
|
accountList, err := am.config.Storage.List(ctx, am.storageKeyUsersPrefix(caURL), false)
|
2020-07-28 08:50:41 +10:00
|
|
|
if err != nil || len(accountList) == 0 {
|
2019-02-25 16:09:13 +10:00
|
|
|
return "", false
|
2018-12-10 13:15:26 +10:00
|
|
|
}
|
2020-03-27 03:03:28 +10:00
|
|
|
|
|
|
|
// get all the key infos ahead of sorting, because
|
|
|
|
// we might filter some out
|
|
|
|
stats := make(map[string]KeyInfo)
|
2020-12-29 02:05:11 +10:00
|
|
|
for i := 0; i < len(accountList); i++ {
|
|
|
|
u := accountList[i]
|
2022-03-08 05:26:52 +10:00
|
|
|
keyInfo, err := am.config.Storage.Stat(ctx, u)
|
2020-03-27 03:03:28 +10:00
|
|
|
if err != nil {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
if keyInfo.IsTerminal {
|
|
|
|
// I found a bug when macOS created a .DS_Store file in
|
|
|
|
// the users folder, and CertMagic tried to use that as
|
|
|
|
// the user email because it was newer than the other one
|
|
|
|
// which existed... sure, this isn't a perfect fix but
|
|
|
|
// frankly one's OS shouldn't mess with the data folder
|
|
|
|
// in the first place.
|
2020-07-28 08:50:41 +10:00
|
|
|
accountList = append(accountList[:i], accountList[i+1:]...)
|
2020-12-29 02:05:11 +10:00
|
|
|
i--
|
2020-03-27 03:03:28 +10:00
|
|
|
continue
|
|
|
|
}
|
|
|
|
stats[u] = keyInfo
|
|
|
|
}
|
|
|
|
|
2020-07-28 08:50:41 +10:00
|
|
|
sort.Slice(accountList, func(i, j int) bool {
|
|
|
|
iInfo := stats[accountList[i]]
|
|
|
|
jInfo := stats[accountList[j]]
|
2018-12-11 02:59:03 +10:00
|
|
|
return jInfo.Modified.Before(iInfo.Modified)
|
2018-12-10 13:15:26 +10:00
|
|
|
})
|
2020-03-27 03:03:28 +10:00
|
|
|
|
2020-07-28 08:50:41 +10:00
|
|
|
if len(accountList) == 0 {
|
|
|
|
return "", false
|
|
|
|
}
|
|
|
|
|
2022-03-08 05:26:52 +10:00
|
|
|
account, err := am.getAccount(ctx, caURL, path.Base(accountList[0]))
|
2018-12-10 13:15:26 +10:00
|
|
|
if err != nil {
|
2019-02-25 16:09:13 +10:00
|
|
|
return "", false
|
2018-12-10 13:15:26 +10:00
|
|
|
}
|
2020-03-27 03:03:28 +10:00
|
|
|
|
2020-07-28 08:50:41 +10:00
|
|
|
return getPrimaryContact(account), true
|
|
|
|
}
|
|
|
|
|
2024-06-06 21:17:18 +10:00
|
|
|
func accountRegLockKey(acc acme.Account) string {
|
|
|
|
key := "register_acme_account"
|
|
|
|
if len(acc.Contact) == 0 {
|
|
|
|
return key
|
|
|
|
}
|
|
|
|
key += "_" + getPrimaryContact(acc)
|
|
|
|
return key
|
|
|
|
}
|
|
|
|
|
2020-07-28 08:50:41 +10:00
|
|
|
// getPrimaryContact returns the first contact on the account (if any)
|
|
|
|
// without the scheme. (I guess we assume an email address.)
|
|
|
|
func getPrimaryContact(account acme.Account) string {
|
|
|
|
// TODO: should this be abstracted with some lower-level helper?
|
|
|
|
var primaryContact string
|
|
|
|
if len(account.Contact) > 0 {
|
|
|
|
primaryContact = account.Contact[0]
|
|
|
|
if idx := strings.Index(primaryContact, ":"); idx >= 0 {
|
|
|
|
primaryContact = primaryContact[idx+1:]
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return primaryContact
|
2018-12-10 13:15:26 +10:00
|
|
|
}
|
|
|
|
|
2021-04-13 03:57:11 +10:00
|
|
|
// When an email address is not explicitly specified, we can remember
|
|
|
|
// the last one we discovered to avoid having to ask again later.
|
|
|
|
// (We used to store this in DefaultACME.Email but it was racey; see #127)
|
|
|
|
var (
|
|
|
|
discoveredEmail string
|
|
|
|
discoveredEmailMu sync.Mutex
|
|
|
|
)
|
|
|
|
|
2018-12-10 13:15:26 +10:00
|
|
|
// stdin is used to read the user's input if prompted;
|
|
|
|
// this is changed by tests during tests.
|
|
|
|
var stdin = io.ReadWriter(os.Stdin)
|
|
|
|
|
|
|
|
// The name of the folder for accounts where the email
|
|
|
|
// address was not provided; default 'username' if you will,
|
|
|
|
// but only for local/storage use, not with the CA.
|
|
|
|
const emptyEmail = "default"
|