236 lines
7.5 KiB
Go
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)
|
|
}
|
|
}
|