Files
p9/cmd/wallet/rpcserver.go
Loki Verloren 0e2bba237a initial commit
2021-05-03 10:43:10 +02:00

236 lines
7.5 KiB
Go

package wallet
import (
"crypto/tls"
"errors"
"fmt"
"io/ioutil"
"net"
"os"
"path/filepath"
"runtime"
"strings"
"time"
"github.com/p9c/p9/pkg/util"
"github.com/p9c/p9/pod/config"
"github.com/p9c/p9/pod/state"
)
type listenFunc func(net string, laddr string) (net.Listener, 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(config *config.Config, writeKey bool) (tls.Certificate, error) {
D.Ln("generating TLS certificates")
// Create directories for cert and key files if they do not yet exist.
D.Ln("rpc tls ", *config.RPCCert, " ", *config.RPCKey)
certDir, _ := filepath.Split(config.RPCCert.V())
keyDir, _ := filepath.Split(config.RPCKey.V())
e := os.MkdirAll(certDir, 0700)
if e != nil {
return tls.Certificate{}, e
}
e = os.MkdirAll(keyDir, 0700)
if e != nil {
return tls.Certificate{}, e
}
// Generate cert pair.
org := "pod/wallet autogenerated cert"
validUntil := time.Now().Add(time.Hour * 24 * 365 * 10)
cert, key, e := util.NewTLSCertPair(org, validUntil, nil)
if e != nil {
return tls.Certificate{}, e
}
keyPair, e := tls.X509KeyPair(cert, key)
if e != nil {
return tls.Certificate{}, e
}
// Write cert and (potentially) the key files.
e = ioutil.WriteFile(config.RPCCert.V(), cert, 0600)
if e != nil {
rmErr := os.Remove(config.RPCCert.V())
if rmErr != nil {
E.Ln("cannot remove written certificates:", rmErr)
}
return tls.Certificate{}, e
}
e = ioutil.WriteFile(config.CAFile.V(), cert, 0600)
if e != nil {
rmErr := os.Remove(config.RPCCert.V())
if rmErr != nil {
E.Ln("cannot remove written certificates:", rmErr)
}
return tls.Certificate{}, e
}
if writeKey {
e = ioutil.WriteFile(config.RPCKey.V(), key, 0600)
if e != nil {
rmErr := os.Remove(config.RPCCert.V())
if rmErr != nil {
E.Ln("cannot remove written certificates:", rmErr)
}
rmErr = os.Remove(config.CAFile.V())
if rmErr != nil {
E.Ln("cannot remove written certificates:", rmErr)
}
return tls.Certificate{}, e
}
}
I.Ln("done generating TLS certificates")
return keyPair, nil
}
// makeListeners splits the normalized listen addresses into IPv4 and IPv6 addresses and creates new net.Listeners for
// each with the passed listen func. Invalid addresses are logged and skipped.
func makeListeners(normalizedListenAddrs []string, listen listenFunc) []net.Listener {
ipv4Addrs := make([]string, 0, len(normalizedListenAddrs)*2)
// ipv6Addrs := make([]string, 0, len(normalizedListenAddrs)*2)
for _, addr := range normalizedListenAddrs {
var host string
var e error
host, _, e = net.SplitHostPort(addr)
if e != nil {
// Shouldn't happen due to already being normalized.
E.F(
"`%s` is not a normalized listener address", addr,
)
continue
}
// Empty host or host of * on plan9 is both IPv4 and IPv6.
if host == "" || (host == "*" && runtime.GOOS == "plan9") {
ipv4Addrs = append(ipv4Addrs, addr)
// ipv6Addrs = append(ipv6Addrs, addr)
continue
}
// Remove the IPv6 zone from the host, if present. The zone prevents ParseIP from correctly parsing the IP
// address. ResolveIPAddr is intentionally not used here due to the possibility of leaking a DNS query over Tor
// if the host is a hostname and not an IP address.
zoneIndex := strings.Index(host, "%")
if zoneIndex != -1 {
host = host[:zoneIndex]
}
ip := net.ParseIP(host)
switch {
case ip == nil:
W.F("`%s` is not a valid IP address", host)
case ip.To4() == nil:
// ipv6Addrs = append(ipv6Addrs, addr)
default:
ipv4Addrs = append(ipv4Addrs, addr)
}
}
listeners := make(
[]net.Listener, 0,
// len(ipv6Addrs)+
len(ipv4Addrs),
)
for _, addr := range ipv4Addrs {
listener, e := listen("tcp4", addr)
if e != nil {
W.F(
"Can't listen on %s: %v", addr, e,
)
continue
}
listeners = append(listeners, listener)
}
// for _, addr := range ipv6Addrs {
// listener, e := listen("tcp6", addr)
// if e != nil {
// Warnf(
// "Can't listen on %s: %v", addr, e,
// )
// continue
// }
// listeners = append(listeners, listener)
// }
return listeners
}
// OpenRPCKeyPair creates or loads the RPC TLS keypair specified by the
// application config. This function respects the pod.Config.OneTimeTLSKey
// setting.
func OpenRPCKeyPair(config *config.Config) (tls.Certificate, error) {
// Chk 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(config.RPCKey.V())
keyExists := !os.IsNotExist(e)
switch {
case config.OneTimeTLSKey.True() && keyExists:
if e = fmt.Errorf(
"one time TLS keys are enabled, but TLS key `%s` already exists", config.RPCKey.V(),
); E.Chk(e) {
}
return tls.Certificate{}, e
case config.OneTimeTLSKey.True():
return GenerateRPCKeyPair(config, false)
case !keyExists:
return GenerateRPCKeyPair(config, true)
default:
return tls.LoadX509KeyPair(config.RPCCert.V(), config.RPCKey.V())
}
}
func startRPCServers(cx *state.State, walletLoader *Loader) (*Server, error) {
T.Ln("startRPCServers")
var (
legacyServer *Server
walletListen = net.Listen
keyPair tls.Certificate
e error
)
if cx.Config.ClientTLS.False() {
I.Ln("server TLS is disabled - only legacy RPC may be used")
} else {
keyPair, e = OpenRPCKeyPair(cx.Config)
if e != nil {
return nil, e
}
// Change the standard net.Listen function to the tls one.
tlsConfig := &tls.Config{
Certificates: []tls.Certificate{keyPair},
MinVersion: tls.VersionTLS12,
NextProtos: []string{"h2"}, // HTTP/2 over TLS
InsecureSkipVerify: cx.Config.TLSSkipVerify.True(),
}
walletListen = func(net string, laddr string) (net.Listener, error) {
return tls.Listen(net, laddr, tlsConfig)
}
}
if cx.Config.Username.V() == "" || cx.Config.Password.V() == "" {
I.Ln("legacy RPC server disabled (requires username and password)")
} else if len(cx.Config.WalletRPCListeners.S()) != 0 {
listeners := makeListeners(cx.Config.WalletRPCListeners.S(), walletListen)
if len(listeners) == 0 {
e := errors.New("failed to create listeners for legacy RPC server")
return nil, e
}
opts := Options{
Username: cx.Config.Username.V(),
Password: cx.Config.Password.V(),
MaxPOSTClients: int64(cx.Config.WalletRPCMaxClients.V()),
MaxWebsocketClients: int64(cx.Config.WalletRPCMaxWebsockets.V()),
}
legacyServer = NewServer(&opts, walletLoader, listeners, nil)
}
// Error when no legacy RPC servers can be started.
if legacyServer == nil {
return nil, errors.New("no suitable RPC services can be started")
}
return legacyServer, nil
}
// startWalletRPCServices associates each of the (optionally-nil) RPC servers with a wallet to enable remote wallet
// access. For the legacy JSON-RPC server it enables methods that require a loaded wallet.
func startWalletRPCServices(wallet *Wallet, legacyServer *Server) {
if legacyServer != nil {
D.Ln("starting legacy wallet rpc server")
legacyServer.RegisterWallet(wallet)
}
}