Commit 598de9e6 authored by Matthew Holt's avatar Matthew Holt

vendor: Update certmagic

parent 393bc299
...@@ -16,12 +16,12 @@ ...@@ -16,12 +16,12 @@
// including TLS & HTTPS best practices such as robust OCSP stapling, caching, // including TLS & HTTPS best practices such as robust OCSP stapling, caching,
// HTTP->HTTPS redirects, and more. // HTTP->HTTPS redirects, and more.
// //
// Its high-level API serves your HTTP handlers over HTTPS by simply giving // Its high-level API serves your HTTP handlers over HTTPS if you simply give
// the domain name(s) and the http.Handler; CertMagic will create and run // the domain name(s) and the http.Handler; CertMagic will create and run
// the HTTPS server for you, fully managing certificates during the lifetime // the HTTPS server for you, fully managing certificates during the lifetime
// of the server. Similarly, it can be used to start TLS listeners or return // of the server. Similarly, it can be used to start TLS listeners or return
// a ready-to-use tls.Config -- whatever layer you need TLS for, CertMagic // a ready-to-use tls.Config -- whatever layer you need TLS for, CertMagic
// makes it easy. // makes it easy. See the HTTPS, Listen, and TLS functions for that.
// //
// If you need more control, create a Config using New() and then call // If you need more control, create a Config using New() and then call
// Manage() on the config; but you'll have to be sure to solve the HTTP // Manage() on the config; but you'll have to be sure to solve the HTTP
...@@ -475,10 +475,14 @@ const ( ...@@ -475,10 +475,14 @@ const (
// forward packets from the defaults to whatever these // forward packets from the defaults to whatever these
// are set to; otherwise ACME challenges will fail. // are set to; otherwise ACME challenges will fail.
var ( var (
// HTTPPort is the port on which to serve HTTP. // HTTPPort is the port on which to serve HTTP
// and, by extension, the HTTP challenge (unless
// AltHTTPPort is set).
HTTPPort = 80 HTTPPort = 80
// HTTPSPort is the port on which to serve HTTPS. // HTTPSPort is the port on which to serve HTTPS
// and, by extension, the TLS-ALPN challenge
// (unless AltTLSALPNPort is set).
HTTPSPort = 443 HTTPSPort = 443
) )
......
...@@ -142,6 +142,12 @@ func (cfg *Config) newACMEClient(interactive bool) (*acmeClient, error) { ...@@ -142,6 +142,12 @@ func (cfg *Config) newACMEClient(interactive bool) (*acmeClient, error) {
// figure out which ports we'll be serving the challenges on // figure out which ports we'll be serving the challenges on
useHTTPPort := HTTPChallengePort useHTTPPort := HTTPChallengePort
useTLSALPNPort := TLSALPNChallengePort useTLSALPNPort := TLSALPNChallengePort
if HTTPPort > 0 && HTTPPort != HTTPChallengePort {
useHTTPPort = HTTPPort
}
if HTTPSPort > 0 && HTTPSPort != TLSALPNChallengePort {
useTLSALPNPort = HTTPSPort
}
if cfg.AltHTTPPort > 0 { if cfg.AltHTTPPort > 0 {
useHTTPPort = cfg.AltHTTPPort useHTTPPort = cfg.AltHTTPPort
} }
...@@ -333,7 +339,7 @@ func (c *acmeClient) Renew(name string) error { ...@@ -333,7 +339,7 @@ func (c *acmeClient) Renew(name string) error {
// Revoke revokes the certificate for name and deletes // Revoke revokes the certificate for name and deletes
// it from storage. // it from storage.
func (c *acmeClient) Revoke(name string) error { func (c *acmeClient) Revoke(name string) error {
if !c.config.certCache.storage.Exists(prefixSiteKey(c.config.CA, name)) { if !c.config.certCache.storage.Exists(StorageKeys.SitePrivateKey(c.config.CA, name)) {
return fmt.Errorf("private key not found for %s", name) return fmt.Errorf("private key not found for %s", name)
} }
...@@ -351,15 +357,15 @@ func (c *acmeClient) Revoke(name string) error { ...@@ -351,15 +357,15 @@ func (c *acmeClient) Revoke(name string) error {
c.config.OnEvent("acme_cert_revoked", name) c.config.OnEvent("acme_cert_revoked", name)
} }
err = c.config.certCache.storage.Delete(prefixSiteCert(c.config.CA, name)) err = c.config.certCache.storage.Delete(StorageKeys.SiteCert(c.config.CA, name))
if err != nil { if err != nil {
return fmt.Errorf("certificate revoked, but unable to delete certificate file: %v", err) return fmt.Errorf("certificate revoked, but unable to delete certificate file: %v", err)
} }
err = c.config.certCache.storage.Delete(prefixSiteKey(c.config.CA, name)) err = c.config.certCache.storage.Delete(StorageKeys.SitePrivateKey(c.config.CA, name))
if err != nil { if err != nil {
return fmt.Errorf("certificate revoked, but unable to delete private key: %v", err) return fmt.Errorf("certificate revoked, but unable to delete private key: %v", err)
} }
err = c.config.certCache.storage.Delete(prefixSiteMeta(c.config.CA, name)) err = c.config.certCache.storage.Delete(StorageKeys.SiteMeta(c.config.CA, name))
if err != nil { if err != nil {
return fmt.Errorf("certificate revoked, but unable to delete certificate metadata: %v", err) return fmt.Errorf("certificate revoked, but unable to delete certificate metadata: %v", err)
} }
......
...@@ -266,7 +266,7 @@ func (cfg *Config) ObtainCert(name string, interactive bool) error { ...@@ -266,7 +266,7 @@ func (cfg *Config) ObtainCert(name string, interactive bool) error {
// we expect this to be a new site; if the // we expect this to be a new site; if the
// cert already exists, then no-op // cert already exists, then no-op
if cfg.certCache.storage.Exists(prefixSiteCert(cfg.CA, name)) { if cfg.certCache.storage.Exists(StorageKeys.SiteCert(cfg.CA, name)) {
return nil return nil
} }
......
...@@ -104,15 +104,15 @@ func (cfg *Config) saveCertResource(cert *certificate.Resource) error { ...@@ -104,15 +104,15 @@ func (cfg *Config) saveCertResource(cert *certificate.Resource) error {
all := []keyValue{ all := []keyValue{
{ {
key: prefixSiteCert(cfg.CA, cert.Domain), key: StorageKeys.SiteCert(cfg.CA, cert.Domain),
value: cert.Certificate, value: cert.Certificate,
}, },
{ {
key: prefixSiteKey(cfg.CA, cert.Domain), key: StorageKeys.SitePrivateKey(cfg.CA, cert.Domain),
value: cert.PrivateKey, value: cert.PrivateKey,
}, },
{ {
key: prefixSiteMeta(cfg.CA, cert.Domain), key: StorageKeys.SiteMeta(cfg.CA, cert.Domain),
value: metaBytes, value: metaBytes,
}, },
} }
...@@ -122,15 +122,15 @@ func (cfg *Config) saveCertResource(cert *certificate.Resource) error { ...@@ -122,15 +122,15 @@ func (cfg *Config) saveCertResource(cert *certificate.Resource) error {
func (cfg *Config) loadCertResource(domain string) (certificate.Resource, error) { func (cfg *Config) loadCertResource(domain string) (certificate.Resource, error) {
var certRes certificate.Resource var certRes certificate.Resource
certBytes, err := cfg.certCache.storage.Load(prefixSiteCert(cfg.CA, domain)) certBytes, err := cfg.certCache.storage.Load(StorageKeys.SiteCert(cfg.CA, domain))
if err != nil { if err != nil {
return certRes, err return certRes, err
} }
keyBytes, err := cfg.certCache.storage.Load(prefixSiteKey(cfg.CA, domain)) keyBytes, err := cfg.certCache.storage.Load(StorageKeys.SitePrivateKey(cfg.CA, domain))
if err != nil { if err != nil {
return certRes, err return certRes, err
} }
metaBytes, err := cfg.certCache.storage.Load(prefixSiteMeta(cfg.CA, domain)) metaBytes, err := cfg.certCache.storage.Load(StorageKeys.SiteMeta(cfg.CA, domain))
if err != nil { if err != nil {
return certRes, err return certRes, err
} }
......
...@@ -33,13 +33,13 @@ type FileStorage struct { ...@@ -33,13 +33,13 @@ type FileStorage struct {
// Exists returns true if key exists in fs. // Exists returns true if key exists in fs.
func (fs FileStorage) Exists(key string) bool { func (fs FileStorage) Exists(key string) bool {
_, err := os.Stat(fs.filename(key)) _, err := os.Stat(fs.Filename(key))
return !os.IsNotExist(err) return !os.IsNotExist(err)
} }
// Store saves value at key. // Store saves value at key.
func (fs FileStorage) Store(key string, value []byte) error { func (fs FileStorage) Store(key string, value []byte) error {
filename := fs.filename(key) filename := fs.Filename(key)
err := os.MkdirAll(filepath.Dir(filename), 0700) err := os.MkdirAll(filepath.Dir(filename), 0700)
if err != nil { if err != nil {
return err return err
...@@ -49,7 +49,7 @@ func (fs FileStorage) Store(key string, value []byte) error { ...@@ -49,7 +49,7 @@ func (fs FileStorage) Store(key string, value []byte) error {
// Load retrieves the value at key. // Load retrieves the value at key.
func (fs FileStorage) Load(key string) ([]byte, error) { func (fs FileStorage) Load(key string) ([]byte, error) {
contents, err := ioutil.ReadFile(fs.filename(key)) contents, err := ioutil.ReadFile(fs.Filename(key))
if os.IsNotExist(err) { if os.IsNotExist(err) {
return nil, ErrNotExist(err) return nil, ErrNotExist(err)
} }
...@@ -59,7 +59,7 @@ func (fs FileStorage) Load(key string) ([]byte, error) { ...@@ -59,7 +59,7 @@ func (fs FileStorage) Load(key string) ([]byte, error) {
// Delete deletes the value at key. // Delete deletes the value at key.
// TODO: Delete any empty folders caused by this operation // TODO: Delete any empty folders caused by this operation
func (fs FileStorage) Delete(key string) error { func (fs FileStorage) Delete(key string) error {
err := os.Remove(fs.filename(key)) err := os.Remove(fs.Filename(key))
if os.IsNotExist(err) { if os.IsNotExist(err) {
return ErrNotExist(err) return ErrNotExist(err)
} }
...@@ -68,7 +68,7 @@ func (fs FileStorage) Delete(key string) error { ...@@ -68,7 +68,7 @@ func (fs FileStorage) Delete(key string) error {
// List returns all keys that match prefix. // List returns all keys that match prefix.
func (fs FileStorage) List(prefix string) ([]string, error) { func (fs FileStorage) List(prefix string) ([]string, error) {
d, err := os.Open(fs.filename(prefix)) d, err := os.Open(fs.Filename(prefix))
if os.IsNotExist(err) { if os.IsNotExist(err) {
return nil, ErrNotExist(err) return nil, ErrNotExist(err)
} }
...@@ -81,7 +81,7 @@ func (fs FileStorage) List(prefix string) ([]string, error) { ...@@ -81,7 +81,7 @@ func (fs FileStorage) List(prefix string) ([]string, error) {
// Stat returns information about key. // Stat returns information about key.
func (fs FileStorage) Stat(key string) (KeyInfo, error) { func (fs FileStorage) Stat(key string) (KeyInfo, error) {
fi, err := os.Stat(fs.filename(key)) fi, err := os.Stat(fs.Filename(key))
if os.IsNotExist(err) { if os.IsNotExist(err) {
return KeyInfo{}, ErrNotExist(err) return KeyInfo{}, ErrNotExist(err)
} }
...@@ -95,7 +95,9 @@ func (fs FileStorage) Stat(key string) (KeyInfo, error) { ...@@ -95,7 +95,9 @@ func (fs FileStorage) Stat(key string) (KeyInfo, error) {
}, nil }, nil
} }
func (fs FileStorage) filename(key string) string { // Filename returns the key as a path on the file
// system prefixed by fs.Path.
func (fs FileStorage) Filename(key string) string {
return filepath.Join(fs.Path, filepath.FromSlash(key)) return filepath.Join(fs.Path, filepath.FromSlash(key))
} }
...@@ -149,7 +151,7 @@ func (fs FileStorage) TryLock(key string) (Waiter, error) { ...@@ -149,7 +151,7 @@ func (fs FileStorage) TryLock(key string) (Waiter, error) {
} }
fw = &fileStorageWaiter{ fw = &fileStorageWaiter{
filename: filepath.Join(lockDir, safeKey(key)+".lock"), filename: filepath.Join(lockDir, StorageKeys.safe(key)+".lock"),
wg: new(sync.WaitGroup), wg: new(sync.WaitGroup),
} }
......
...@@ -52,7 +52,7 @@ func (certCache *Cache) stapleOCSP(cert *Certificate, pemBundle []byte) error { ...@@ -52,7 +52,7 @@ func (certCache *Cache) stapleOCSP(cert *Certificate, pemBundle []byte) error {
// First try to load OCSP staple from storage and see if // First try to load OCSP staple from storage and see if
// we can still use it. // we can still use it.
ocspStapleKey := prefixOCSPStaple(cert, pemBundle) ocspStapleKey := StorageKeys.OCSPStaple(cert, pemBundle)
cachedOCSP, err := certCache.storage.Load(ocspStapleKey) cachedOCSP, err := certCache.storage.Load(ocspStapleKey)
if err == nil { if err == nil {
resp, err := ocsp.ParseResponse(cachedOCSP, nil) resp, err := ocsp.ParseResponse(cachedOCSP, nil)
......
...@@ -134,13 +134,13 @@ func (dhs distributedSolver) CleanUp(domain, token, keyAuth string) error { ...@@ -134,13 +134,13 @@ func (dhs distributedSolver) CleanUp(domain, token, keyAuth string) error {
// challengeTokensPrefix returns the key prefix for challenge info. // challengeTokensPrefix returns the key prefix for challenge info.
func (dhs distributedSolver) challengeTokensPrefix() string { func (dhs distributedSolver) challengeTokensPrefix() string {
return filepath.Join(prefixCA(dhs.config.CA), "challenge_tokens") return filepath.Join(StorageKeys.CAPrefix(dhs.config.CA), "challenge_tokens")
} }
// challengeTokensKey returns the key to use to store and access // challengeTokensKey returns the key to use to store and access
// challenge info for domain. // challenge info for domain.
func (dhs distributedSolver) challengeTokensKey(domain string) string { func (dhs distributedSolver) challengeTokensKey(domain string) string {
return filepath.Join(dhs.challengeTokensPrefix(), safeKey(domain)+".json") return filepath.Join(dhs.challengeTokensPrefix(), StorageKeys.safe(domain)+".json")
} }
type challengeInfo struct { type challengeInfo struct {
......
...@@ -117,91 +117,100 @@ type keyValue struct { ...@@ -117,91 +117,100 @@ type keyValue struct {
value []byte value []byte
} }
const ( // KeyBuilder provides a namespace for methods that
prefixACME = "acme" // build keys and key prefixes, for addressing items
prefixOCSP = "ocsp" // in a Storage implementation.
) type KeyBuilder struct{}
func prefixCA(ca string) string { // CAPrefix returns the storage key prefix for
// the given certificate authority URL.
func (keys KeyBuilder) CAPrefix(ca string) string {
caURL, err := url.Parse(ca) caURL, err := url.Parse(ca)
if err != nil { if err != nil {
caURL = &url.URL{Host: ca} caURL = &url.URL{Host: ca}
} }
return path.Join(prefixACME, safeKey(caURL.Host)) return path.Join(prefixACME, keys.safe(caURL.Host))
} }
func prefixSite(ca, domain string) string { // SitePrefix returns a key prefix for items associated with
return path.Join(prefixCA(ca), "sites", safeKey(domain)) // the site using the given CA URL.
func (keys KeyBuilder) SitePrefix(ca, domain string) string {
return path.Join(keys.CAPrefix(ca), "sites", keys.safe(domain))
} }
// prefixSiteCert returns the path to the certificate file for domain. // SiteCert returns the path to the certificate file for domain.
func prefixSiteCert(ca, domain string) string { func (keys KeyBuilder) SiteCert(ca, domain string) string {
return path.Join(prefixSite(ca, domain), safeKey(domain)+".crt") return path.Join(keys.SitePrefix(ca, domain), keys.safe(domain)+".crt")
} }
// prefixSiteKey returns the path to domain's private key file. // SitePrivateKey returns the path to domain's private key file.
func prefixSiteKey(ca, domain string) string { func (keys KeyBuilder) SitePrivateKey(ca, domain string) string {
return path.Join(prefixSite(ca, domain), safeKey(domain)+".key") return path.Join(keys.SitePrefix(ca, domain), keys.safe(domain)+".key")
} }
// prefixSiteMeta returns the path to the domain's asset metadata file. // SiteMeta returns the path to the domain's asset metadata file.
func prefixSiteMeta(ca, domain string) string { func (keys KeyBuilder) SiteMeta(ca, domain string) string {
return path.Join(prefixSite(ca, domain), safeKey(domain)+".json") return path.Join(keys.SitePrefix(ca, domain), keys.safe(domain)+".json")
} }
func prefixUsers(ca string) string { // UsersPrefix returns a key prefix for items related to
return path.Join(prefixCA(ca), "users") // users associated with the given CA URL.
func (keys KeyBuilder) UsersPrefix(ca string) string {
return path.Join(keys.CAPrefix(ca), "users")
} }
// prefixUser gets the account folder for the user with email // UserPrefix returns a key prefix for items related to
func prefixUser(ca, email string) string { // the user with the given email for the given CA URL.
func (keys KeyBuilder) UserPrefix(ca, email string) string {
if email == "" { if email == "" {
email = emptyEmail email = emptyEmail
} }
return path.Join(prefixUsers(ca), safeKey(email)) return path.Join(keys.UsersPrefix(ca), keys.safe(email))
} }
// prefixUserReg gets the path to the registration file for the user with the // UserReg gets the path to the registration file for the user
// given email address. // with the given email address for the given CA URL.
func prefixUserReg(ca, email string) string { func (keys KeyBuilder) UserReg(ca, email string) string {
return safeUserKey(ca, email, "registration", ".json") return keys.safeUserKey(ca, email, "registration", ".json")
} }
// prefixUserKey gets the path to the private key file for the user with the // UserPrivateKey gets the path to the private key file for the
// given email address. // user with the given email address on the given CA URL.
func prefixUserKey(ca, email string) string { func (keys KeyBuilder) UserPrivateKey(ca, email string) string {
return safeUserKey(ca, email, "private", ".key") return keys.safeUserKey(ca, email, "private", ".key")
} }
func prefixOCSPStaple(cert *Certificate, pemBundle []byte) string { // OCSPStaple returns a key for the OCSP staple associated
// with the given certificate. If you have the PEM bundle
// handy, pass that in to save an extra encoding step.
func (keys KeyBuilder) OCSPStaple(cert *Certificate, pemBundle []byte) string {
var ocspFileName string var ocspFileName string
if len(cert.Names) > 0 { if len(cert.Names) > 0 {
firstName := safeKey(cert.Names[0]) firstName := keys.safe(cert.Names[0])
ocspFileName = firstName + "-" ocspFileName = firstName + "-"
} }
ocspFileName += fastHash(pemBundle) ocspFileName += fastHash(pemBundle)
return path.Join(prefixOCSP, ocspFileName) return path.Join(prefixOCSP, ocspFileName)
} }
// safeUserKey returns a key for the given email, // safeUserKey returns a key for the given email, with the default
// with the default filename, and the filename // filename, and the filename ending in the given extension.
// ending in the given extension. func (keys KeyBuilder) safeUserKey(ca, email, defaultFilename, extension string) string {
func safeUserKey(ca, email, defaultFilename, extension string) string {
if email == "" { if email == "" {
email = emptyEmail email = emptyEmail
} }
email = strings.ToLower(email) email = strings.ToLower(email)
filename := emailUsername(email) filename := keys.emailUsername(email)
if filename == "" { if filename == "" {
filename = defaultFilename filename = defaultFilename
} }
filename = safeKey(filename) filename = keys.safe(filename)
return path.Join(prefixUser(ca, email), filename+extension) return path.Join(keys.UserPrefix(ca, email), filename+extension)
} }
// emailUsername returns the username portion of an email address (part before // emailUsername returns the username portion of an email address (part before
// '@') or the original input if it can't find the "@" symbol. // '@') or the original input if it can't find the "@" symbol.
func emailUsername(email string) string { func (keys KeyBuilder) emailUsername(email string) string {
at := strings.Index(email, "@") at := strings.Index(email, "@")
if at == -1 { if at == -1 {
return email return email
...@@ -211,8 +220,9 @@ func emailUsername(email string) string { ...@@ -211,8 +220,9 @@ func emailUsername(email string) string {
return email[:at] return email[:at]
} }
// safeKey standardizes and sanitizes str for use in a file path. // safe standardizes and sanitizes str for use as
func safeKey(str string) string { // a storage key. This method is idempotent.
func (keys KeyBuilder) safe(str string) string {
str = strings.ToLower(str) str = strings.ToLower(str)
str = strings.TrimSpace(str) str = strings.TrimSpace(str)
...@@ -229,6 +239,19 @@ func safeKey(str string) string { ...@@ -229,6 +239,19 @@ func safeKey(str string) string {
return safeKeyRE.ReplaceAllLiteralString(str, "") return safeKeyRE.ReplaceAllLiteralString(str, "")
} }
// StorageKeys provides methods for accessing
// keys and key prefixes for items in a Storage.
// Typically, you will not need to use this
// because accessing storage is abstracted away
// for most cases. Only use this if you need to
// directly access TLS assets in your application.
var StorageKeys KeyBuilder
const (
prefixACME = "acme"
prefixOCSP = "ocsp"
)
// safeKeyRE matches any undesirable characters in storage keys. // safeKeyRE matches any undesirable characters in storage keys.
// Note that this allows dots, so you'll have to strip ".." manually. // Note that this allows dots, so you'll have to strip ".." manually.
var safeKeyRE = regexp.MustCompile(`[^\w@.-]`) var safeKeyRE = regexp.MustCompile(`[^\w@.-]`)
......
...@@ -155,7 +155,7 @@ func (cfg *Config) getEmail(userPresent bool) (string, error) { ...@@ -155,7 +155,7 @@ func (cfg *Config) getEmail(userPresent bool) (string, error) {
func (cfg *Config) getUser(email string) (user, error) { func (cfg *Config) getUser(email string) (user, error) {
var user user var user user
regBytes, err := cfg.certCache.storage.Load(prefixUserReg(cfg.CA, email)) regBytes, err := cfg.certCache.storage.Load(StorageKeys.UserReg(cfg.CA, email))
if err != nil { if err != nil {
if _, ok := err.(ErrNotExist); ok { if _, ok := err.(ErrNotExist); ok {
// create a new user // create a new user
...@@ -163,7 +163,7 @@ func (cfg *Config) getUser(email string) (user, error) { ...@@ -163,7 +163,7 @@ func (cfg *Config) getUser(email string) (user, error) {
} }
return user, err return user, err
} }
keyBytes, err := cfg.certCache.storage.Load(prefixUserKey(cfg.CA, email)) keyBytes, err := cfg.certCache.storage.Load(StorageKeys.UserPrivateKey(cfg.CA, email))
if err != nil { if err != nil {
if _, ok := err.(ErrNotExist); ok { if _, ok := err.(ErrNotExist); ok {
// create a new user // create a new user
...@@ -197,11 +197,11 @@ func (cfg *Config) saveUser(user user) error { ...@@ -197,11 +197,11 @@ func (cfg *Config) saveUser(user user) error {
all := []keyValue{ all := []keyValue{
{ {
key: prefixUserReg(cfg.CA, user.Email), key: StorageKeys.UserReg(cfg.CA, user.Email),
value: regBytes, value: regBytes,
}, },
{ {
key: prefixUserKey(cfg.CA, user.Email), key: StorageKeys.UserPrivateKey(cfg.CA, user.Email),
value: keyBytes, value: keyBytes,
}, },
} }
...@@ -240,13 +240,13 @@ func (cfg *Config) askUserAgreement(agreementURL string) bool { ...@@ -240,13 +240,13 @@ func (cfg *Config) askUserAgreement(agreementURL string) bool {
// account, errors here are discarded to simplify code flow in // account, errors here are discarded to simplify code flow in
// the caller, and errors are not important here anyway. // the caller, and errors are not important here anyway.
func (cfg *Config) mostRecentUserEmail() string { func (cfg *Config) mostRecentUserEmail() string {
userList, err := cfg.certCache.storage.List(prefixUsers(cfg.CA)) userList, err := cfg.certCache.storage.List(StorageKeys.UsersPrefix(cfg.CA))
if err != nil || len(userList) == 0 { if err != nil || len(userList) == 0 {
return "" return ""
} }
sort.Slice(userList, func(i, j int) bool { sort.Slice(userList, func(i, j int) bool {
iInfo, _ := cfg.certCache.storage.Stat(prefixUser(cfg.CA, userList[i])) iInfo, _ := cfg.certCache.storage.Stat(StorageKeys.UserPrefix(cfg.CA, userList[i]))
jInfo, _ := cfg.certCache.storage.Stat(prefixUser(cfg.CA, userList[j])) jInfo, _ := cfg.certCache.storage.Stat(StorageKeys.UserPrefix(cfg.CA, userList[j]))
return jInfo.Modified.Before(iInfo.Modified) return jInfo.Modified.Before(iInfo.Modified)
}) })
user, err := cfg.getUser(userList[0]) user, err := cfg.getUser(userList[0])
......
...@@ -138,7 +138,7 @@ ...@@ -138,7 +138,7 @@
"importpath": "github.com/mholt/certmagic", "importpath": "github.com/mholt/certmagic",
"repository": "https://github.com/mholt/certmagic", "repository": "https://github.com/mholt/certmagic",
"vcs": "git", "vcs": "git",
"revision": "8b6ddf223c912a863aaadd388bfdd29be295fb5d", "revision": "5b3085c491553887f36460365533eb5955fdeef0",
"branch": "master", "branch": "master",
"notests": true "notests": true
}, },
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment