summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--proxy/snowflake.coffee4
-rw-r--r--proxy/websocket.coffee2
-rw-r--r--server/server.go111
-rw-r--r--server/torrc4
4 files changed, 89 insertions, 32 deletions
diff --git a/proxy/snowflake.coffee b/proxy/snowflake.coffee
index 1fff686..38d7d08 100644
--- a/proxy/snowflake.coffee
+++ b/proxy/snowflake.coffee
@@ -9,8 +9,8 @@ this must always act as the answerer.
###
DEFAULT_BROKER = 'snowflake-reg.appspot.com'
DEFAULT_RELAY =
- host: '192.81.135.242'
- port: 9902
+ host: 'snowflake.bamsoftware.com'
+ port: 443
COPY_PASTE_ENABLED = false
COOKIE_NAME = "snowflake-allow"
diff --git a/proxy/websocket.coffee b/proxy/websocket.coffee
index 94cf274..75c7a2f 100644
--- a/proxy/websocket.coffee
+++ b/proxy/websocket.coffee
@@ -46,7 +46,7 @@ buildUrl = (scheme, host, port, path, params) ->
parts.join ''
makeWebsocket = (addr) ->
- url = buildUrl 'ws', addr.host, addr.port, '/'
+ url = buildUrl 'wss', addr.host, addr.port, '/'
ws = new WebSocket url
###
'User agents can use this as a hint for how to handle incoming binary data: if
diff --git a/server/server.go b/server/server.go
index 8025ad3..aec9b51 100644
--- a/server/server.go
+++ b/server/server.go
@@ -19,12 +19,15 @@ import (
"net/http"
"os"
"os/signal"
+ "path/filepath"
+ "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"
@@ -39,11 +42,15 @@ var ptInfo pt.ServerInfo
var handlerChan = make(chan int)
func usage() {
- fmt.Printf("Usage: %s [OPTIONS]\n\n", os.Args[0])
- fmt.Printf("WebSocket server pluggable transport for Tor.\n")
- fmt.Printf("Works only as a managed proxy.\n")
- fmt.Printf("\n")
- fmt.Printf(" -h, -help show this help.\n")
+ fmt.Fprintf(os.Stderr, `Usage: %s [OPTIONS]
+
+WebSocket server pluggable transport for Snowflake. Works only as a managed
+proxy. Uses TLS with ACME (Let's Encrypt) by default. Set the certificate
+hostnames with the --acme-hostnames option. Use ServerTransportListenAddr in
+torrc to choose the listening port. When using TLS, if the port is not 443, this
+program will open an additional listening port on 443 to work with ACME.
+
+`, os.Args[0])
flag.PrintDefaults()
}
@@ -150,7 +157,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 +165,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 +191,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
}
@@ -216,15 +217,24 @@ 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, "snowflake-certificate-cache"), nil
+}
+
func main() {
+ var acmeEmail string
+ var acmeHostnamesCommas string
var disableTLS bool
- var certFilename, keyFilename string
var logFilename string
flag.Usage = usage
+ flag.StringVar(&acmeEmail, "acme-email", "", "optional contact email for Let's Encrypt notifications")
+ 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 +247,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 +259,41 @@ func main() {
log.Fatalf("error in setup: %s", err)
}
+ var certManager *autocert.Manager
+ if !disableTLS {
+ log.Printf("ACME hostnames: %q", acmeHostnames)
+
+ 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)
+ }
+
+ certManager = &autocert.Manager{
+ Prompt: autocert.AcceptTOS,
+ HostPolicy: autocert.HostWhitelist(acmeHostnames...),
+ Email: acmeEmail,
+ Cache: cache,
+ }
+ }
+
+ // 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 +301,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 +322,10 @@ func main() {
ln, err = startListener("tcp", bindaddr.Addr)
} else {
args.Add("tls", "yes")
- ln, err = startListenerTLS("tcp", bindaddr.Addr, certFilename, keyFilename)
+ for _, hostname := range acmeHostnames {
+ args.Add("hostname", hostname)
+ }
+ 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