[Author Prev][Author Next][Thread Prev][Thread Next][Author Index][Thread Index]
[tor-commits] [meek/master] Add support for dynamic reloading of certificates. (Go 1.6+)
commit f7f792f28ebc3520e06b124083a768cb32e4c88d
Author: Yawning Angel <yawning@xxxxxxxxxxxxxxx>
Date: Sun Apr 10 06:26:36 2016 +0000
Add support for dynamic reloading of certificates. (Go 1.6+)
Entirely untested, probably correct. The callback used to feed the TLS
Listener the certificate was introduced in Go 1.6, so that version or
newer is now required.
---
meek-server/certificate.go | 114 +++++++++++++++++++++++++++++++++++++++++++++
meek-server/meek-server.go | 14 +++---
2 files changed, 122 insertions(+), 6 deletions(-)
diff --git a/meek-server/certificate.go b/meek-server/certificate.go
new file mode 100644
index 0000000..2c57d85
--- /dev/null
+++ b/meek-server/certificate.go
@@ -0,0 +1,114 @@
+// certificate.go - Certificate management for meek-server.
+
+// +build go1.6
+
+package main
+
+import (
+ "crypto/tls"
+ "log"
+ "os"
+ "sync"
+ "time"
+)
+
+const certLoadErrorRateLimit = 1 * time.Minute
+
+type certContext struct {
+ sync.Mutex
+
+ certFile string
+ keyFile string
+
+ certFileInfo os.FileInfo
+ keyFileInfo os.FileInfo
+ cachedCert *tls.Certificate
+
+ lastWarnAt time.Time
+}
+
+func newCertContext(certFilename, keyFilename string) (*certContext, error) {
+ ctx := new(certContext)
+ ctx.certFile = certFilename
+ ctx.keyFile = keyFilename
+ if _, err := ctx.reloadCertificate(); err != nil {
+ return nil, err
+ }
+ return ctx, nil
+}
+
+func (ctx *certContext) reloadCertificate() (*tls.Certificate, error) {
+ doReload := true
+
+ // XXX/Yawning: I assume compared to everything else related to TLS
+ // handshakes, stat() is cheap. If not, ratelimit here. gettimeofday()
+ // is vDSO-ed so it would be significantly faster than the syscalls.
+
+ var err error
+ var cfInfo, kfInfo os.FileInfo
+ if cfInfo, err = os.Stat(ctx.certFile); err == nil {
+ kfInfo, err = os.Stat(ctx.keyFile)
+ }
+
+ ctx.Lock()
+ defer ctx.Unlock()
+
+ // Grab the cached certificate, compare the modification times if able.
+ cert := ctx.cachedCert
+ if err != nil {
+ // If stat fails, we likely aren't going to be able to reload, so
+ // return early.
+ return cert, err
+ } else if ctx.cachedCert != nil {
+ // Only compare the file times if there's actually a cached cert,
+ // and reload the cert if either the key or the certificate have
+ // been modified.
+ doReload = !ctx.certFileInfo.ModTime().Equal(cfInfo.ModTime()) || !ctx.keyFileInfo.ModTime().Equal(kfInfo.ModTime())
+ }
+
+ // Attempt to load the updated certificate, if required.
+ if doReload {
+ newCert, err := tls.LoadX509KeyPair(ctx.certFile, ctx.keyFile)
+ if err != nil {
+ // If the load fails, return the old certificate, so that it can
+ // be used till the load succeeds.
+ return cert, err
+ }
+
+ // If the user regenerates the cert/key between the stat() and
+ // LoadX509KeyPair calls, this will race, but will self-correct
+ // after the next reloadCertificate() call because doReload will
+ // be true.
+
+ ctx.cachedCert = &newCert
+ ctx.certFileInfo = cfInfo
+ ctx.keyFileInfo = kfInfo
+
+ cert = ctx.cachedCert
+ }
+ return cert, nil
+}
+
+func (ctx *certContext) getCertificate(clientHello *tls.ClientHelloInfo) (*tls.Certificate, error) {
+ cert, err := ctx.reloadCertificate()
+ if err != nil {
+ // Failure to reload the certificate is a non-fatal error as this
+ // may be a filesystem related race condition. There is nothing
+ // preventing the next callback from hopefully succeeding, so rate
+ // limit an error log.
+ now := time.Now()
+ if now.After(ctx.lastWarnAt.Add(certLoadErrorRateLimit)) {
+ ctx.lastWarnAt = now
+ log.Printf("failed to reload certificate: %v", err)
+ }
+ }
+
+ // This should NEVER happen because we will continue to use the old
+ // certificate on load failure, and we will never be calling the
+ // listener GetCertificate() callback if the initial load fails.
+ if cert == nil {
+ panic("no cached certificate available")
+ }
+
+ return cert, nil
+}
diff --git a/meek-server/meek-server.go b/meek-server/meek-server.go
index ab7730a..eb7adc9 100644
--- a/meek-server/meek-server.go
+++ b/meek-server/meek-server.go
@@ -264,6 +264,11 @@ func (state *State) ExpireSessions() {
}
func listenTLS(network string, addr *net.TCPAddr, certFilename, keyFilename string) (net.Listener, error) {
+ ctx, err := newCertContext(certFilename, keyFilename)
+ if err != nil {
+ return nil, err
+ }
+
// This is cribbed from the source of net/http.Server.ListenAndServeTLS.
// We have to separate the Listen and Serve parts because we need to
// report the listening address before entering Serve (which is an
@@ -272,12 +277,9 @@ func listenTLS(network string, addr *net.TCPAddr, certFilename, keyFilename stri
config := &tls.Config{}
config.NextProtos = []string{"http/1.1"}
- var err error
- config.Certificates = make([]tls.Certificate, 1)
- config.Certificates[0], err = tls.LoadX509KeyPair(certFilename, keyFilename)
- if err != nil {
- return nil, err
- }
+ // Install a GetCertificate callback that ensures that the certificate is
+ // up to date.
+ config.GetCertificate = ctx.getCertificate
conn, err := net.ListenTCP(network, addr)
if err != nil {
_______________________________________________
tor-commits mailing list
tor-commits@xxxxxxxxxxxxxxxxxxxx
https://lists.torproject.org/cgi-bin/mailman/listinfo/tor-commits