[tor-commits] [meek/master] Add the --acme-hostnames option for automatic Let's Encrypt certificates.

dcf at torproject.org dcf at torproject.org
Tue Apr 11 08:54:17 UTC 2017


commit 20bea12d64473a57969c2b1fbb087d64a7259116
Author: David Fifield <david at bamsoftware.com>
Date:   Thu Mar 30 18:08:03 2017 -0700

    Add the --acme-hostnames option for automatic Let's Encrypt certificates.
    
    The inspiration for this code came from George Tankersley's patch:
    	https://bugs.torproject.org/18655#comment:8
    	https://github.com/gtank/meek/tree/letsencrypt
    
    The meek-server code is partially cribbed from the Snowflake server
    code:
    	https://trac.torproject.org/projects/tor/ticket/18654#comment:7
    	https://gitweb.torproject.org/user/dcf/snowflake.git/diff/?h=letsencrypt&id=1f8be86a01&id2=af70d49e96
---
 doc/meek-server.1.txt      | 33 ++++++++++++++---
 meek-server/meek-server.go | 90 ++++++++++++++++++++++++++++++++++++++--------
 2 files changed, 104 insertions(+), 19 deletions(-)

diff --git a/doc/meek-server.1.txt b/doc/meek-server.1.txt
index b162af8..bbbcc7d 100644
--- a/doc/meek-server.1.txt
+++ b/doc/meek-server.1.txt
@@ -11,26 +11,51 @@ meek-server - The meek server transport plugin
 
 SYNOPSIS
 --------
-**meek-server** **--cert**=__FILENAME__ **--key**=__FILENAME__ [__OPTIONS__]
+**meek-server** **--acme-hostnames**=__HOSTNAME__ [__OPTIONS__]
 
 DESCRIPTION
 -----------
 meek-server is a transport plugin for Tor that encodes a stream as a
 sequence of HTTP requests and responses.
 
-The server runs in HTTPS mode by default, and the **--cert** and
-**--key** options are required.
+You will need to configure TLS certificates. There are two ways to set
+up certificates:
+
+* **--acme-hostnames**=__HOSTNAME__ will automatically get certificates
+  for __HOSTNAME__ using Let's Encrypt. This only works when meek-server
+  is running on port 443.
+* **--cert**=__FILENAME__ and **--key**=__FILENAME__ allow use to use
+  your own externally acquired certificate.
 
 Configuration for meek-server usually appears in a torrc file. Here is a
-sample configuration using HTTPS:
+sample configuration using automatic Let's Encrypt certificates:
+----
+ExtORPort auto
+ServerTransportListenAddr 0.0.0.0:443
+ServerTransportPlugin meek exec ./meek-server --acme-hostnames meek-server.example --log meek-server.log
+----
+Here is a sample configuration using externally acquired certificates:
 ----
 ExtORPort auto
 ServerTransportListenAddr meek 0.0.0.0:8443
 ServerTransportPlugin meek exec ./meek-server 8443 --cert cert.pem --key key.pem --log meek-server.log
 ----
 
+To listen on port 443 without needed to run as root, on Linux,
+you can use the `setcap` program, part of libcap2:
+----
+setcap 'cap_net_bind_service=+ep' /usr/local/bin/meek-server
+----
+
 OPTIONS
 -------
+**--acme-hostnames**=__HOSTNAME__[,__HOSTNAME__]...::
+    Comma-separated list of hostnames to honor when getting automatic
+    certificates from Let's Encrypt. meek-server has to be running on
+    port 443 in order for the **--acme-hostnames** option to work. The
+    certificates will be cached in the pt_state/meek-certificate-cache
+    directory inside tor state directory.
+
 **--cert**=__FILENAME__::
     Name of a PEM-encoded TLS certificate file. Required unless
     **--disable-tls** is used.
diff --git a/meek-server/meek-server.go b/meek-server/meek-server.go
index be2cd36..b8449a0 100644
--- a/meek-server/meek-server.go
+++ b/meek-server/meek-server.go
@@ -3,14 +3,19 @@
 // data to a local OR port.
 //
 // Sample usage in torrc:
+// 	ServerTransportListenAddr meek 0.0.0.0:443
+// 	ServerTransportPlugin meek exec ./meek-server --acme-hostnames meek-server.example --log meek-server.log
+// Using your own TLS certificate:
 // 	ServerTransportListenAddr meek 0.0.0.0:8443
 // 	ServerTransportPlugin meek exec ./meek-server --cert cert.pem --key key.pem --log meek-server.log
 // Plain HTTP usage:
 // 	ServerTransportListenAddr meek 0.0.0.0:8080
 // 	ServerTransportPlugin meek exec ./meek-server --disable-tls --log meek-server.log
 //
