commit 61310600c3dc419da5661dba9b11da57acd5188f Author: David Fifield david@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-... + 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@snowflake.example --log snowflake.log
tor-commits@lists.torproject.org