Add option for one time TLS keys.

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.
This commit is contained in:
Josh Rickmar 2016-02-11 00:00:32 -05:00
parent 97963b47ce
commit 567752ea9b
3 changed files with 47 additions and 20 deletions

View file

@ -94,6 +94,7 @@ type config struct {
// aren't considered legacy. // aren't considered legacy.
RPCCert string `long:"rpccert" description:"File containing the certificate file"` RPCCert string `long:"rpccert" description:"File containing the certificate file"`
RPCKey string `long:"rpckey" description:"File containing the certificate key"` 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"` 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)"` 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"` LegacyRPCMaxClients int64 `long:"rpcmaxclients" description:"Max number of legacy RPC clients for standard connections"`

View file

@ -19,6 +19,7 @@ package main
import ( import (
"crypto/tls" "crypto/tls"
"errors" "errors"
"fmt"
"io/ioutil" "io/ioutil"
"net" "net"
"os" "os"
@ -36,23 +37,36 @@ import (
) )
// openRPCKeyPair creates or loads the RPC TLS keypair specified by the // 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) { func openRPCKeyPair() (tls.Certificate, error) {
// Check for existence of cert file and key file. Generate a new // Check for existence of the TLS key file. If one time TLS keys are
// keypair if both are missing. If one exists but not the other, the // enabled but a key already exists, this function should error since
// error will occur in LoadX509KeyPair. // it's possible that a persistent certificate was copied to a remote
_, e1 := os.Stat(cfg.RPCKey) // machine. Otherwise, generate a new keypair when the key is missing.
_, e2 := os.Stat(cfg.RPCCert) // When generating new persistent keys, overwriting an existing cert is
if os.IsNotExist(e1) && os.IsNotExist(e2) { // acceptable if the previous execution used a one time TLS key.
return generateRPCKeyPair() // 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 // generateRPCKeyPair generates a new RPC TLS keypair and writes the cert and
// format to the paths specified by the config. If successful, the new keypair // possibly also the key in PEM format to the paths specified by the config. If
// is returned. // successful, the new keypair is returned.
func generateRPCKeyPair() (tls.Certificate, error) { func generateRPCKeyPair(writeKey bool) (tls.Certificate, error) {
log.Infof("Generating TLS certificates...") log.Infof("Generating TLS certificates...")
// Create directories for cert and key files if they do not yet exist. // Create directories for cert and key files if they do not yet exist.
@ -79,19 +93,22 @@ func generateRPCKeyPair() (tls.Certificate, error) {
return tls.Certificate{}, err return tls.Certificate{}, err
} }
// Write cert and key files. // Write cert and (potentially) the key files.
err = ioutil.WriteFile(cfg.RPCCert, cert, 0600) err = ioutil.WriteFile(cfg.RPCCert, cert, 0600)
if err != nil { if err != nil {
return tls.Certificate{}, err return tls.Certificate{}, err
} }
if writeKey {
err = ioutil.WriteFile(cfg.RPCKey, key, 0600) err = ioutil.WriteFile(cfg.RPCKey, key, 0600)
if err != nil { if err != nil {
rmErr := os.Remove(cfg.RPCCert) rmErr := os.Remove(cfg.RPCCert)
if rmErr != nil { if rmErr != nil {
log.Warnf("Cannot remove written certificates: %v", rmErr) log.Warnf("Cannot remove written certificates: %v",
rmErr)
} }
return tls.Certificate{}, err return tls.Certificate{}, err
} }
}
log.Info("Done generating TLS certificates") log.Info("Done generating TLS certificates")
return keyPair, nil return keyPair, nil

View file

@ -50,6 +50,15 @@
; rpccert=~/.btcwallet/rpc.cert ; rpccert=~/.btcwallet/rpc.cert
; rpckey=~/.btcwallet/rpc.key ; 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 ; Specify the interfaces for the RPC server listen on. One rpclisten address
; per line. Multiple rpclisten options may be set in the same configuration, ; 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 ; and each will be used to listen for connections. NOTE: The default port is