commit f7f792f28ebc3520e06b124083a768cb32e4c88d
Author: Yawning Angel <[email protected]>
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
[email protected]
https://lists.torproject.org/cgi-bin/mailman/listinfo/tor-commits

Reply via email to