Skip to content

Commit

Permalink
Add option for one time TLS keys.
Browse files Browse the repository at this point in the history
This option prevents the RPC server TLS key from ever being written to
disk.  This is performed by generating a new certificate pair each
startup and writing (possibly overwriting) the certificate but not the
key.

Closes #359.
  • Loading branch information
jrick committed Feb 11, 2016
1 parent 97963b4 commit 567752e
Show file tree
Hide file tree
Showing 3 changed files with 47 additions and 20 deletions.
1 change: 1 addition & 0 deletions config.go
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ type config struct {
// aren't considered legacy.
RPCCert string `long:"rpccert" description:"File containing the certificate file"`
RPCKey string `long:"rpckey" description:"File containing the certificate key"`
OneTimeTLSKey bool `long:"onetimetlskey" description:"Generate a new TLS certpair at startup, but only write the certificate to disk"`
DisableServerTLS bool `long:"noservertls" description:"Disable TLS for the RPC server -- NOTE: This is only allowed if the RPC server is bound to localhost"`
LegacyRPCListeners []string `long:"rpclisten" description:"Listen for legacy RPC connections on this interface/port (default port: 18332, mainnet: 8332, simnet: 18554)"`
LegacyRPCMaxClients int64 `long:"rpcmaxclients" description:"Max number of legacy RPC clients for standard connections"`
Expand Down
57 changes: 37 additions & 20 deletions rpcserver.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ package main
import (
"crypto/tls"
"errors"
"fmt"
"io/ioutil"
"net"
"os"
Expand All @@ -36,23 +37,36 @@ import (
)

// openRPCKeyPair creates or loads the RPC TLS keypair specified by the
// application config.
// application config. This function respects the cfg.OneTimeTLSKey setting.
func openRPCKeyPair() (tls.Certificate, error) {
// Check for existence of cert file and key file. Generate a new
// keypair if both are missing. If one exists but not the other, the
// error will occur in LoadX509KeyPair.
_, e1 := os.Stat(cfg.RPCKey)
_, e2 := os.Stat(cfg.RPCCert)
if os.IsNotExist(e1) && os.IsNotExist(e2) {
return generateRPCKeyPair()
// Check for existence of the TLS key file. If one time TLS keys are
// enabled but a key already exists, this function should error since
// it's possible that a persistent certificate was copied to a remote
// machine. Otherwise, generate a new keypair when the key is missing.
// When generating new persistent keys, overwriting an existing cert is
// acceptable if the previous execution used a one time TLS key.
// Otherwise, both the cert and key should be read from disk. If the
// cert is missing, the read error will occur in LoadX509KeyPair.
_, e := os.Stat(cfg.RPCKey)
keyExists := !os.IsNotExist(e)
switch {
case cfg.OneTimeTLSKey && keyExists:
err := fmt.Errorf("one time TLS keys are enabled, but TLS key "+
"`%s` already exists", cfg.RPCKey)
return tls.Certificate{}, err
case cfg.OneTimeTLSKey:
return generateRPCKeyPair(false)
case !keyExists:
return generateRPCKeyPair(true)
default:
return tls.LoadX509KeyPair(cfg.RPCCert, cfg.RPCKey)
}
return tls.LoadX509KeyPair(cfg.RPCCert, cfg.RPCKey)
}

// generateRPCKeyPair generates a new RPC TLS keypair and writes the pair in PEM
// format to the paths specified by the config. If successful, the new keypair
// is returned.
func generateRPCKeyPair() (tls.Certificate, error) {
// generateRPCKeyPair generates a new RPC TLS keypair and writes the cert and
// possibly also the key in PEM format to the paths specified by the config. If
// successful, the new keypair is returned.
func generateRPCKeyPair(writeKey bool) (tls.Certificate, error) {
log.Infof("Generating TLS certificates...")

// Create directories for cert and key files if they do not yet exist.
Expand All @@ -79,18 +93,21 @@ func generateRPCKeyPair() (tls.Certificate, error) {
return tls.Certificate{}, err
}

// Write cert and key files.
// Write cert and (potentially) the key files.
err = ioutil.WriteFile(cfg.RPCCert, cert, 0600)
if err != nil {
return tls.Certificate{}, err
}
err = ioutil.WriteFile(cfg.RPCKey, key, 0600)
if err != nil {
rmErr := os.Remove(cfg.RPCCert)
if rmErr != nil {
log.Warnf("Cannot remove written certificates: %v", rmErr)
if writeKey {
err = ioutil.WriteFile(cfg.RPCKey, key, 0600)
if err != nil {
rmErr := os.Remove(cfg.RPCCert)
if rmErr != nil {
log.Warnf("Cannot remove written certificates: %v",
rmErr)
}
return tls.Certificate{}, err
}
return tls.Certificate{}, err
}

log.Info("Done generating TLS certificates")
Expand Down
9 changes: 9 additions & 0 deletions sample-btcwallet.conf
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,15 @@
; rpccert=~/.btcwallet/rpc.cert
; rpckey=~/.btcwallet/rpc.key

; Enable one time TLS keys. This option results in the process generating
; a new certificate pair each startup, writing only the certificate file
; to disk. This is a more secure option for clients that only interact with
; a local wallet process where persistent certs are not needed.
;
; This option will error at startup if the key specified by the rpckey option
; already exists.
; onetimetlskey=0

; Specify the interfaces for the RPC server listen on. One rpclisten address
; per line. Multiple rpclisten options may be set in the same configuration,
; and each will be used to listen for connections. NOTE: The default port is
Expand Down

0 comments on commit 567752e

Please sign in to comment.