From 5dbf69d23e48685a95ebb17a8eb2a8b89426809a Mon Sep 17 00:00:00 2001 From: Josh Rickmar Date: Tue, 19 Nov 2013 12:21:54 -0500 Subject: [PATCH] Enable TLS support for btcd websocket connections. This adds an additional config option, -cafile, to specify the root certificates checked when verifying a btcd TLC connection. btcd will now automatically generate certs in ~/.btcd/data/{main,test}net/rpc.cert, and this file should be copied to ~/.btcwallet/cert.pem. The -btcdport option is also gone now, and replaced with -connect (or -c), to specify both the hostname/ip and port of the server running btcd. --- README.md | 2 +- cmd.go | 10 +++++++++- config.go | 26 ++++++++++++++++++++++---- params.go | 3 +++ sample-btcwallet.conf | 4 ++-- sockets.go | 24 +++++++++++++++++------- 6 files changed, 54 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index cbb47fe..1c3550a 100644 --- a/README.md +++ b/README.md @@ -96,7 +96,7 @@ messages they originated from. ## TODO - Require authentication before wallet functionality can be accessed -- Serve websocket connections over TLS +- Serve frontend websocket connections over TLS - Rescan the blockchain for missed transactions - Documentation (specifically the websocket API additions) - Code cleanup diff --git a/cmd.go b/cmd.go index 689ab53..9318c99 100644 --- a/cmd.go +++ b/cmd.go @@ -25,6 +25,7 @@ import ( "github.com/conformal/btcwallet/wallet" "github.com/conformal/btcwire" "github.com/conformal/btcws" + "io/ioutil" "os" "path/filepath" "sync" @@ -312,6 +313,13 @@ func main() { log.Errorf("cannot open wallet: %v", err) } + // Read CA file to verify a btcd TLS connection. + cafile, err := ioutil.ReadFile(cfg.CAFile) + if err != nil { + log.Errorf("cannot open CA file: %v", err) + os.Exit(1) + } + // Start account disk syncer goroutine. go DirtyAccountSyncer() @@ -334,7 +342,7 @@ func main() { replies := make(chan error) done := make(chan int) go func() { - BtcdConnect(replies) + BtcdConnect(cafile, replies) close(done) }() selectLoop: diff --git a/config.go b/config.go index 7bfcb45..3b7a7d5 100644 --- a/config.go +++ b/config.go @@ -21,12 +21,14 @@ import ( "github.com/conformal/btcutil" "github.com/conformal/btcwire" "github.com/conformal/go-flags" + "net" "os" "path/filepath" "strings" ) const ( + defaultCAFilename = "cert.pem" defaultConfigFilename = "btcwallet.conf" defaultDataDirname = "data" defaultBtcNet = btcwire.TestNet3 @@ -35,13 +37,15 @@ const ( var ( btcwalletHomeDir = btcutil.AppDataDir("btcwallet", false) + defaultCAFile = filepath.Join(btcwalletHomeDir, defaultCAFilename) defaultConfigFile = filepath.Join(btcwalletHomeDir, defaultConfigFilename) defaultDataDir = btcwalletHomeDir ) type config struct { ShowVersion bool `short:"V" long:"version" description:"Display version information and exit"` - BtcdPort string `short:"b" long:"btcdport" description:"Port to connect to btcd on (default: 18334, mainnet: 18332)"` + CAFile string `long:"cafile" description:"File containing root certificates to authenticate a TLS connections with btcd"` + Connect string `short:"c" long:"connect" description:"Server and port of btcd instance to connect to"` DebugLevel string `short:"d" long:"debuglevel" description:"Logging level {trace, debug, info, warn, error, critical}"` ConfigFile string `short:"C" long:"configfile" description:"Path to configuration file"` SvrPort string `short:"p" long:"serverport" description:"Port to serve frontend websocket connections on (default: 18332, mainnet: 8332)"` @@ -56,8 +60,8 @@ type config struct { // object are the default so options specified by the user on the command line // are not overridden. func updateConfigWithActiveParams(cfg *config) { - if cfg.BtcdPort == netParams(defaultBtcNet).btcdPort { - cfg.BtcdPort = activeNetParams.btcdPort + if cfg.Connect == netParams(defaultBtcNet).connect { + cfg.Connect = activeNetParams.connect } if cfg.SvrPort == netParams(defaultBtcNet).svrPort { @@ -75,6 +79,16 @@ func fileExists(name string) bool { return true } +// normalizeAddress returns addr with the passed default port appended if +// there is not already a port specified. +func normalizeAddress(addr, defaultPort string) string { + _, _, err := net.SplitHostPort(addr) + if err != nil { + return net.JoinHostPort(addr, defaultPort) + } + return addr +} + // loadConfig initializes and parses the config using a config file and command // line options. // @@ -91,8 +105,9 @@ func loadConfig() (*config, []string, error) { // Default config. cfg := config{ DebugLevel: defaultLogLevel, + CAFile: defaultCAFile, ConfigFile: defaultConfigFile, - BtcdPort: netParams(defaultBtcNet).btcdPort, + Connect: netParams(defaultBtcNet).connect, SvrPort: netParams(defaultBtcNet).svrPort, DataDir: defaultDataDir, } @@ -161,6 +176,9 @@ func loadConfig() (*config, []string, error) { return nil, nil, err } + // Add default port to connect flag if missing. + cfg.Connect = normalizeAddress(cfg.Connect, activeNetParams.btcdPort) + return &cfg, remainingArgs, nil } diff --git a/params.go b/params.go index a1877d2..60b558f 100644 --- a/params.go +++ b/params.go @@ -25,6 +25,7 @@ var activeNetParams = netParams(defaultBtcNet) // params is used to group parameters for various networks such as the main // network and test networks. type params struct { + connect string btcdPort string svrPort string } @@ -32,6 +33,7 @@ type params struct { // mainNetParams contains parameters specific running btcwallet and // btcd on the main network (btcwire.MainNet). var mainNetParams = params{ + connect: "localhost:8334", btcdPort: "8334", svrPort: "8332", } @@ -39,6 +41,7 @@ var mainNetParams = params{ // testNet3Params contains parameters specific running btcwallet and // btcd on the test network (version 3) (btcwire.TestNet3). var testNet3Params = params{ + connect: "localhost:18334", btcdPort: "18334", svrPort: "18332", } diff --git a/sample-btcwallet.conf b/sample-btcwallet.conf index 73054a0..7b678f9 100644 --- a/sample-btcwallet.conf +++ b/sample-btcwallet.conf @@ -4,8 +4,8 @@ ; Network settings ; ------------------------------------------------------------------------------ -; The port used for btcd websocket connections. -; btcdport=18334 +; The server and port used for btcd websocket connections. +; connect=localhost:18334 ; Username and password to authenticate to btcd RPC/websocket HTTP server. ; username= diff --git a/sockets.go b/sockets.go index f748496..d5d4066 100644 --- a/sockets.go +++ b/sockets.go @@ -18,6 +18,8 @@ package main import ( "code.google.com/p/go.net/websocket" + "crypto/tls" + "crypto/x509" "encoding/base64" "encoding/json" "errors" @@ -577,22 +579,30 @@ func (s *server) Start() { // BtcdConnect connects to a running btcd instance over a websocket // for sending and receiving chain-related messages, failing if the // connection cannot be established or is lost. -func BtcdConnect(reply chan error) { - // btcd requires basic authorization, so we use a custom config with - // the Authorization header set. - server := fmt.Sprintf("ws://%s/wallet", net.JoinHostPort("localhost", cfg.BtcdPort)) - login := cfg.Username + ":" + cfg.Password - auth := "Basic " + base64.StdEncoding.EncodeToString([]byte(login)) - config, err := websocket.NewConfig(server, "http://localhost/") +func BtcdConnect(certificates []byte, reply chan error) { + url := fmt.Sprintf("wss://%s/wallet", cfg.Connect) + config, err := websocket.NewConfig(url, "https://localhost/") if err != nil { reply <- ErrConnRefused return } + + pool := x509.NewCertPool() + pool.AppendCertsFromPEM(certificates) + config.TlsConfig = &tls.Config{ + RootCAs: pool, + } + + // btcd requires basic authorization, so we use a custom config with + // the Authorization header set. + login := cfg.Username + ":" + cfg.Password + auth := "Basic" + base64.StdEncoding.EncodeToString([]byte(login)) config.Header.Add("Authorization", auth) // Attempt to connect to running btcd instance. Bail if it fails. btcdws, err := websocket.DialConfig(config) if err != nil { + log.Errorf("%s", err) reply <- ErrConnRefused return }