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.
This commit is contained in:
Josh Rickmar 2013-11-19 12:21:54 -05:00
parent 474106a757
commit 5dbf69d23e
6 changed files with 54 additions and 15 deletions

View file

@ -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

10
cmd.go
View file

@ -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:

View file

@ -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
}

View file

@ -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",
}

View file

@ -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=

View file

@ -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
}