Add keystore to the repo
License: MIT Signed-off-by: Jakub Sztandera <kubuxu@protonmail.ch>
This commit is contained in:
parent
6e24543e57
commit
902ea18686
1
go.mod
1
go.mod
@ -48,6 +48,7 @@ require (
|
||||
github.com/libp2p/go-maddr-filter v0.0.5
|
||||
github.com/minio/blake2b-simd v0.0.0-20160723061019-3f5f724cb5b1
|
||||
github.com/mitchellh/go-homedir v1.1.0
|
||||
github.com/multiformats/go-base32 v0.0.3
|
||||
github.com/multiformats/go-multiaddr v0.0.4
|
||||
github.com/multiformats/go-multiaddr-dns v0.0.3
|
||||
github.com/multiformats/go-multiaddr-net v0.0.1
|
||||
|
@ -1,6 +1,7 @@
|
||||
package repo
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
@ -15,6 +16,7 @@ import (
|
||||
logging "github.com/ipfs/go-log"
|
||||
"github.com/libp2p/go-libp2p-core/crypto"
|
||||
"github.com/mitchellh/go-homedir"
|
||||
"github.com/multiformats/go-base32"
|
||||
"github.com/multiformats/go-multiaddr"
|
||||
"github.com/pkg/errors"
|
||||
"golang.org/x/xerrors"
|
||||
@ -28,6 +30,7 @@ const (
|
||||
fsDatastore = "datastore"
|
||||
fsLibp2pKey = "libp2p.priv"
|
||||
fsLock = "repo.lock"
|
||||
fsKeystore = "keystore"
|
||||
)
|
||||
|
||||
var log = logging.Logger("repo")
|
||||
@ -55,14 +58,28 @@ func NewFS(path string) (*FsRepo, error) {
|
||||
|
||||
func (fsr *FsRepo) Init() error {
|
||||
if _, err := os.Stat(fsr.path); err == nil {
|
||||
return ErrRepoExists
|
||||
return fsr.initKeystore()
|
||||
} else if !os.IsNotExist(err) {
|
||||
return err
|
||||
}
|
||||
|
||||
log.Infof("Initializing repo at '%s'", fsr.path)
|
||||
err := os.Mkdir(fsr.path, 0755) //nolint: gosec
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return fsr.initKeystore()
|
||||
|
||||
return os.Mkdir(fsr.path, 0755) //nolint: gosec
|
||||
}
|
||||
|
||||
func (fsr *FsRepo) initKeystore() error {
|
||||
kstorePath := filepath.Join(fsr.path, fsKeystore)
|
||||
if _, err := os.Stat(kstorePath); err == nil {
|
||||
return ErrRepoExists
|
||||
} else if !os.IsNotExist(err) {
|
||||
return err
|
||||
}
|
||||
return os.Mkdir(kstorePath, 0700)
|
||||
}
|
||||
|
||||
// APIEndpoint returns endpoint of API in this repo
|
||||
@ -169,6 +186,9 @@ func (fsr *fsLockedRepo) Config() (*config.Root, error) {
|
||||
}
|
||||
|
||||
func (fsr *fsLockedRepo) Libp2pIdentity() (crypto.PrivKey, error) {
|
||||
if err := fsr.stillValid(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
kpath := fsr.join(fsLibp2pKey)
|
||||
stat, err := os.Stat(kpath)
|
||||
|
||||
@ -224,6 +244,131 @@ func (fsr *fsLockedRepo) SetAPIEndpoint(ma multiaddr.Multiaddr) error {
|
||||
return ioutil.WriteFile(fsr.join(fsAPI), []byte(ma.String()), 0644)
|
||||
}
|
||||
|
||||
func (fsr *fsLockedRepo) Wallet() (interface{}, error) {
|
||||
panic("not implemented")
|
||||
func (fsr *fsLockedRepo) KeyStore() (KeyStore, error) {
|
||||
if err := fsr.stillValid(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return fsr, nil
|
||||
}
|
||||
|
||||
var kstrPermissionMsg = "permissions of key: '%s' are too relaxed, " +
|
||||
"required: 0600, got: %#o"
|
||||
|
||||
// List lists all the keys stored in the KeyStore
|
||||
func (fsr *fsLockedRepo) List() ([]string, error) {
|
||||
if err := fsr.stillValid(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
kstorePath := fsr.join(fsKeystore)
|
||||
dir, err := os.Open(kstorePath)
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("opening dir to list keystore: %w", err)
|
||||
}
|
||||
files, err := dir.Readdir(-1)
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("reading keystore dir: %w", err)
|
||||
}
|
||||
keys := make([]string, 0, len(files))
|
||||
for _, f := range files {
|
||||
if f.Mode()&0077 != 0 {
|
||||
return nil, xerrors.Errorf(kstrPermissionMsg, f.Name(), f.Mode())
|
||||
}
|
||||
name, err := base32.RawStdEncoding.DecodeString(f.Name())
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("decoding key: '%s': %w", f.Name(), err)
|
||||
}
|
||||
keys = append(keys, string(name))
|
||||
}
|
||||
return keys, nil
|
||||
}
|
||||
|
||||
// Get gets a key out of keystore and returns KeyInfo coresponding to named key
|
||||
func (fsr *fsLockedRepo) Get(name string) (KeyInfo, error) {
|
||||
if err := fsr.stillValid(); err != nil {
|
||||
return KeyInfo{}, err
|
||||
}
|
||||
|
||||
encName := base32.RawStdEncoding.EncodeToString([]byte(name))
|
||||
keyPath := fsr.join(fsKeystore, encName)
|
||||
|
||||
fstat, err := os.Stat(keyPath)
|
||||
if os.IsNotExist(err) {
|
||||
return KeyInfo{}, xerrors.Errorf("opening key '%s': %w", name, ErrKeyNotFound)
|
||||
} else if err != nil {
|
||||
return KeyInfo{}, xerrors.Errorf("opening key '%s': %w", name, err)
|
||||
}
|
||||
|
||||
if fstat.Mode()&0077 != 0 {
|
||||
return KeyInfo{}, xerrors.Errorf(kstrPermissionMsg, name, err)
|
||||
}
|
||||
|
||||
file, err := os.Open(keyPath)
|
||||
if err != nil {
|
||||
return KeyInfo{}, xerrors.Errorf("opening key '%s': %w", name, err)
|
||||
}
|
||||
defer file.Close() //nolint: errcheck // read only op
|
||||
|
||||
data, err := ioutil.ReadAll(file)
|
||||
if err != nil {
|
||||
return KeyInfo{}, xerrors.Errorf("reading key '%s': %w", name, err)
|
||||
}
|
||||
|
||||
var res KeyInfo
|
||||
err = json.Unmarshal(data, &res)
|
||||
if err != nil {
|
||||
return KeyInfo{}, xerrors.Errorf("decoding key '%s': %w", name, err)
|
||||
}
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
// Put saves key info under given name
|
||||
func (fsr *fsLockedRepo) Put(name string, info KeyInfo) error {
|
||||
if err := fsr.stillValid(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
encName := base32.RawStdEncoding.EncodeToString([]byte(name))
|
||||
keyPath := fsr.join(fsKeystore, encName)
|
||||
|
||||
_, err := os.Stat(keyPath)
|
||||
if err == nil {
|
||||
return xerrors.Errorf("checking key before put '%s': %w", name, ErrKeyExists)
|
||||
} else if !os.IsNotExist(err) {
|
||||
return xerrors.Errorf("checking key before put '%s': %w", name, err)
|
||||
}
|
||||
|
||||
keyData, err := json.Marshal(info)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("encoding key '%s': %w", name, err)
|
||||
}
|
||||
|
||||
err = ioutil.WriteFile(keyPath, keyData, 0600)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("writing key '%s': %w", name, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (fsr *fsLockedRepo) Delete(name string) error {
|
||||
if err := fsr.stillValid(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
encName := base32.RawStdEncoding.EncodeToString([]byte(name))
|
||||
keyPath := fsr.join(fsKeystore, encName)
|
||||
|
||||
_, err := os.Stat(keyPath)
|
||||
if os.IsNotExist(err) {
|
||||
return xerrors.Errorf("checking key before delete '%s': %w", name, ErrKeyNotFound)
|
||||
} else if err != nil {
|
||||
return xerrors.Errorf("checking key before delete '%s': %w", name, err)
|
||||
}
|
||||
|
||||
err = os.Remove(keyPath)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("deleting key '%s': %w", name, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
@ -1,11 +1,13 @@
|
||||
package repo
|
||||
|
||||
import "io/ioutil"
|
||||
import "os"
|
||||
import "testing"
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func genFsRepo(t *testing.T) (*FsRepo, func()) {
|
||||
path, err := ioutil.TempDir("", "lotus-repo-*")
|
||||
path, err := ioutil.TempDir("", "lotus-repo-")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@ -14,6 +16,11 @@ func genFsRepo(t *testing.T) (*FsRepo, func()) {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
err = repo.Init()
|
||||
if err != ErrRepoExists && err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return repo, func() {
|
||||
_ = os.RemoveAll(path)
|
||||
}
|
||||
|
@ -1,18 +1,22 @@
|
||||
package repo
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/ipfs/go-datastore"
|
||||
"github.com/libp2p/go-libp2p-core/crypto"
|
||||
"github.com/multiformats/go-multiaddr"
|
||||
"golang.org/x/xerrors"
|
||||
|
||||
"github.com/filecoin-project/go-lotus/node/config"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrNoAPIEndpoint = xerrors.New("API not running (no endpoint)")
|
||||
ErrRepoAlreadyLocked = xerrors.New("repo is already locked")
|
||||
ErrClosedRepo = xerrors.New("repo is no longer open")
|
||||
ErrNoAPIEndpoint = errors.New("API not running (no endpoint)")
|
||||
ErrRepoAlreadyLocked = errors.New("repo is already locked")
|
||||
ErrClosedRepo = errors.New("repo is no longer open")
|
||||
|
||||
ErrKeyExists = errors.New("key already exists")
|
||||
ErrKeyNotFound = errors.New("key not found")
|
||||
)
|
||||
|
||||
type Repo interface {
|
||||
@ -23,6 +27,22 @@ type Repo interface {
|
||||
Lock() (LockedRepo, error)
|
||||
}
|
||||
|
||||
type KeyInfo struct {
|
||||
Type string
|
||||
PrivateKey []byte
|
||||
}
|
||||
|
||||
type KeyStore interface {
|
||||
// List lists all the keys stored in the KeyStore
|
||||
List() ([]string, error)
|
||||
// Get gets a key out of keystore and returns KeyInfo coresponding to named key
|
||||
Get(string) (KeyInfo, error)
|
||||
// Put saves a key info under given name
|
||||
Put(string, KeyInfo) error
|
||||
// Delete removes a key from keystore
|
||||
Delete(string) error
|
||||
}
|
||||
|
||||
type LockedRepo interface {
|
||||
// Close closes repo and removes lock.
|
||||
Close() error
|
||||
@ -40,8 +60,8 @@ type LockedRepo interface {
|
||||
// so it can be read by API clients
|
||||
SetAPIEndpoint(multiaddr.Multiaddr) error
|
||||
|
||||
// Wallet returns store of private keys for Filecoin transactions
|
||||
Wallet() (interface{}, error)
|
||||
// KeyStore returns store of private keys for Filecoin transactions
|
||||
KeyStore() (KeyStore, error)
|
||||
|
||||
// Path returns absolute path of the repo (or empty string if in-memory)
|
||||
Path() string
|
||||
|
@ -9,6 +9,7 @@ import (
|
||||
dssync "github.com/ipfs/go-datastore/sync"
|
||||
"github.com/libp2p/go-libp2p-core/crypto"
|
||||
"github.com/multiformats/go-multiaddr"
|
||||
"golang.org/x/xerrors"
|
||||
|
||||
"github.com/filecoin-project/go-lotus/node/config"
|
||||
)
|
||||
@ -25,7 +26,7 @@ type MemRepo struct {
|
||||
datastore datastore.Datastore
|
||||
configF func() *config.Root
|
||||
libp2pKey crypto.PrivKey
|
||||
wallet interface{}
|
||||
keystore map[string]KeyInfo
|
||||
}
|
||||
|
||||
type lockedMemRepo struct {
|
||||
@ -46,7 +47,7 @@ type MemRepoOptions struct {
|
||||
Ds datastore.Datastore
|
||||
ConfigF func() *config.Root
|
||||
Libp2pKey crypto.PrivKey
|
||||
Wallet interface{}
|
||||
KeyStore map[string]KeyInfo
|
||||
}
|
||||
|
||||
func genLibp2pKey() (crypto.PrivKey, error) {
|
||||
@ -77,6 +78,9 @@ func NewMemory(opts *MemRepoOptions) *MemRepo {
|
||||
}
|
||||
opts.Libp2pKey = pk
|
||||
}
|
||||
if opts.KeyStore == nil {
|
||||
opts.KeyStore = make(map[string]KeyInfo)
|
||||
}
|
||||
|
||||
return &MemRepo{
|
||||
repoLock: make(chan struct{}, 1),
|
||||
@ -84,7 +88,7 @@ func NewMemory(opts *MemRepoOptions) *MemRepo {
|
||||
datastore: opts.Ds,
|
||||
configF: opts.ConfigF,
|
||||
libp2pKey: opts.Libp2pKey,
|
||||
wallet: opts.Wallet,
|
||||
keystore: opts.KeyStore,
|
||||
}
|
||||
}
|
||||
|
||||
@ -172,9 +176,73 @@ func (lmem *lockedMemRepo) SetAPIEndpoint(ma multiaddr.Multiaddr) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (lmem *lockedMemRepo) Wallet() (interface{}, error) {
|
||||
func (lmem *lockedMemRepo) KeyStore() (KeyStore, error) {
|
||||
if err := lmem.checkToken(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return lmem.mem.wallet, nil
|
||||
return lmem, nil
|
||||
}
|
||||
|
||||
// Implement KeyStore on the same instance
|
||||
|
||||
// List lists all the keys stored in the KeyStore
|
||||
func (lmem *lockedMemRepo) List() ([]string, error) {
|
||||
if err := lmem.checkToken(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
lmem.RLock()
|
||||
defer lmem.RUnlock()
|
||||
|
||||
res := make([]string, 0, len(lmem.mem.keystore))
|
||||
for k := range lmem.mem.keystore {
|
||||
res = append(res, k)
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
// Get gets a key out of keystore and returns KeyInfo coresponding to named key
|
||||
func (lmem *lockedMemRepo) Get(name string) (KeyInfo, error) {
|
||||
if err := lmem.checkToken(); err != nil {
|
||||
return KeyInfo{}, err
|
||||
}
|
||||
lmem.RLock()
|
||||
defer lmem.RUnlock()
|
||||
|
||||
key, ok := lmem.mem.keystore[name]
|
||||
if !ok {
|
||||
return KeyInfo{}, xerrors.Errorf("getting key '%s': %w", name, ErrKeyNotFound)
|
||||
}
|
||||
return key, nil
|
||||
}
|
||||
|
||||
// Put saves key info under given name
|
||||
func (lmem *lockedMemRepo) Put(name string, key KeyInfo) error {
|
||||
if err := lmem.checkToken(); err != nil {
|
||||
return err
|
||||
}
|
||||
lmem.Lock()
|
||||
defer lmem.Unlock()
|
||||
|
||||
_, isThere := lmem.mem.keystore[name]
|
||||
if isThere {
|
||||
return xerrors.Errorf("putting key '%s': %w", name, ErrKeyExists)
|
||||
}
|
||||
|
||||
lmem.mem.keystore[name] = key
|
||||
return nil
|
||||
}
|
||||
|
||||
func (lmem *lockedMemRepo) Delete(name string) error {
|
||||
if err := lmem.checkToken(); err != nil {
|
||||
return err
|
||||
}
|
||||
lmem.Lock()
|
||||
defer lmem.Unlock()
|
||||
|
||||
_, isThere := lmem.mem.keystore[name]
|
||||
if !isThere {
|
||||
return xerrors.Errorf("deleting key '%s': %w", name, ErrKeyNotFound)
|
||||
}
|
||||
delete(lmem.mem.keystore, name)
|
||||
return nil
|
||||
}
|
||||
|
@ -5,6 +5,7 @@ import (
|
||||
|
||||
"github.com/multiformats/go-multiaddr"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"golang.org/x/xerrors"
|
||||
|
||||
"github.com/filecoin-project/go-lotus/node/config"
|
||||
)
|
||||
@ -62,4 +63,56 @@ func basicTest(t *testing.T, repo Repo) {
|
||||
assert.Equal(t, ErrNoAPIEndpoint, err, "after closing repo, api should be nil")
|
||||
}
|
||||
assert.Nil(t, apima, "with closed repo, apima should be set back to nil")
|
||||
|
||||
k1 := KeyInfo{Type: "foo"}
|
||||
k2 := KeyInfo{Type: "bar"}
|
||||
|
||||
lrepo, err = repo.Lock()
|
||||
assert.NoError(t, err, "should be able to relock")
|
||||
assert.NotNil(t, lrepo, "locked repo shouldn't be nil")
|
||||
|
||||
kstr, err := lrepo.KeyStore()
|
||||
assert.NoError(t, err, "should be able to get keystore")
|
||||
assert.NotNil(t, lrepo, "keystore shouldn't be nil")
|
||||
|
||||
list, err := kstr.List()
|
||||
assert.NoError(t, err, "should be able to list key")
|
||||
assert.Empty(t, list, "there should be no keys")
|
||||
|
||||
err = kstr.Put("k1", k1)
|
||||
assert.NoError(t, err, "should be able to put k1")
|
||||
|
||||
err = kstr.Put("k1", k1)
|
||||
if assert.Error(t, err, "putting key under the same name should error") {
|
||||
assert.True(t, xerrors.Is(err, ErrKeyExists), "returned error is ErrKeyExists")
|
||||
}
|
||||
|
||||
k1prim, err := kstr.Get("k1")
|
||||
assert.NoError(t, err, "should be able to get k1")
|
||||
assert.Equal(t, k1, k1prim, "returned key should be the same")
|
||||
|
||||
k2prim, err := kstr.Get("k2")
|
||||
if assert.Error(t, err, "should not be able to get k2") {
|
||||
assert.True(t, xerrors.Is(err, ErrKeyNotFound), "returned error is ErrKeyNotFound")
|
||||
}
|
||||
assert.Empty(t, k2prim, "there should be no output for k2")
|
||||
|
||||
err = kstr.Put("k2", k2)
|
||||
assert.NoError(t, err, "should be able to put k2")
|
||||
|
||||
list, err = kstr.List()
|
||||
assert.NoError(t, err, "should be able to list keys")
|
||||
assert.ElementsMatch(t, []string{"k1", "k2"}, list, "returned elements match")
|
||||
|
||||
err = kstr.Delete("k2")
|
||||
assert.NoError(t, err, "should be able to delete key")
|
||||
|
||||
list, err = kstr.List()
|
||||
assert.NoError(t, err, "should be able to list keys")
|
||||
assert.ElementsMatch(t, []string{"k1"}, list, "returned elements match")
|
||||
|
||||
err = kstr.Delete("k2")
|
||||
if assert.Error(t, err) {
|
||||
assert.True(t, xerrors.Is(err, ErrKeyNotFound), "returned errror is ErrKeyNotFound")
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user