[tor-commits] [snowflake/master] Automatically fetch certificates from Let's Encrypt.

dcf at torproject.org dcf at torproject.org
Fri Mar 31 02:16:53 UTC 2017


commit 61310600c3dc419da5661dba9b11da57acd5188f
Author: David Fifield <david at bamsoftware.com>
Date:   Wed Jan 18 19:19:26 2017 -0800

    Automatically fetch certificates from Let's Encrypt.
    
    This removes the --tls-cert and --tls-keys options and replaces them
    with --acme-hostname and (optional) --acme-email. It uses
    https://godoc.org/golang.org/x/crypto/acme/autocert, which is kind of a
    successor to https://godoc.org/rsc.io/letsencrypt.
    
    The autocert package only works when the listener runs on port 443. For
    that reason, if TOR_PT_SERVER_BINDADDR asks for a port other than 443,
    the program will open an *additional* listening port on 443. If there is
    an error opening the listener, it is reported through an SMETHOD-ERROR
    for the requested address.
    
    The inspiration for this code came from George Tankersley's patch for
    meek-server:
    https://bugs.torproject.org/18655#comment:8
    https://github.com/gtank/meek/tree/letsencrypt
---
 server/server.go | 70 ++++++++++++++++++++++++++++++++++++++------------------
 server/torrc     |  4 ++--
 2 files changed, 50 insertions(+), 24 deletions(-)

diff --git a/server/server.go b/server/server.go
index 8025ad3..bedc70c 100644
--- a/server/server.go
+++ b/server/server.go
@@ -19,12 +19,14 @@ import (
 	"net/http"
 	"os"
 	"os/signal"
+	"strings"
 	"sync"
 	"syscall"
 	"time"
 
 	"git.torproject.org/pluggable-transports/goptlib.git"
 	"git.torproject.org/pluggable-transports/websocket.git/websocket"
+	"golang.org/x/crypto/acme/autocert"
 )
 
 const ptMethodName = "snowflake"
@@ -150,7 +152,7 @@ func webSocketHandler(ws *websocket.WebSocket) {
 	proxy(or, &conn)
 }
 
-func listenTLS(network string, addr *net.TCPAddr, certFilename, keyFilename string) (net.Listener, error) {
+func listenTLS(network string, addr *net.TCPAddr, m *autocert.Manager) (net.Listener, error) {
 	// 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
@@ -158,13 +160,7 @@ func listenTLS(network string, addr *net.TCPAddr, certFilename, keyFilename stri
 	// https://groups.google.com/d/msg/Golang-nuts/3F1VRCCENp8/3hcayZiwYM8J
 	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
-	}
+	config.GetCertificate = m.GetCertificate
 
 	conn, err := net.ListenTCP(network, addr)
 	if err != nil {
@@ -190,8 +186,8 @@ func startListener(network string, addr *net.TCPAddr) (net.Listener, error) {
 	return startServer(ln)
 }
 
-func startListenerTLS(network string, addr *net.TCPAddr, certFilename, keyFilename string) (net.Listener, error) {
-	ln, err := listenTLS(network, addr, certFilename, keyFilename)
+func startListenerTLS(network string, addr *net.TCPAddr, m *autocert.Manager) (net.Listener, error) {
+	ln, err := listenTLS(network, addr, m)
 	if err != nil {
 		return nil, err
 	}
@@ -217,14 +213,13 @@ func startServer(ln net.Listener) (net.Listener, error) {
 }
 
 func main() {
+	var acmeHostnamesCommas string
 	var disableTLS bool
-	var certFilename, keyFilename string
 	var logFilename string
 
 	flag.Usage = usage
+	flag.StringVar(&acmeHostnamesCommas, "acme-hostnames", "", "comma-separated hostnames for 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(&logFilename, "log", "", "log file to write to")
 	flag.Parse()
 
@@ -237,15 +232,10 @@ func main() {
 		log.SetOutput(f)
 	}
 
-	if disableTLS {
-		if certFilename != "" || keyFilename != "" {
-			log.Fatalf("the --cert and --key options are not allowed with --disable-tls")
-		}
-	} else {
-		if certFilename == "" || keyFilename == "" {
-			log.Fatalf("the --cert and --key options are required")
-		}
+	if !disableTLS && acmeHostnamesCommas == "" {
+		log.Fatal("the --acme-hostnames option is required")
 	}
+	acmeHostnames := strings.Split(acmeHostnamesCommas, ",")
 
 	log.Printf("starting")
 	var err error
@@ -254,6 +244,28 @@ func main() {
 		log.Fatalf("error in setup: %s", err)
 	}
 
+	if !disableTLS {
+		log.Printf("ACME hostnames: %q", acmeHostnames)
+	}
+	certManager := autocert.Manager{
+		Prompt:     autocert.AcceptTOS,
+		HostPolicy: autocert.HostWhitelist(acmeHostnames...),
+	}
+
+	// The ACME responder only works when it is running on port 443. In case
+	// there is not already going to be a TLS listener on port 443, we need
+	// to open an additional one. The port is actually opened in the loop
+	// below, so that any errors can be reported in the SMETHOD-ERROR of
+	// another bindaddr.
+	// https://letsencrypt.github.io/acme-spec/#domain-validation-with-server-name-indication-dvsni
+	need443Listener := !disableTLS
+	for _, bindaddr := range ptInfo.Bindaddrs {
+		if !disableTLS && bindaddr.Addr.Port == 443 {
+			need443Listener = false
+			break
+		}
+	}
+
 	listeners := make([]net.Listener, 0)
 	for _, bindaddr := range ptInfo.Bindaddrs {
 		if bindaddr.MethodName != ptMethodName {
@@ -261,6 +273,20 @@ func main() {
 			continue
 		}
 
+		if need443Listener {
+			addr := *bindaddr.Addr
+			addr.Port = 443
+			log.Printf("opening additional ACME listener on %s", addr.String())
+			ln443, err := startListenerTLS("tcp", &addr, &certManager)
+			if err != nil {
+				log.Printf("error opening ACME listener: %s", err)
+				pt.SmethodError(bindaddr.MethodName, "ACME listener: "+err.Error())
+				continue
+			}
+			listeners = append(listeners, ln443)
+			need443Listener = false
+		}
+
 		var ln net.Listener
 		args := pt.Args{}
 		if disableTLS {
@@ -268,7 +294,7 @@ func main() {
 			ln, err = startListener("tcp", bindaddr.Addr)
 		} else {
 			args.Add("tls", "yes")
-			ln, err = startListenerTLS("tcp", bindaddr.Addr, certFilename, keyFilename)
+			ln, err = startListenerTLS("tcp", bindaddr.Addr, &certManager)
 		}
 		if err != nil {
 			log.Printf("error opening listener: %s", err)
diff --git a/server/torrc b/server/torrc
index 74f6af0..ed71a39 100644
--- a/server/torrc
+++ b/server/torrc
@@ -5,5 +5,5 @@ SocksPort 0
 ExitPolicy reject *:*
 DataDirectory datadir
 
-ServerTransportListenAddr snowflake 0.0.0.0:9902
-ServerTransportPlugin snowflake exec ./server --disable-tls --log snowflake.log
+ServerTransportListenAddr snowflake 0.0.0.0:443
+ServerTransportPlugin snowflake exec ./server --acme-hostnames snowflake.example --acme-email admin at snowflake.example --log snowflake.log





More information about the tor-commits mailing list