Skip to content

Commit

Permalink
feat(keystore): keystore implmenetation (#1602)
Browse files Browse the repository at this point in the history
* feat(keys): initial keys package

* feat(keystore): keystore implmenetation

Co-authored-by: b5 <sparkle_pony_2000@qri.io>
  • Loading branch information
Arqu and b5 authored Jan 20, 2021
1 parent 1401638 commit 205165a
Show file tree
Hide file tree
Showing 13 changed files with 685 additions and 20 deletions.
13 changes: 11 additions & 2 deletions dscache/convert_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"github.com/qri-io/qfs"
testPeers "github.com/qri-io/qri/config/test"
"github.com/qri-io/qri/dsref"
"github.com/qri-io/qri/key"
"github.com/qri-io/qri/logbook"
"github.com/qri-io/qri/profile"
reporef "github.com/qri-io/qri/repo/ref"
Expand Down Expand Up @@ -440,7 +441,11 @@ func TestBuildDscacheFromLogbookAndProfilesAndDsrefAlphabetized(t *testing.T) {
Peername: "test_user",
PrivKey: peerInfo.PrivKey,
}
profiles, err := profile.NewMemStore(pro)
keyStore, err := key.NewMemStore()
if err != nil {
t.Fatal(err)
}
profiles, err := profile.NewMemStore(pro, keyStore)
if err != nil {
t.Fatal(err)
}
Expand Down Expand Up @@ -513,7 +518,11 @@ func TestBuildDscacheFromLogbookAndProfilesAndDsrefFillInfo(t *testing.T) {
Peername: "test_user",
PrivKey: peerInfo.PrivKey,
}
profiles, err := profile.NewMemStore(pro)
keyStore, err := key.NewMemStore()
if err != nil {
t.Fatal(err)
}
profiles, err := profile.NewMemStore(pro, keyStore)
if err != nil {
t.Fatal(err)
}
Expand Down
10 changes: 10 additions & 0 deletions key/key.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package key

import (
logger "github.com/ipfs/go-log"
)

var log = logger.Logger("key")

// ID is the key identifier
type ID string
178 changes: 178 additions & 0 deletions key/keybook.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
package key

import (
"encoding/json"
"errors"
"sync"

ic "github.com/libp2p/go-libp2p-core/crypto"
)

// Book defines the interface for keybook implementations
// which hold the key information
type Book interface {
// PubKey stores the public key for a key.ID
PubKey(ID) ic.PubKey

// AddPubKey stores the public for a key.ID
AddPubKey(ID, ic.PubKey) error

// PrivKey returns the private key for a key.ID, if known
PrivKey(ID) ic.PrivKey

// AddPrivKey stores the private key for a key.ID
AddPrivKey(ID, ic.PrivKey) error

// IDsWithKeys returns all the key IDs stored in the KeyBook
IDsWithKeys() []ID
}

type memoryKeyBook struct {
sync.RWMutex // same lock. wont happen a ton.
pks map[ID]ic.PubKey
sks map[ID]ic.PrivKey
}

var _ Book = (*memoryKeyBook)(nil)

func newKeyBook() *memoryKeyBook {
return &memoryKeyBook{
pks: map[ID]ic.PubKey{},
sks: map[ID]ic.PrivKey{},
}
}

// IDsWithKeys returns the list of IDs in the KeyBook
func (mkb *memoryKeyBook) IDsWithKeys() []ID {
mkb.RLock()
ps := make([]ID, 0, len(mkb.pks)+len(mkb.sks))
for p := range mkb.pks {
ps = append(ps, p)
}
for p := range mkb.sks {
if _, found := mkb.pks[p]; !found {
ps = append(ps, p)
}
}
mkb.RUnlock()
return ps
}

// PubKey returns the public key for a given ID if it exists
func (mkb *memoryKeyBook) PubKey(k ID) ic.PubKey {
mkb.RLock()
pk := mkb.pks[k]
mkb.RUnlock()
// TODO(arqu): we ignore the recovery mechanic to avoid magic
// behavior in above stores. We should revisit once we work out
// the broader mechanics of managing keys.
// pk, _ = p.ExtractPublicKey()
// if err == nil {
// mkb.Lock()
// mkb.pks[p] = pk
// mkb.Unlock()
// }
return pk
}

// AddPubKey inserts a public key for a given ID
func (mkb *memoryKeyBook) AddPubKey(k ID, pk ic.PubKey) error {
mkb.Lock()
mkb.pks[k] = pk
mkb.Unlock()
return nil
}

// PrivKey returns the private key for a given ID if it exists
func (mkb *memoryKeyBook) PrivKey(k ID) ic.PrivKey {
mkb.RLock()
sk := mkb.sks[k]
mkb.RUnlock()
return sk
}

// AddPrivKey inserts a private key for a given ID
func (mkb *memoryKeyBook) AddPrivKey(k ID, sk ic.PrivKey) error {
if sk == nil {
return errors.New("sk is nil (PrivKey)")
}

mkb.Lock()
mkb.sks[k] = sk
mkb.Unlock()
return nil
}

// MarshalJSON implements the JSON marshal interface
func (mkb *memoryKeyBook) MarshalJSON() ([]byte, error) {
mkb.RLock()
res := map[string]interface{}{}
pubKeys := map[ID]string{}
privKeys := map[ID]string{}
for k, v := range mkb.pks {
byteKey, err := ic.MarshalPublicKey(v)
if err != nil {
// skip/don't marshal ill formed keys
log.Debugf("keybook: failed to marshal key: %q", err.Error())
continue
}
pubKeys[k] = ic.ConfigEncodeKey(byteKey)
}
for k, v := range mkb.sks {
byteKey, err := ic.MarshalPrivateKey(v)
if err != nil {
// skip/don't marshal ill formed keys
log.Debugf("keybook: failed to marshal key: %q", err.Error())
continue
}
privKeys[k] = ic.ConfigEncodeKey(byteKey)
}

res["public_keys"] = pubKeys
res["private_keys"] = privKeys

mkb.RUnlock()
return json.Marshal(res)
}

// UnmarshalJSON implements the JSON unmarshal interface
func (mkb *memoryKeyBook) UnmarshalJSON(data []byte) error {
keyBookJSON := map[string]map[string]string{}
err := json.Unmarshal(data, &keyBookJSON)
if err != nil {
return err
}
if pubKeys, ok := keyBookJSON["public_keys"]; ok {
for k, v := range pubKeys {
byteKey, err := ic.ConfigDecodeKey(v)
if err != nil {
return err
}
key, err := ic.UnmarshalPublicKey(byteKey)
if err != nil {
return err
}
err = mkb.AddPubKey(ID(k), key)
if err != nil {
return err
}
}
}
if privKeys, ok := keyBookJSON["private_keys"]; ok {
for k, v := range privKeys {
byteKey, err := ic.ConfigDecodeKey(v)
if err != nil {
return err
}
key, err := ic.UnmarshalPrivateKey(byteKey)
if err != nil {
return err
}
err = mkb.AddPrivKey(ID(k), key)
if err != nil {
return err
}
}
}
return nil
}
93 changes: 93 additions & 0 deletions key/keybook_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
package key

import (
"testing"

cfgtest "github.com/qri-io/qri/config/test"
)

func TestPublicKey(t *testing.T) {
kb := newKeyBook()
pi0 := cfgtest.GetTestPeerInfo(0)
k0 := ID("key_id_0")

err := kb.AddPubKey(k0, pi0.PubKey)
if err != nil {
t.Fatal(err)
}

pi1 := cfgtest.GetTestPeerInfo(1)
k1 := ID("key_id_1")
err = kb.AddPubKey(k1, pi1.PubKey)
if err != nil {
t.Fatal(err)
}

tPub := kb.PubKey(k0)
if tPub != pi0.PubKey {
t.Fatalf("returned key does not match")
}

tPub = kb.PubKey(k1)
if tPub != pi1.PubKey {
t.Fatalf("returned key does not match")
}
}

func TestPrivateKey(t *testing.T) {
kb := newKeyBook()
pi0 := cfgtest.GetTestPeerInfo(0)
k0 := ID("key_id_0")

err := kb.AddPrivKey(k0, pi0.PrivKey)
if err != nil {
t.Fatal(err)
}

pi1 := cfgtest.GetTestPeerInfo(1)
k1 := ID("key_id_1")
err = kb.AddPrivKey(k1, pi1.PrivKey)
if err != nil {
t.Fatal(err)
}

tPriv := kb.PrivKey(k0)
if tPriv != pi0.PrivKey {
t.Fatalf("returned key does not match")
}

tPriv = kb.PrivKey(k1)
if tPriv != pi1.PrivKey {
t.Fatalf("returned key does not match")
}
}

func TestIDsWithKeys(t *testing.T) {
kb := newKeyBook()
pi0 := cfgtest.GetTestPeerInfo(0)
k0 := ID("key_id_0")

err := kb.AddPrivKey(k0, pi0.PrivKey)
if err != nil {
t.Fatal(err)
}

pi1 := cfgtest.GetTestPeerInfo(1)
k1 := ID("key_id_1")
err = kb.AddPubKey(k1, pi1.PubKey)
if err != nil {
t.Fatal(err)
}

pids := kb.IDsWithKeys()

if len(pids) != 2 {
t.Fatalf("expected to get 2 ids but got: %d", len(pids))
}

// the output of kb.IDsWithKeys is in a non-deterministic order
// so we have to account for all permutations
if !(pids[0] == k0 && pids[1] == k1) && !(pids[0] == k1 && pids[1] == k0) {
t.Fatalf("invalid ids returned")
}
}
Loading

0 comments on commit 205165a

Please sign in to comment.