-// The server runs in HTTPS mode by default, and the --cert and --key options
-// are required. Use the --disable-tls option to run with plain HTTP.
+// The server runs in HTTPS mode by default, getting certificates from Let's
+// Encrypt automatically. The server must be listening on port 443 for the
+// automatic certificates to work. If you have your own certificate, use the
+// --cert and --key options. Use --disable-tls option to run with plain HTTP.
 package main
 
 import (
@@ -24,12 +29,15 @@ import (
 	"os"
 	"os/signal"
 	"path"
+	"path/filepath"
 	"runtime"
+	"strings"
 	"sync"
 	"syscall"
 	"time"
 
 	"git.torproject.org/pluggable-transports/goptlib.git"
+	"golang.org/x/crypto/acme/autocert"
 )
 
 const (
@@ -326,15 +334,25 @@ func startServer(ln net.Listener) (net.Listener, error) {
 	return ln, nil
 }
 
+func getCertificateCacheDir() (string, error) {
+	stateDir, err := pt.MakeStateDir()
+	if err != nil {
+		return "", err
+	}
+	return filepath.Join(stateDir, "meek-certificate-cache"), nil
+}
+
 func main() {
+	var acmeHostnamesCommas string
 	var disableTLS bool
 	var certFilename, keyFilename string
 	var logFilename string
 	var port int
 
+	flag.StringVar(&acmeHostnamesCommas, "acme-hostnames", "", "comma-separated hostnames for automatic TLS certificate")
 	flag.BoolVar(&disableTLS, "disable-tls", false, "don't use HTTPS")
-	flag.StringVar(&certFilename, "cert", "", "TLS certificate file (required without --disable-tls)")
-	flag.StringVar(&keyFilename, "key", "", "TLS private key file (required without --disable-tls)")
+	flag.StringVar(&certFilename, "cert", "", "TLS certificate file")
+	flag.StringVar(&keyFilename, "key", "", "TLS private key file")
 	flag.StringVar(&logFilename, "log", "", "name of log file")
 	flag.IntVar(&port, "port", 0, "port to listen on")
 	flag.Parse()
@@ -348,31 +366,69 @@ func main() {
 		log.SetOutput(f)
 	}
 
+	var err error
+	ptInfo, err = pt.ServerSetup(nil)
+	if err != nil {
+		log.Fatalf("error in ServerSetup: %s", err)
+	}
+
 	// Handle the various ways of setting up TLS. The legal configurations
 	// are:
+	//   --acme-hostnames
 	//   --cert and --key together
 	//   --disable-tls
-	// The outputs of this block of code are the disableTLS and
-	// getCertificate variables.
+	// The outputs of this block of code are the disableTLS,
+	// missing443Listener, and getCertificate variables.
+	var missing443Listener = false
 	var getCertificate func (*tls.ClientHelloInfo) (*tls.Certificate, error)
 	if disableTLS {
-		if certFilename != "" || keyFilename != "" {
-			log.Fatalf("The --cert and --key options are not allowed with --disable-tls.\n")
+		if acmeHostnamesCommas != "" || certFilename != "" || keyFilename != "" {
+			log.Fatalf("The --acme-hostnames, --cert, and --key options are not allowed with --disable-tls.")
 		}
 	} else if certFilename != "" && keyFilename != "" {
+		if acmeHostnamesCommas != "" {
+			log.Fatalf("The --cert and --key options are not allowed with --acme-hostnames.")
+		}
 		ctx, err := newCertContext(certFilename, keyFilename)
 		if err != nil {
 			log.Fatal(err)
 		}
 		getCertificate = ctx.GetCertificate
-	} else {
-		log.Fatalf("The --cert and --key options are required.\n")
-	}
+	} else if acmeHostnamesCommas != "" {
+		acmeHostnames := strings.Split(acmeHostnamesCommas, ",")
+		log.Printf("ACME hostnames: %q", acmeHostnames)
+
+		missing443Listener = true
+		// The ACME responder only works when it is running on port 443.
+		// https://letsencrypt.github.io/acme-spec/#domain-validation-with-server-name-indication-dvsni
+		for _, bindaddr := range ptInfo.Bindaddrs {
+			if bindaddr.Addr.Port == 443 {
+				missing443Listener = false
+				break
+			}
+		}
+		// Don't quit immediately if we need a 443 listener and don't
+		// have it; do it later in the SMETHOD loop so it appears in the
+		// tor log.
+
+		var cache autocert.Cache
+		cacheDir, err := getCertificateCacheDir()
+		if err == nil {
+			log.Printf("caching ACME certificates in directory %q", cacheDir)
+			cache = autocert.DirCache(cacheDir)
+		} else {
+			log.Printf("disabling ACME certificate cache: %s", err)
+		}
 
-	var err error
-	ptInfo, err = pt.ServerSetup(nil)
-	if err != nil {
-		log.Fatalf("error in ServerSetup: %s", err)
+		certManager := &autocert.Manager{
+			Prompt:     autocert.AcceptTOS,
+			HostPolicy: autocert.HostWhitelist(acmeHostnames...),
+			// Email:      acmeEmail,
+			Cache:      cache,
+		}
+		getCertificate = certManager.GetCertificate
+	} else {
+		log.Fatalf("You must use either --acme-hostnames, or --cert and --key.")
 	}
 
 	log.Printf("starting version %s (%s)", programVersion, runtime.Version())
@@ -383,6 +439,10 @@ func main() {
 		}
 		switch bindaddr.MethodName {
 		case ptMethodName:
+			if missing443Listener {
+				pt.SmethodError(bindaddr.MethodName, "The --acme-hostnames option requires one of the bindaddrs to be on port 443.")
+				break
+			}
 			var ln net.Listener
 			if disableTLS {
 				ln, err = startListener("tcp", bindaddr.Addr)





More information about the tor-commits mailing list