Commit 4b643f4b authored by Janos Guljas's avatar Janos Guljas

add keystore to manage libp2p and swarm private keys

parent a5467202
...@@ -19,10 +19,11 @@ func init() { ...@@ -19,10 +19,11 @@ func init() {
} }
type command struct { type command struct {
root *cobra.Command root *cobra.Command
config *viper.Viper config *viper.Viper
cfgFile string passwordReader passwordReader
homeDir string cfgFile string
homeDir string
} }
type option func(*command) type option func(*command)
...@@ -43,6 +44,9 @@ func newCommand(opts ...option) (c *command, err error) { ...@@ -43,6 +44,9 @@ func newCommand(opts ...option) (c *command, err error) {
for _, o := range opts { for _, o := range opts {
o(c) o(c)
} }
if c.passwordReader == nil {
c.passwordReader = new(stdInPasswordReader)
}
// Find home directory. // Find home directory.
if err := c.setHomeDir(); err != nil { if err := c.setHomeDir(); err != nil {
......
...@@ -7,8 +7,9 @@ package cmd ...@@ -7,8 +7,9 @@ package cmd
import "io" import "io"
type ( type (
Command = command Command = command
Option = option Option = option
PasswordReader = passwordReader
) )
var ( var (
...@@ -18,6 +19,7 @@ var ( ...@@ -18,6 +19,7 @@ var (
_ = WithCfgFile _ = WithCfgFile
_ = WithInput _ = WithInput
_ = WithErrorOutput _ = WithErrorOutput
_ = WithPasswordReader
) )
func WithCfgFile(f string) func(c *Command) { func WithCfgFile(f string) func(c *Command) {
...@@ -55,3 +57,9 @@ func WithErrorOutput(w io.Writer) func(c *Command) { ...@@ -55,3 +57,9 @@ func WithErrorOutput(w io.Writer) func(c *Command) {
c.root.SetErr(w) c.root.SetErr(w)
} }
} }
func WithPasswordReader(r PasswordReader) func(c *Command) {
return func(c *Command) {
c.passwordReader = r
}
}
...@@ -5,9 +5,9 @@ ...@@ -5,9 +5,9 @@
package cmd package cmd
import ( import (
"bytes"
"context" "context"
"fmt" "fmt"
"io"
"io/ioutil" "io/ioutil"
"os" "os"
"os/signal" "os/signal"
...@@ -28,6 +28,8 @@ func (c *command) initStartCmd() (err error) { ...@@ -28,6 +28,8 @@ func (c *command) initStartCmd() (err error) {
const ( const (
optionNameDataDir = "data-dir" optionNameDataDir = "data-dir"
optionNamePassword = "password"
optionNamePasswordFile = "password-file"
optionNameAPIAddr = "api-addr" optionNameAPIAddr = "api-addr"
optionNameP2PAddr = "p2p-addr" optionNameP2PAddr = "p2p-addr"
optionNameP2PDisableWS = "p2p-disable-ws" optionNameP2PDisableWS = "p2p-disable-ws"
...@@ -68,34 +70,34 @@ func (c *command) initStartCmd() (err error) { ...@@ -68,34 +70,34 @@ func (c *command) initStartCmd() (err error) {
return fmt.Errorf("unknown verbosity level %q", v) return fmt.Errorf("unknown verbosity level %q", v)
} }
var libp2pPrivateKey, swarmPrivateKey io.ReadWriteCloser debugAPIAddr := c.config.GetString(optionNameDebugAPIAddr)
if dataDir := c.config.GetString(optionNameDataDir); dataDir != "" { if !c.config.GetBool(optionNameEnableDebugAPI) {
if err := os.MkdirAll(dataDir, os.ModePerm); err != nil { debugAPIAddr = ""
return err }
}
libp2pKey, err := os.OpenFile(filepath.Join(dataDir, "libp2p.key"), os.O_CREATE|os.O_RDWR, 0600) var password string
if p := c.config.GetString(optionNamePassword); p != "" {
password = p
} else if pf := c.config.GetString(optionNamePasswordFile); pf != "" {
b, err := ioutil.ReadFile(pf)
if err != nil { if err != nil {
return err return err
} }
libp2pPrivateKey = libp2pKey password = string(bytes.Trim(b, "\n"))
swarmKey, err := os.OpenFile(filepath.Join(dataDir, "swarm.key"), os.O_CREATE|os.O_RDWR, 0600) } else {
p, err := terminalPromptPassword(cmd, c.passwordReader, "Password")
if err != nil { if err != nil {
return err return err
} }
swarmPrivateKey = swarmKey password = p
}
debugAPIAddr := c.config.GetString(optionNameDebugAPIAddr)
if !c.config.GetBool(optionNameEnableDebugAPI) {
debugAPIAddr = ""
} }
b, err := node.NewBee(node.Options{ b, err := node.NewBee(node.Options{
PrivateKey: swarmPrivateKey, DataDir: c.config.GetString(optionNameDataDir),
Password: password,
APIAddr: c.config.GetString(optionNameAPIAddr), APIAddr: c.config.GetString(optionNameAPIAddr),
DebugAPIAddr: debugAPIAddr, DebugAPIAddr: debugAPIAddr,
LibP2POptions: libp2p.Options{ LibP2POptions: libp2p.Options{
PrivateKey: libp2pPrivateKey,
Addr: c.config.GetString(optionNameP2PAddr), Addr: c.config.GetString(optionNameP2PAddr),
DisableWS: c.config.GetBool(optionNameP2PDisableWS), DisableWS: c.config.GetBool(optionNameP2PDisableWS),
DisableQUIC: c.config.GetBool(optionNameP2PDisableQUIC), DisableQUIC: c.config.GetBool(optionNameP2PDisableQUIC),
...@@ -152,6 +154,8 @@ func (c *command) initStartCmd() (err error) { ...@@ -152,6 +154,8 @@ func (c *command) initStartCmd() (err error) {
} }
cmd.Flags().String(optionNameDataDir, filepath.Join(c.homeDir, ".bee"), "data directory") cmd.Flags().String(optionNameDataDir, filepath.Join(c.homeDir, ".bee"), "data directory")
cmd.Flags().String(optionNamePassword, "", "password for decrypting keys")
cmd.Flags().String(optionNamePasswordFile, "", "path to a file that contains password for decrypting keys")
cmd.Flags().String(optionNameAPIAddr, ":8080", "HTTP API listen address") cmd.Flags().String(optionNameAPIAddr, ":8080", "HTTP API listen address")
cmd.Flags().String(optionNameP2PAddr, ":7070", "P2P listen address") cmd.Flags().String(optionNameP2PAddr, ":7070", "P2P listen address")
cmd.Flags().Bool(optionNameP2PDisableWS, false, "disable P2P WebSocket protocol") cmd.Flags().Bool(optionNameP2PDisableWS, false, "disable P2P WebSocket protocol")
......
// Copyright 2020 The Swarm Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package cmd
import (
"os"
"github.com/spf13/cobra"
"golang.org/x/crypto/ssh/terminal"
)
type passwordReader interface {
ReadPassword() (password string, err error)
}
type stdInPasswordReader struct{}
func (stdInPasswordReader) ReadPassword() (password string, err error) {
v, err := terminal.ReadPassword(int(os.Stdin.Fd()))
if err != nil {
return "", err
}
return string(v), err
}
func terminalPromptPassword(cmd *cobra.Command, r passwordReader, title string) (password string, err error) {
cmd.Print(title + ": ")
password, err = r.ReadPassword()
cmd.Println()
if err != nil {
return "", err
}
return password, nil
}
...@@ -8,7 +8,6 @@ import ( ...@@ -8,7 +8,6 @@ import (
"crypto/ecdsa" "crypto/ecdsa"
"crypto/elliptic" "crypto/elliptic"
"crypto/rand" "crypto/rand"
"encoding/json"
"fmt" "fmt"
"github.com/btcsuite/btcd/btcec" "github.com/btcsuite/btcd/btcec"
...@@ -16,51 +15,25 @@ import ( ...@@ -16,51 +15,25 @@ import (
"golang.org/x/crypto/sha3" "golang.org/x/crypto/sha3"
) )
var keyTypeSecp256k1 = "secp256k1"
// GenerateSecp256k1Key generates an ECDSA private key using
// secp256k1 elliptic curve.
func GenerateSecp256k1Key() (*ecdsa.PrivateKey, error) {
return ecdsa.GenerateKey(btcec.S256(), rand.Reader)
}
// NewAddress constructs a Swarm Address from ECDSA private key. // NewAddress constructs a Swarm Address from ECDSA private key.
func NewAddress(p ecdsa.PublicKey) swarm.Address { func NewAddress(p ecdsa.PublicKey) swarm.Address {
h := sha3.Sum256(elliptic.Marshal(btcec.S256(), p.X, p.Y)) h := sha3.Sum256(elliptic.Marshal(btcec.S256(), p.X, p.Y))
return swarm.NewAddress(h[:]) return swarm.NewAddress(h[:])
} }
// privateKey holds information about Swarm private key for marshaling. // GenerateSecp256k1Key generates an ECDSA private key using
type privateKey struct { // secp256k1 elliptic curve.
Type string `json:"type"` func GenerateSecp256k1Key() (*ecdsa.PrivateKey, error) {
Key []byte `json:"key"` return ecdsa.GenerateKey(btcec.S256(), rand.Reader)
}
// MarshalSecp256k1PrivateKey marshals secp256k1 ECDSA private key
// that can be unmarshaled by UnmarshalPrivateKey.
func MarshalSecp256k1PrivateKey(k *ecdsa.PrivateKey) ([]byte, error) {
return json.Marshal(privateKey{
Type: keyTypeSecp256k1,
Key: (*btcec.PrivateKey)(k).Serialize(),
})
} }
// UnmarshalPrivateKey unmarshals ECDSA private key from encoded data. // EncodeSecp256k1PrivateKey encodes raw ECDSA private key.
func UnmarshalPrivateKey(data []byte) (*ecdsa.PrivateKey, error) { func EncodeSecp256k1PrivateKey(k *ecdsa.PrivateKey) []byte {
var pk privateKey return (*btcec.PrivateKey)(k).Serialize()
if err := json.Unmarshal(data, &pk); err != nil {
return nil, err
}
switch t := pk.Type; t {
case keyTypeSecp256k1:
return decodeSecp256k1PrivateKey(pk.Key)
default:
return nil, fmt.Errorf("unknown key type %q", t)
}
} }
// decodeSecp256k1PrivateKey decodes raw ECDSA private key. // DecodeSecp256k1PrivateKey decodes raw ECDSA private key.
func decodeSecp256k1PrivateKey(data []byte) (*ecdsa.PrivateKey, error) { func DecodeSecp256k1PrivateKey(data []byte) (*ecdsa.PrivateKey, error) {
if l := len(data); l != btcec.PrivKeyBytesLen { if l := len(data); l != btcec.PrivKeyBytesLen {
return nil, fmt.Errorf("secp256k1 data size %d expected %d", l, btcec.PrivKeyBytesLen) return nil, fmt.Errorf("secp256k1 data size %d expected %d", l, btcec.PrivKeyBytesLen)
} }
......
...@@ -43,20 +43,17 @@ func TestNewAddress(t *testing.T) { ...@@ -43,20 +43,17 @@ func TestNewAddress(t *testing.T) {
} }
} }
func TestMarshalSecp256k1PrivateKey(t *testing.T) { func TestEncodeSecp256k1PrivateKey(t *testing.T) {
k1, err := crypto.GenerateSecp256k1Key() k1, err := crypto.GenerateSecp256k1Key()
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
d, err := crypto.MarshalSecp256k1PrivateKey(k1) d := crypto.EncodeSecp256k1PrivateKey(k1)
if err != nil { k2, err := crypto.DecodeSecp256k1PrivateKey(d)
t.Fatal(err)
}
k2, err := crypto.UnmarshalPrivateKey(d)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
if !bytes.Equal(k1.D.Bytes(), k2.D.Bytes()) { if !bytes.Equal(k1.D.Bytes(), k2.D.Bytes()) {
t.Fatal("marshaled and unmarshaled keys are not equal") t.Fatal("encoded and decoded keys are not equal")
} }
} }
// Copyright 2020 The Swarm Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package file
import (
"bytes"
"crypto/aes"
"crypto/cipher"
"crypto/ecdsa"
"crypto/rand"
"encoding/hex"
"encoding/json"
"fmt"
"io"
"github.com/ethersphere/bee/pkg/crypto"
"github.com/ethersphere/bee/pkg/keystore"
"github.com/ethersphere/bee/pkg/swarm"
"golang.org/x/crypto/scrypt"
"golang.org/x/crypto/sha3"
)
var _ keystore.Service = (*Service)(nil)
const (
keyHeaderKDF = "scrypt"
keyVersion = 1
scryptN = 1 << 15
scryptR = 8
scryptP = 1
scryptDKLen = 32
)
type encryptedKey struct {
Address swarm.Address `json:"address"`
Crypto keyCripto `json:"crypto"`
Version int `json:"version"`
}
type keyCripto struct {
Cipher string `json:"cipher"`
CipherText string `json:"ciphertext"`
CipherParams cipherParams `json:"cipherparams"`
KDF string `json:"kdf"`
KDFParams kdfParams `json:"kdfparams"`
MAC string `json:"mac"`
}
type cipherParams struct {
IV string `json:"iv"`
}
type kdfParams struct {
N int `json:"n"`
R int `json:"r"`
P int `json:"p"`
DKLen int `json:"dklen"`
Salt string `json:"salt"`
}
func encryptKey(k *ecdsa.PrivateKey, password string) ([]byte, error) {
data := crypto.EncodeSecp256k1PrivateKey(k)
kc, err := encryptData(data, []byte(password))
if err != nil {
return nil, err
}
return json.Marshal(encryptedKey{
Address: crypto.NewAddress(k.PublicKey),
Crypto: *kc,
Version: keyVersion,
})
}
func decryptKey(data []byte, password string) (*ecdsa.PrivateKey, error) {
var k encryptedKey
if err := json.Unmarshal(data, &k); err != nil {
return nil, err
}
if k.Version != keyVersion {
return nil, fmt.Errorf("unsupported key version: %v", k.Version)
}
d, err := decryptData(k.Crypto, password)
if err != nil {
return nil, err
}
return crypto.DecodeSecp256k1PrivateKey(d)
}
func encryptData(data, password []byte) (*keyCripto, error) {
salt := make([]byte, 32)
if _, err := io.ReadFull(rand.Reader, salt); err != nil {
return nil, fmt.Errorf("read random data: %w", err)
}
derivedKey, err := scrypt.Key(password, salt, scryptN, scryptR, scryptP, scryptDKLen)
if err != nil {
return nil, err
}
encryptKey := derivedKey[:16]
iv := make([]byte, aes.BlockSize)
if _, err := io.ReadFull(rand.Reader, iv); err != nil {
return nil, fmt.Errorf("read random data: %w", err)
}
cipherText, err := aesCTRXOR(encryptKey, data, iv)
if err != nil {
return nil, err
}
mac := sha3.Sum256(append(derivedKey[16:32], cipherText...))
return &keyCripto{
Cipher: "aes-128-ctr",
CipherText: hex.EncodeToString(cipherText),
CipherParams: cipherParams{
IV: hex.EncodeToString(iv),
},
KDF: keyHeaderKDF,
KDFParams: kdfParams{
N: scryptN,
R: scryptR,
P: scryptP,
DKLen: scryptDKLen,
Salt: hex.EncodeToString(salt),
},
MAC: hex.EncodeToString(mac[:]),
}, nil
}
func decryptData(v keyCripto, password string) ([]byte, error) {
if v.Cipher != "aes-128-ctr" {
return nil, fmt.Errorf("unsupported cipher: %v", v.Cipher)
}
mac, err := hex.DecodeString(v.MAC)
if err != nil {
return nil, fmt.Errorf("hex decode mac: %s", err)
}
cipherText, err := hex.DecodeString(v.CipherText)
if err != nil {
return nil, fmt.Errorf("hex decode cipher text: %s", err)
}
derivedKey, err := getKDFKey(v, []byte(password))
if err != nil {
return nil, err
}
calculatedMAC := sha3.Sum256(append(derivedKey[16:32], cipherText...))
if !bytes.Equal(calculatedMAC[:], mac) {
return nil, keystore.ErrInvalidPassword
}
iv, err := hex.DecodeString(v.CipherParams.IV)
if err != nil {
return nil, fmt.Errorf("hex decode IV cipher parameter: %s", err)
}
data, err := aesCTRXOR(derivedKey[:16], cipherText, iv)
if err != nil {
return nil, err
}
return data, nil
}
func aesCTRXOR(key, inText, iv []byte) ([]byte, error) {
aesBlock, err := aes.NewCipher(key)
if err != nil {
return nil, err
}
stream := cipher.NewCTR(aesBlock, iv)
outText := make([]byte, len(inText))
stream.XORKeyStream(outText, inText)
return outText, nil
}
func getKDFKey(v keyCripto, password []byte) ([]byte, error) {
if v.KDF != keyHeaderKDF {
return nil, fmt.Errorf("unsupported KDF: %s", v.KDF)
}
salt, err := hex.DecodeString(v.KDFParams.Salt)
if err != nil {
return nil, fmt.Errorf("hex decode salt: %s", err)
}
return scrypt.Key(
password,
salt,
v.KDFParams.N,
v.KDFParams.R,
v.KDFParams.P,
v.KDFParams.DKLen,
)
}
// Copyright 2020 The Swarm Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package file
import (
"crypto/ecdsa"
"fmt"
"io/ioutil"
"os"
"path/filepath"
"github.com/ethersphere/bee/pkg/crypto"
)
type Service struct {
dir string
}
func New(dir string) *Service {
return &Service{dir: dir}
}
func (s *Service) Key(name, password string) (pk *ecdsa.PrivateKey, created bool, err error) {
filename := s.keyFilename(name)
data, err := ioutil.ReadFile(filename)
if err != nil && !os.IsNotExist(err) {
return nil, false, fmt.Errorf("read private key: %w", err)
}
if len(data) == 0 {
var err error
pk, err = crypto.GenerateSecp256k1Key()
if err != nil {
return nil, false, fmt.Errorf("generate secp256k1 key: %w", err)
}
d, err := encryptKey(pk, password)
if err != nil {
return nil, false, err
}
if err := os.MkdirAll(filepath.Dir(filename), 0700); err != nil {
return nil, false, err
}
if err := ioutil.WriteFile(filename, d, 0600); err != nil {
return nil, false, err
}
return pk, true, nil
}
pk, err = decryptKey(data, password)
if err != nil {
return nil, false, err
}
return pk, false, nil
}
func (s *Service) keyFilename(name string) string {
return filepath.Join(s.dir, fmt.Sprintf("%s.key", name))
}
// Copyright 2020 The Swarm Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package file_test
import (
"io/ioutil"
"os"
"testing"
"github.com/ethersphere/bee/pkg/keystore/file"
"github.com/ethersphere/bee/pkg/keystore/test"
)
func TestService(t *testing.T) {
dir, err := ioutil.TempDir("", "bzz-keystore-file-")
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(dir)
test.Service(t, file.New(dir))
}
// Copyright 2020 The Swarm Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package keystore
import (
"crypto/ecdsa"
"errors"
)
var ErrInvalidPassword = errors.New("invalid password")
type Service interface {
Key(name, password string) (k *ecdsa.PrivateKey, created bool, err error)
}
// Copyright 2020 The Swarm Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package mem
import (
"crypto/ecdsa"
"fmt"
"sync"
"github.com/ethersphere/bee/pkg/crypto"
"github.com/ethersphere/bee/pkg/keystore"
)
var _ keystore.Service = (*Service)(nil)
type Service struct {
m map[string]key
mu sync.Mutex
}
func New() *Service {
return &Service{
m: make(map[string]key),
}
}
func (s *Service) Key(name, password string) (pk *ecdsa.PrivateKey, created bool, err error) {
s.mu.Lock()
defer s.mu.Unlock()
k, ok := s.m[name]
if !ok {
pk, err := crypto.GenerateSecp256k1Key()
if err != nil {
return nil, false, fmt.Errorf("generate secp256k1 key: %w", err)
}
s.m[name] = key{
pk: pk,
password: password,
}
return pk, true, nil
}
if k.password != password {
return nil, false, keystore.ErrInvalidPassword
}
return k.pk, created, nil
}
type key struct {
pk *ecdsa.PrivateKey
password string
}
// Copyright 2020 The Swarm Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package mem_test
import (
"testing"
"github.com/ethersphere/bee/pkg/keystore/mem"
"github.com/ethersphere/bee/pkg/keystore/test"
)
func TestService(t *testing.T) {
test.Service(t, mem.New())
}
// Copyright 2020 The Swarm Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package test
import (
"bytes"
"errors"
"testing"
"github.com/ethersphere/bee/pkg/keystore"
)
func Service(t *testing.T, s keystore.Service) {
// create a new swarm key
k1, created, err := s.Key("swarm", "pass123456")
if err != nil {
t.Fatal(err)
}
if !created {
t.Fatal("key is not created")
}
// get swarm key
k2, created, err := s.Key("swarm", "pass123456")
if err != nil {
t.Fatal(err)
}
if created {
t.Fatal("key is created, but should not be")
}
if !bytes.Equal(k1.D.Bytes(), k2.D.Bytes()) {
t.Fatal("two keys are not equal")
}
// invalid password
_, _, err = s.Key("swarm", "invalid password")
if !errors.Is(err, keystore.ErrInvalidPassword) {
t.Fatal(err)
}
// create a new libp2p key
k3, created, err := s.Key("libp2p", "p2p pass")
if err != nil {
t.Fatal(err)
}
if !created {
t.Fatal("key is not created")
}
if bytes.Equal(k1.D.Bytes(), k3.D.Bytes()) {
t.Fatal("two keys are equal, but should not be")
}
// get libp2p key
k4, created, err := s.Key("libp2p", "p2p pass")
if err != nil {
t.Fatal(err)
}
if created {
t.Fatal("key is created, but should not be")
}
if !bytes.Equal(k3.D.Bytes(), k4.D.Bytes()) {
t.Fatal("two keys are not equal")
}
}
...@@ -5,16 +5,13 @@ ...@@ -5,16 +5,13 @@
package node package node
import ( import (
"bytes"
"context" "context"
"crypto/ecdsa"
"fmt" "fmt"
"io" "io"
"io/ioutil"
"log" "log"
"net" "net"
"net/http" "net/http"
"os" "path/filepath"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
"golang.org/x/sync/errgroup" "golang.org/x/sync/errgroup"
...@@ -22,6 +19,9 @@ import ( ...@@ -22,6 +19,9 @@ import (
"github.com/ethersphere/bee/pkg/api" "github.com/ethersphere/bee/pkg/api"
"github.com/ethersphere/bee/pkg/crypto" "github.com/ethersphere/bee/pkg/crypto"
"github.com/ethersphere/bee/pkg/debugapi" "github.com/ethersphere/bee/pkg/debugapi"
"github.com/ethersphere/bee/pkg/keystore"
filekeystore "github.com/ethersphere/bee/pkg/keystore/file"
memkeystore "github.com/ethersphere/bee/pkg/keystore/mem"
"github.com/ethersphere/bee/pkg/logging" "github.com/ethersphere/bee/pkg/logging"
"github.com/ethersphere/bee/pkg/metrics" "github.com/ethersphere/bee/pkg/metrics"
"github.com/ethersphere/bee/pkg/p2p/libp2p" "github.com/ethersphere/bee/pkg/p2p/libp2p"
...@@ -37,7 +37,8 @@ type Bee struct { ...@@ -37,7 +37,8 @@ type Bee struct {
} }
type Options struct { type Options struct {
PrivateKey io.ReadWriteCloser DataDir string
Password string
APIAddr string APIAddr string
DebugAPIAddr string DebugAPIAddr string
LibP2POptions libp2p.Options LibP2POptions libp2p.Options
...@@ -54,49 +55,37 @@ func NewBee(o Options) (*Bee, error) { ...@@ -54,49 +55,37 @@ func NewBee(o Options) (*Bee, error) {
errorLogWriter: logger.WriterLevel(logrus.ErrorLevel), errorLogWriter: logger.WriterLevel(logrus.ErrorLevel),
} }
var privateKey *ecdsa.PrivateKey var keyStore keystore.Service
if o.PrivateKey != nil { if o.DataDir == "" {
privateKeyData, err := ioutil.ReadAll(o.PrivateKey) keyStore = memkeystore.New()
if err != nil && !os.IsNotExist(err) { logger.Warning("data directory not provided, keys are not persisted")
return nil, fmt.Errorf("read private key: %w", err)
}
if len(privateKeyData) == 0 {
var err error
privateKey, err = crypto.GenerateSecp256k1Key()
if err != nil {
return nil, fmt.Errorf("generate secp256k1 key: %w", err)
}
d, err := crypto.MarshalSecp256k1PrivateKey(privateKey)
if err != nil {
return nil, fmt.Errorf("encode private key: %w", err)
}
if _, err := io.Copy(o.PrivateKey, bytes.NewReader(d)); err != nil {
return nil, fmt.Errorf("write private key: %w", err)
}
} else {
var err error
privateKey, err = crypto.UnmarshalPrivateKey(privateKeyData)
if err != nil {
return nil, fmt.Errorf("decode private key: %w", err)
}
}
if err := o.PrivateKey.Close(); err != nil {
return nil, fmt.Errorf("close private key: %w", err)
}
} else { } else {
var err error keyStore = filekeystore.New(filepath.Join(o.DataDir, "keys"))
privateKey, err = crypto.GenerateSecp256k1Key() }
if err != nil {
return nil, fmt.Errorf("generate secp256k1 key: %w", err) swarmPrivateKey, created, err := keyStore.Key("swarm", o.Password)
} if err != nil {
return nil, fmt.Errorf("swarm key: %w", err)
}
address := crypto.NewAddress(swarmPrivateKey.PublicKey)
if created {
logger.Info("new swarm key created")
} }
address := crypto.NewAddress(privateKey.PublicKey)
logger.Infof("address: %s", address) logger.Infof("address: %s", address)
// Construct P2P service. // Construct P2P service.
libp2pPrivateKey, created, err := keyStore.Key("libp2p", o.Password)
if err != nil {
return nil, fmt.Errorf("libp2p key: %w", err)
}
if created {
logger.Infof("new libp2p key created")
}
libP2POptions := o.LibP2POptions libP2POptions := o.LibP2POptions
libP2POptions.Overlay = address libP2POptions.Overlay = address
libP2POptions.PrivateKey = libp2pPrivateKey
p2ps, err := libp2p.New(p2pCtx, libP2POptions) p2ps, err := libp2p.New(p2pCtx, libP2POptions)
if err != nil { if err != nil {
return nil, fmt.Errorf("p2p service: %w", err) return nil, fmt.Errorf("p2p service: %w", err)
......
...@@ -5,14 +5,11 @@ ...@@ -5,14 +5,11 @@
package libp2p package libp2p
import ( import (
"bytes"
"context" "context"
"crypto/ecdsa"
"errors" "errors"
"fmt" "fmt"
"io"
"io/ioutil"
"net" "net"
"os"
"time" "time"
"github.com/ethersphere/bee/pkg/logging" "github.com/ethersphere/bee/pkg/logging"
...@@ -22,7 +19,7 @@ import ( ...@@ -22,7 +19,7 @@ import (
"github.com/libp2p/go-libp2p" "github.com/libp2p/go-libp2p"
autonat "github.com/libp2p/go-libp2p-autonat-svc" autonat "github.com/libp2p/go-libp2p-autonat-svc"
connmgr "github.com/libp2p/go-libp2p-connmgr" connmgr "github.com/libp2p/go-libp2p-connmgr"
"github.com/libp2p/go-libp2p-core/crypto" crypto "github.com/libp2p/go-libp2p-core/crypto"
"github.com/libp2p/go-libp2p-core/helpers" "github.com/libp2p/go-libp2p-core/helpers"
"github.com/libp2p/go-libp2p-core/host" "github.com/libp2p/go-libp2p-core/host"
"github.com/libp2p/go-libp2p-core/network" "github.com/libp2p/go-libp2p-core/network"
...@@ -47,7 +44,7 @@ type Service struct { ...@@ -47,7 +44,7 @@ type Service struct {
} }
type Options struct { type Options struct {
PrivateKey io.ReadWriteCloser PrivateKey *ecdsa.PrivateKey
Overlay swarm.Address Overlay swarm.Address
Addr string Addr string
DisableWS bool DisableWS bool
...@@ -122,37 +119,8 @@ func New(ctx context.Context, o Options) (*Service, error) { ...@@ -122,37 +119,8 @@ func New(ctx context.Context, o Options) (*Service, error) {
} }
if o.PrivateKey != nil { if o.PrivateKey != nil {
var privateKey crypto.PrivKey
privateKeyData, err := ioutil.ReadAll(o.PrivateKey)
if err != nil && !os.IsNotExist(err) {
return nil, fmt.Errorf("read private key: %w", err)
}
if len(privateKeyData) == 0 {
var err error
privateKey, _, err = crypto.GenerateSecp256k1Key(nil)
if err != nil {
return nil, fmt.Errorf("generate secp256k1 key: %w", err)
}
d, err := crypto.MarshalPrivateKey(privateKey)
if err != nil {
return nil, fmt.Errorf("encode private key: %w", err)
}
if _, err := io.Copy(o.PrivateKey, bytes.NewReader(d)); err != nil {
return nil, fmt.Errorf("write private key: %w", err)
}
} else {
var err error
privateKey, err = crypto.UnmarshalPrivateKey(privateKeyData)
if err != nil {
return nil, fmt.Errorf("decode private key: %w", err)
}
}
if err := o.PrivateKey.Close(); err != nil {
return nil, fmt.Errorf("close private key: %w", err)
}
opts = append(opts, opts = append(opts,
// Use the keypair we generated libp2p.Identity((*crypto.Secp256k1PrivateKey)(o.PrivateKey)),
libp2p.Identity(privateKey),
) )
} }
......
...@@ -8,6 +8,7 @@ package swarm ...@@ -8,6 +8,7 @@ package swarm
import ( import (
"bytes" "bytes"
"encoding/hex" "encoding/hex"
"encoding/json"
) )
// Address represents an address in Swarm metric space of // Address represents an address in Swarm metric space of
...@@ -60,5 +61,20 @@ func (a Address) Bytes() []byte { ...@@ -60,5 +61,20 @@ func (a Address) Bytes() []byte {
return a.b return a.b
} }
// UnmarshalJSON sets Address to a value from JSON-encoded representation.
func (a *Address) UnmarshalJSON(b []byte) (err error) {
var s string
if err := json.Unmarshal(b, &s); err != nil {
return err
}
*a, err = ParseHexAddress(s)
return err
}
// MarshalJSON returns JSON-encoded representation of Address.
func (a Address) MarshalJSON() ([]byte, error) {
return json.Marshal(a.String())
}
// ZeroAddress is the address that has no value. // ZeroAddress is the address that has no value.
var ZeroAddress = NewAddress(nil) var ZeroAddress = NewAddress(nil)
...@@ -6,6 +6,7 @@ package swarm_test ...@@ -6,6 +6,7 @@ package swarm_test
import ( import (
"encoding/hex" "encoding/hex"
"encoding/json"
"errors" "errors"
"testing" "testing"
...@@ -62,3 +63,21 @@ func TestAddress(t *testing.T) { ...@@ -62,3 +63,21 @@ func TestAddress(t *testing.T) {
}) })
} }
} }
func TestAddress_jsonMarshalling(t *testing.T) {
a1 := swarm.MustParseHexAddress("24798dd5a470e927fa")
b, err := json.Marshal(a1)
if err != nil {
t.Fatal(err)
}
var a2 swarm.Address
if err := json.Unmarshal(b, &a2); err != nil {
t.Fatal(err)
}
if !a1.Equal(a2) {
t.Error("unmarshalled address is not equal to the original")
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment