diff --git a/.gitignore b/.gitignore index 55f84c6a..8bb6d7b5 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ *.db +nodekey # Binaries for programs and plugins *.exe diff --git a/waku/node.go b/waku/node.go index 0bf643ef..0f626100 100644 --- a/waku/node.go +++ b/waku/node.go @@ -2,11 +2,12 @@ package waku import ( "context" - "crypto/rand" + "crypto/ecdsa" "database/sql" "encoding/hex" "errors" "fmt" + "io/ioutil" "net" "os" "os/signal" @@ -19,7 +20,9 @@ import ( logging "github.com/ipfs/go-log" "github.com/libp2p/go-libp2p" + libp2pcrypto "github.com/libp2p/go-libp2p-core/crypto" libp2pdisc "github.com/libp2p/go-libp2p-core/discovery" + "github.com/libp2p/go-libp2p-core/protocol" "github.com/libp2p/go-libp2p-peerstore/pstoreds" "github.com/multiformats/go-multiaddr" @@ -40,14 +43,6 @@ import ( var log = logging.Logger("wakunode") -func randomHex(n int) (string, error) { - bytes := make([]byte, n) - if _, err := rand.Read(bytes); err != nil { - return "", err - } - return hex.EncodeToString(bytes), nil -} - func failOnErr(err error, msg string) { if err != nil { if msg != "" { @@ -58,17 +53,19 @@ func failOnErr(err error, msg string) { } func Execute(options Options) { + if options.GenerateKey { + if err := writePrivateKeyToFile(options.KeyFile, options.Overwrite); err != nil { + failOnErr(err, "nodekey error") + } + return + } + hostAddr, _ := net.ResolveTCPAddr("tcp", fmt.Sprint("0.0.0.0:", options.Port)) var err error - if options.NodeKey == "" { - options.NodeKey, err = randomHex(32) - failOnErr(err, "could not generate random key") - } - - prvKey, err := crypto.HexToECDSA(options.NodeKey) - failOnErr(err, "error converting key into valid ecdsa key") + prvKey, err := getPrivKey(options) + failOnErr(err, "nodekey error") if options.DBPath == "" && options.UseDB { failOnErr(errors.New("dbpath can't be null"), "") @@ -245,3 +242,95 @@ func addPeers(wakuNode *node.WakuNode, addresses []string, protocol protocol.ID) failOnErr(err, "error adding peer") } } + +func loadPrivateKeyFromFile(path string) (*ecdsa.PrivateKey, error) { + src, err := ioutil.ReadFile(path) + if err != nil { + return nil, err + } + dst := make([]byte, hex.DecodedLen(len(src))) + _, err = hex.Decode(dst, src) + if err != nil { + return nil, err + } + + p, err := libp2pcrypto.UnmarshalSecp256k1PrivateKey(dst) + if err != nil { + return nil, err + } + + privKey := (*ecdsa.PrivateKey)(p.(*libp2pcrypto.Secp256k1PrivateKey)) + privKey.Curve = crypto.S256() + + return privKey, nil +} + +func writePrivateKeyToFile(path string, force bool) error { + _, err := os.Stat(path) + + if err == nil && !force { + return fmt.Errorf("%s already exists. Use --overwrite to overwrite the file", path) + } + + if err := os.Remove(path); err != nil && !os.IsNotExist(err) { + return err + } + + key, err := crypto.GenerateKey() + if err != nil { + return err + } + + privKey := libp2pcrypto.PrivKey((*libp2pcrypto.Secp256k1PrivateKey)(key)) + + b, err := privKey.Raw() + if err != nil { + return err + } + + output := make([]byte, hex.EncodedLen(len(b))) + hex.Encode(output, b) + + return ioutil.WriteFile(path, output, 0600) +} + +func getPrivKey(options Options) (*ecdsa.PrivateKey, error) { + var prvKey *ecdsa.PrivateKey + var err error + if options.KeyFile != "" { + prvKey, err = loadPrivateKeyFromFile(options.KeyFile) + if err != nil { + return nil, fmt.Errorf("could not read keyfile: %w", err) + } + } else if options.NodeKey != "" { + prvKey, err = crypto.HexToECDSA(options.NodeKey) + if err != nil { + return nil, fmt.Errorf("error converting key into valid ecdsa key: %w", err) + } + } else { + keyString := os.Getenv("GOWAKU-NODEKEY") + if keyString != "" { + prvKey, err = crypto.HexToECDSA(keyString) + if err != nil { + return nil, fmt.Errorf("error converting key into valid ecdsa key: %w", err) + } + } else { + if _, err := os.Stat(options.KeyFile); err == nil { + prvKey, err = loadPrivateKeyFromFile(options.KeyFile) + if err != nil { + return nil, fmt.Errorf("could not read keyfile: %w", err) + } + } else { + if os.IsNotExist(err) { + prvKey, err = crypto.GenerateKey() + if err != nil { + return nil, fmt.Errorf("error generating key: %w", err) + } + } else { + return nil, fmt.Errorf("could not read keyfile: %w", err) + } + } + } + } + return prvKey, nil +} diff --git a/waku/options.go b/waku/options.go index cdd9f986..7571706e 100644 --- a/waku/options.go +++ b/waku/options.go @@ -47,7 +47,10 @@ type Options struct { Port int `short:"p" long:"port" description:"Libp2p TCP listening port (0 for random)" default:"9000"` EnableWS bool `long:"ws" description:"Enable websockets support"` WSPort int `long:"ws-port" description:"Libp2p TCP listening port for websocket connection (0 for random)" default:"9001"` - NodeKey string `long:"nodekey" description:"P2P node private key as hex (UNSAFE!, default random)"` + NodeKey string `long:"nodekey" description:"P2P node private key as hex. Can also be set with GOWAKU-NODEKEY env variable (default random)"` + KeyFile string `long:"key-file" description:"Path to a file containing the private key for the P2P node" default:"./nodekey"` + GenerateKey bool `long:"generate-key" description:"Generate private key file at path specified in --key-file"` + Overwrite bool `long:"overwrite" description:"When generating a keyfile, overwrite the nodekey file if it already exists"` StaticNodes []string `long:"staticnodes" description:"Multiaddr of peer to directly connect with. Argument may be repeated"` KeepAlive int `long:"keep-alive" default:"300" description:"Interval in seconds for pinging peers to keep the connection alive."` UseDB bool `long:"use-db" description:"Use SQLiteDB to persist information"`