aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNiall Sheridan <nsheridan@gmail.com>2018-08-10 17:14:17 +0100
committerNiall Sheridan <nsheridan@gmail.com>2018-08-10 17:38:29 +0100
commit99a01f63f51b73f103cd1e094f1a8e7f35d9d30b (patch)
tree37cc9bdaf329773d284834b24d70ac1c14e6ffd3
parent80bab78526a161b3389358a55652650bb35f567d (diff)
Fix LetsEncrypt support
-rw-r--r--server/server.go6
-rw-r--r--vendor/golang.org/x/crypto/acme/acme.go19
-rw-r--r--vendor/golang.org/x/crypto/acme/autocert/autocert.go91
-rw-r--r--vendor/golang.org/x/crypto/acme/autocert/listener.go7
-rw-r--r--vendor/golang.org/x/crypto/acme/autocert/renewal.go2
-rw-r--r--vendor/golang.org/x/crypto/acme/http.go19
-rw-r--r--vendor/vendor.json12
7 files changed, 109 insertions, 47 deletions
diff --git a/server/server.go b/server/server.go
index 2995ead..42476f3 100644
--- a/server/server.go
+++ b/server/server.go
@@ -60,10 +60,12 @@ func Run(conf *config.Config) {
if conf.Server.LetsEncryptServername != "" {
m := autocert.Manager{
Prompt: autocert.AcceptTOS,
- Cache: wkfscache.Cache(conf.Server.LetsEncryptCache),
HostPolicy: autocert.HostWhitelist(conf.Server.LetsEncryptServername),
}
- tlsConfig.GetCertificate = m.GetCertificate
+ if conf.Server.LetsEncryptCache != "" {
+ m.Cache = wkfscache.Cache(conf.Server.LetsEncryptCache)
+ }
+ tlsConfig = m.TLSConfig()
} else {
if conf.Server.TLSCert == "" || conf.Server.TLSKey == "" {
log.Fatal("TLS cert or key not specified in config")
diff --git a/vendor/golang.org/x/crypto/acme/acme.go b/vendor/golang.org/x/crypto/acme/acme.go
index 8257ffb..7df6476 100644
--- a/vendor/golang.org/x/crypto/acme/acme.go
+++ b/vendor/golang.org/x/crypto/acme/acme.go
@@ -39,8 +39,18 @@ import (
"time"
)
-// LetsEncryptURL is the Directory endpoint of Let's Encrypt CA.
-const LetsEncryptURL = "https://acme-v01.api.letsencrypt.org/directory"
+const (
+ // LetsEncryptURL is the Directory endpoint of Let's Encrypt CA.
+ LetsEncryptURL = "https://acme-v01.api.letsencrypt.org/directory"
+
+ // ALPNProto is the ALPN protocol name used by a CA server when validating
+ // tls-alpn-01 challenges.
+ //
+ // Package users must ensure their servers can negotiate the ACME ALPN in
+ // order for tls-alpn-01 challenge verifications to succeed.
+ // See the crypto/tls package's Config.NextProtos field.
+ ALPNProto = "acme-tls/1"
+)
// idPeACMEIdentifierV1 is the OID for the ACME extension for the TLS-ALPN challenge.
var idPeACMEIdentifierV1 = asn1.ObjectIdentifier{1, 3, 6, 1, 5, 5, 7, 1, 30, 1}
@@ -645,8 +655,9 @@ func (c *Client) doReg(ctx context.Context, url string, typ string, acct *Accoun
req.Agreement = acct.AgreedTerms
}
res, err := c.post(ctx, c.Key, url, req, wantStatus(
- http.StatusOK, // updates and deletes
- http.StatusCreated, // new account creation
+ http.StatusOK, // updates and deletes
+ http.StatusCreated, // new account creation
+ http.StatusAccepted, // Let's Encrypt divergent implementation
))
if err != nil {
return nil, err
diff --git a/vendor/golang.org/x/crypto/acme/autocert/autocert.go b/vendor/golang.org/x/crypto/acme/autocert/autocert.go
index c8fa4e6..4c2fc07 100644
--- a/vendor/golang.org/x/crypto/acme/autocert/autocert.go
+++ b/vendor/golang.org/x/crypto/acme/autocert/autocert.go
@@ -44,7 +44,7 @@ var createCertRetryAfter = time.Minute
var pseudoRand *lockedMathRand
func init() {
- src := mathrand.NewSource(timeNow().UnixNano())
+ src := mathrand.NewSource(time.Now().UnixNano())
pseudoRand = &lockedMathRand{rnd: mathrand.New(src)}
}
@@ -81,9 +81,9 @@ func defaultHostPolicy(context.Context, string) error {
}
// Manager is a stateful certificate manager built on top of acme.Client.
-// It obtains and refreshes certificates automatically using "tls-sni-01",
-// "tls-sni-02" and "http-01" challenge types, as well as providing them
-// to a TLS server via tls.Config.
+// It obtains and refreshes certificates automatically using "tls-alpn-01",
+// "tls-sni-01", "tls-sni-02" and "http-01" challenge types,
+// as well as providing them to a TLS server via tls.Config.
//
// You must specify a cache implementation, such as DirCache,
// to reuse obtained certificates across program restarts.
@@ -177,18 +177,22 @@ type Manager struct {
// to be provisioned.
// The entries are stored for the duration of the authorization flow.
httpTokens map[string][]byte
- // certTokens contains temporary certificates for tls-sni challenges
+ // certTokens contains temporary certificates for tls-sni and tls-alpn challenges
// and is keyed by token domain name, which matches server name of ClientHello.
- // Keys always have ".acme.invalid" suffix.
+ // Keys always have ".acme.invalid" suffix for tls-sni. Otherwise, they are domain names
+ // for tls-alpn.
// The entries are stored for the duration of the authorization flow.
certTokens map[string]*tls.Certificate
+ // nowFunc, if not nil, returns the current time. This may be set for
+ // testing purposes.
+ nowFunc func() time.Time
}
// certKey is the key by which certificates are tracked in state, renewal and cache.
type certKey struct {
domain string // without trailing dot
isRSA bool // RSA cert for legacy clients (as opposed to default ECDSA)
- isToken bool // tls-sni challenge token cert; key type is undefined regardless of isRSA
+ isToken bool // tls-based challenge token cert; key type is undefined regardless of isRSA
}
func (c certKey) String() string {
@@ -201,14 +205,32 @@ func (c certKey) String() string {
return c.domain
}
+// TLSConfig creates a new TLS config suitable for net/http.Server servers,
+// supporting HTTP/2 and the tls-alpn-01 ACME challenge type.
+func (m *Manager) TLSConfig() *tls.Config {
+ return &tls.Config{
+ GetCertificate: m.GetCertificate,
+ NextProtos: []string{
+ "h2", "http/1.1", // enable HTTP/2
+ acme.ALPNProto, // enable tls-alpn ACME challenges
+ },
+ }
+}
+
// GetCertificate implements the tls.Config.GetCertificate hook.
// It provides a TLS certificate for hello.ServerName host, including answering
-// *.acme.invalid (TLS-SNI) challenges. All other fields of hello are ignored.
+// tls-alpn-01 and *.acme.invalid (tls-sni-01 and tls-sni-02) challenges.
+// All other fields of hello are ignored.
//
// If m.HostPolicy is non-nil, GetCertificate calls the policy before requesting
// a new cert. A non-nil error returned from m.HostPolicy halts TLS negotiation.
// The error is propagated back to the caller of GetCertificate and is user-visible.
// This does not affect cached certs. See HostPolicy field description for more details.
+//
+// If GetCertificate is used directly, instead of via Manager.TLSConfig, package users will
+// also have to add acme.ALPNProto to NextProtos for tls-alpn-01, or use HTTPHandler
+// for http-01. (The tls-sni-* challenges have been deprecated by popular ACME providers
+// due to security issues in the ecosystem.)
func (m *Manager) GetCertificate(hello *tls.ClientHelloInfo) (*tls.Certificate, error) {
if m.Prompt == nil {
return nil, errors.New("acme/autocert: Manager.Prompt not set")
@@ -230,10 +252,13 @@ func (m *Manager) GetCertificate(hello *tls.ClientHelloInfo) (*tls.Certificate,
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute)
defer cancel()
- // check whether this is a token cert requested for TLS-SNI challenge
- if strings.HasSuffix(name, ".acme.invalid") {
+ // Check whether this is a token cert requested for TLS-SNI or TLS-ALPN challenge.
+ if wantsTokenCert(hello) {
m.tokensMu.RLock()
defer m.tokensMu.RUnlock()
+ // It's ok to use the same token cert key for both tls-sni and tls-alpn
+ // because there's always at most 1 token cert per on-going domain authorization.
+ // See m.verify for details.
if cert := m.certTokens[name]; cert != nil {
return cert, nil
}
@@ -269,6 +294,17 @@ func (m *Manager) GetCertificate(hello *tls.ClientHelloInfo) (*tls.Certificate,
return cert, nil
}
+// wantsTokenCert reports whether a TLS request with SNI is made by a CA server
+// for a challenge verification.
+func wantsTokenCert(hello *tls.ClientHelloInfo) bool {
+ // tls-alpn-01
+ if len(hello.SupportedProtos) == 1 && hello.SupportedProtos[0] == acme.ALPNProto {
+ return true
+ }
+ // tls-sni-xx
+ return strings.HasSuffix(hello.ServerName, ".acme.invalid")
+}
+
func supportsECDSA(hello *tls.ClientHelloInfo) bool {
// The "signature_algorithms" extension, if present, limits the key exchange
// algorithms allowed by the cipher suites. See RFC 5246, section 7.4.1.4.1.
@@ -328,8 +364,8 @@ func supportsECDSA(hello *tls.ClientHelloInfo) bool {
// Because the fallback handler is run with unencrypted port 80 requests,
// the fallback should not serve TLS-only requests.
//
-// If HTTPHandler is never called, the Manager will only use TLS SNI
-// challenges for domain verification.
+// If HTTPHandler is never called, the Manager will only use the "tls-alpn-01"
+// challenge for domain verification.
func (m *Manager) HTTPHandler(fallback http.Handler) http.Handler {
m.tokensMu.Lock()
defer m.tokensMu.Unlock()
@@ -447,7 +483,7 @@ func (m *Manager) cacheGet(ctx context.Context, ck certKey) (*tls.Certificate, e
}
// verify and create TLS cert
- leaf, err := validCert(ck, pubDER, privKey)
+ leaf, err := validCert(ck, pubDER, privKey, m.now())
if err != nil {
return nil, ErrCacheMiss
}
@@ -542,7 +578,7 @@ func (m *Manager) createCert(ctx context.Context, ck certKey) (*tls.Certificate,
if !ok {
return
}
- if _, err := validCert(ck, s.cert, s.key); err == nil {
+ if _, err := validCert(ck, s.cert, s.key, m.now()); err == nil {
return
}
delete(m.state, ck)
@@ -611,7 +647,7 @@ func (m *Manager) authorizedCert(ctx context.Context, key crypto.Signer, ck cert
if err != nil {
return nil, nil, err
}
- leaf, err = validCert(ck, der, key)
+ leaf, err = validCert(ck, der, key, m.now())
if err != nil {
return nil, nil, err
}
@@ -635,7 +671,7 @@ func (m *Manager) revokePendingAuthz(ctx context.Context, uri []string) {
func (m *Manager) verify(ctx context.Context, client *acme.Client, domain string) error {
// The list of challenge types we'll try to fulfill
// in this specific order.
- challengeTypes := []string{"tls-sni-02", "tls-sni-01"}
+ challengeTypes := []string{"tls-alpn-01", "tls-sni-02", "tls-sni-01"}
m.tokensMu.RLock()
if m.tryHTTP01 {
challengeTypes = append(challengeTypes, "http-01")
@@ -691,7 +727,7 @@ func (m *Manager) verify(ctx context.Context, client *acme.Client, domain string
}
return errors.New(errorMsg)
}
- cleanup, err := m.fulfill(ctx, client, chal)
+ cleanup, err := m.fulfill(ctx, client, chal, domain)
if err != nil {
errs[chal] = err
continue
@@ -714,8 +750,15 @@ func (m *Manager) verify(ctx context.Context, client *acme.Client, domain string
// fulfill provisions a response to the challenge chal.
// The cleanup is non-nil only if provisioning succeeded.
-func (m *Manager) fulfill(ctx context.Context, client *acme.Client, chal *acme.Challenge) (cleanup func(), err error) {
+func (m *Manager) fulfill(ctx context.Context, client *acme.Client, chal *acme.Challenge, domain string) (cleanup func(), err error) {
switch chal.Type {
+ case "tls-alpn-01":
+ cert, err := client.TLSALPN01ChallengeCert(chal.Token, domain)
+ if err != nil {
+ return nil, err
+ }
+ m.putCertToken(ctx, domain, &cert)
+ return func() { go m.deleteCertToken(domain) }, nil
case "tls-sni-01":
cert, name, err := client.TLSSNI01ChallengeCert(chal.Token)
if err != nil {
@@ -948,6 +991,13 @@ func (m *Manager) renewBefore() time.Duration {
return 720 * time.Hour // 30 days
}
+func (m *Manager) now() time.Time {
+ if m.nowFunc != nil {
+ return m.nowFunc()
+ }
+ return time.Now()
+}
+
// certState is ready when its mutex is unlocked for reading.
type certState struct {
sync.RWMutex
@@ -1014,7 +1064,7 @@ func parsePrivateKey(der []byte) (crypto.Signer, error) {
// are valid. It doesn't do any revocation checking.
//
// The returned value is the verified leaf cert.
-func validCert(ck certKey, der [][]byte, key crypto.Signer) (leaf *x509.Certificate, err error) {
+func validCert(ck certKey, der [][]byte, key crypto.Signer, now time.Time) (leaf *x509.Certificate, err error) {
// parse public part(s)
var n int
for _, b := range der {
@@ -1031,7 +1081,6 @@ func validCert(ck certKey, der [][]byte, key crypto.Signer) (leaf *x509.Certific
}
// verify the leaf is not expired and matches the domain name
leaf = x509Cert[0]
- now := timeNow()
if now.Before(leaf.NotBefore) {
return nil, errors.New("acme/autocert: certificate is not valid yet")
}
@@ -1085,8 +1134,6 @@ func (r *lockedMathRand) int63n(max int64) int64 {
// For easier testing.
var (
- timeNow = time.Now
-
// Called when a state is removed.
testDidRemoveState = func(certKey) {}
)
diff --git a/vendor/golang.org/x/crypto/acme/autocert/listener.go b/vendor/golang.org/x/crypto/acme/autocert/listener.go
index d744df0..1e06981 100644
--- a/vendor/golang.org/x/crypto/acme/autocert/listener.go
+++ b/vendor/golang.org/x/crypto/acme/autocert/listener.go
@@ -72,11 +72,8 @@ func NewListener(domains ...string) net.Listener {
// the Manager m's Prompt, Cache, HostPolicy, and other desired options.
func (m *Manager) Listener() net.Listener {
ln := &listener{
- m: m,
- conf: &tls.Config{
- GetCertificate: m.GetCertificate, // bonus: panic on nil m
- NextProtos: []string{"h2", "http/1.1"}, // Enable HTTP/2
- },
+ m: m,
+ conf: m.TLSConfig(),
}
ln.tcpListener, ln.tcpListenErr = net.Listen("tcp", ":443")
return ln
diff --git a/vendor/golang.org/x/crypto/acme/autocert/renewal.go b/vendor/golang.org/x/crypto/acme/autocert/renewal.go
index ef3e44e..665f870 100644
--- a/vendor/golang.org/x/crypto/acme/autocert/renewal.go
+++ b/vendor/golang.org/x/crypto/acme/autocert/renewal.go
@@ -128,7 +128,7 @@ func (dr *domainRenewal) do(ctx context.Context) (time.Duration, error) {
}
func (dr *domainRenewal) next(expiry time.Time) time.Duration {
- d := expiry.Sub(timeNow()) - dr.m.renewBefore()
+ d := expiry.Sub(dr.m.now()) - dr.m.renewBefore()
// add a bit of randomness to renew deadline
n := pseudoRand.int63n(int64(renewJitter))
d -= time.Duration(n)
diff --git a/vendor/golang.org/x/crypto/acme/http.go b/vendor/golang.org/x/crypto/acme/http.go
index 56ba53a..a43ce6a 100644
--- a/vendor/golang.org/x/crypto/acme/http.go
+++ b/vendor/golang.org/x/crypto/acme/http.go
@@ -140,10 +140,13 @@ func (c *Client) get(ctx context.Context, url string, ok resOkay) (*http.Respons
case ok(res):
return res, nil
case isRetriable(res.StatusCode):
- res.Body.Close()
retry.inc()
- if err := retry.backoff(ctx, req, res); err != nil {
- return nil, err
+ resErr := responseError(res)
+ res.Body.Close()
+ // Ignore the error value from retry.backoff
+ // and return the one from last retry, as received from the CA.
+ if retry.backoff(ctx, req, res) != nil {
+ return nil, resErr
}
default:
defer res.Body.Close()
@@ -169,20 +172,22 @@ func (c *Client) post(ctx context.Context, key crypto.Signer, url string, body i
if ok(res) {
return res, nil
}
- err = responseError(res)
+ resErr := responseError(res)
res.Body.Close()
switch {
// Check for bad nonce before isRetriable because it may have been returned
// with an unretriable response code such as 400 Bad Request.
- case isBadNonce(err):
+ case isBadNonce(resErr):
// Consider any previously stored nonce values to be invalid.
c.clearNonces()
case !isRetriable(res.StatusCode):
- return nil, err
+ return nil, resErr
}
retry.inc()
+ // Ignore the error value from retry.backoff
+ // and return the one from last retry, as received from the CA.
if err := retry.backoff(ctx, req, res); err != nil {
- return nil, err
+ return nil, resErr
}
}
}
diff --git a/vendor/vendor.json b/vendor/vendor.json
index 4471df2..aca060d 100644
--- a/vendor/vendor.json
+++ b/vendor/vendor.json
@@ -835,16 +835,16 @@
"revisionTime": "2018-04-17T22:48:46Z"
},
{
- "checksumSHA1": "1wzl062spqfhV985nlLdj/cngns=",
+ "checksumSHA1": "/pfkYB22L5MtzxrfIHQ0tWylnZw=",
"path": "golang.org/x/crypto/acme",
- "revision": "7f39a6fea4fe9364fb61e1def6a268a51b4f3a06",
- "revisionTime": "2018-06-15T16:03:23Z"
+ "revision": "de0752318171da717af4ce24d0a2e8626afaeb11",
+ "revisionTime": "2018-08-08T16:52:45Z"
},
{
- "checksumSHA1": "JjpnjJabumVpCg8Sths68wWNv7Q=",
+ "checksumSHA1": "Ayt033DfS42Y57XUoOAZvfGUyaA=",
"path": "golang.org/x/crypto/acme/autocert",
- "revision": "7f39a6fea4fe9364fb61e1def6a268a51b4f3a06",
- "revisionTime": "2018-06-15T16:03:23Z"
+ "revision": "de0752318171da717af4ce24d0a2e8626afaeb11",
+ "revisionTime": "2018-08-08T16:52:45Z"
},
{
"checksumSHA1": "IQkUIOnvlf0tYloFx9mLaXSvXWQ=",