Skip to content

Commit

Permalink
Add import/export of public keys cosmos#79
Browse files Browse the repository at this point in the history
  • Loading branch information
Alessio Treglia committed Apr 4, 2018
1 parent 7fb3f70 commit 9c02c8c
Show file tree
Hide file tree
Showing 4 changed files with 127 additions and 7 deletions.
47 changes: 47 additions & 0 deletions keys/keybase.go
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,10 @@ func (kb dbKeybase) Sign(name, passphrase string, msg []byte) (sig crypto.Signat
if err != nil {
return
}
if info.PrivKeyArmor == "" {
err = fmt.Errorf("private key not available")
return
}
priv, err := unarmorDecryptPrivKey(info.PrivKeyArmor, passphrase)
if err != nil {
return
Expand All @@ -127,6 +131,22 @@ func (kb dbKeybase) Export(name string) (armor string, err error) {
return armorInfoBytes(bz), nil
}

// ExportPubKey returns public keys in ASCII armored format.
func (kb dbKeybase) ExportPubKey(name string) (armor string, err error) {
bz := kb.db.Get(infoKey(name))
if bz == nil {
return "", errors.New("No key to export with name " + name)
}
info, err := readInfo(bz)
if err != nil {
return
}
return armorPubKeyBytes(info.PubKey.Bytes()), nil
}

// ExportPubKey imports ASCII-armored public keys.
// Store a new Info object holding a public key only, i.e. it will
// not be possible to sign with it as it lacks the secret key.
func (kb dbKeybase) Import(name string, armor string) (err error) {
bz := kb.db.Get(infoKey(name))
if len(bz) > 0 {
Expand All @@ -140,6 +160,23 @@ func (kb dbKeybase) Import(name string, armor string) (err error) {
return nil
}

func (kb dbKeybase) ImportPubKey(name string, armor string) (err error) {
bz := kb.db.Get(infoKey(name))
if len(bz) > 0 {
return errors.New("Cannot overwrite data for name " + name)
}
pubBytes, err := unarmorPubKeyBytes(armor)
if err != nil {
return
}
pubKey, err := crypto.PubKeyFromBytes(pubBytes)
if err != nil {
return
}
kb.writePubKey(pubKey, name)
return
}

// Delete removes key forever, but we must present the
// proper passphrase before deleting it (for security).
func (kb dbKeybase) Delete(name, passphrase string) error {
Expand Down Expand Up @@ -174,6 +211,16 @@ func (kb dbKeybase) Update(name, oldpass, newpass string) error {
kb.writeKey(key, name, newpass)
return nil
}

func (kb dbKeybase) writePubKey(pub crypto.PubKey, name string) Info {
// make Info
info := newInfo(name, pub, "")

// write them both
kb.db.SetSync(infoKey(name), info.bytes())
return info
}

func (kb dbKeybase) writeKey(priv crypto.PrivKey, name, passphrase string) Info {
// generate the encrypted privkey
privArmor := encryptArmorPrivKey(priv, passphrase)
Expand Down
59 changes: 57 additions & 2 deletions keys/keybase_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -91,8 +91,8 @@ func TestSignVerify(t *testing.T) {
)
algo := keys.AlgoSecp256k1

n1, n2 := "some dude", "a dudette"
p1, p2 := "1234", "foobar"
n1, n2, n3 := "some dude", "a dudette", "dude-ish"
p1, p2, p3 := "1234", "foobar", "foobar"

// create two users and get their info
i1, _, err := cstore.Create(n1, p1, algo)
Expand All @@ -101,9 +101,18 @@ func TestSignVerify(t *testing.T) {
i2, _, err := cstore.Create(n2, p2, algo)
require.Nil(t, err)

// Import a public key
armor, err := cstore.ExportPubKey(n2)
require.Nil(t, err)
cstore.ImportPubKey(n3, armor)
i3, err := cstore.Get(n3)
require.Nil(t, err)
require.Equal(t, i3.PrivKeyArmor, "")

// let's try to sign some messages
d1 := []byte("my first message")
d2 := []byte("some other important info!")
d3 := []byte("feels like I forgot something...")

// try signing both data with both keys...
s11, pub1, err := cstore.Sign(n1, p1, d1)
Expand Down Expand Up @@ -145,6 +154,10 @@ func TestSignVerify(t *testing.T) {
valid := tc.key.VerifyBytes(tc.data, tc.sig)
assert.Equal(t, tc.valid, valid, "%d", i)
}

// Now try to sign data with a secret-less key
_, _, err = cstore.Sign(n3, p3, d3)
assert.NotNil(t, err)
}

/*
Expand Down Expand Up @@ -243,6 +256,48 @@ func TestExportImport(t *testing.T) {
assert.Equal(t, john, john2)
}

func TestExportImportPubKey(t *testing.T) {
// make the storage with reasonable defaults
db := dbm.NewMemDB()
cstore := keys.New(
db,
words.MustLoadCodec("english"),
)

// Create a private-public key pair and ensure consistency
info, _, err := cstore.Create("john", "passphrase", keys.AlgoEd25519)
assert.Nil(t, err)
assert.NotEqual(t, info.PrivKeyArmor, "")
assert.Equal(t, info.Name, "john")
addr := info.PubKey.Address()
john, err := cstore.Get("john")
assert.Nil(t, err)
assert.Equal(t, john.Name, "john")
assert.Equal(t, john.PubKey.Address(), addr)

// Export the public key only
armor, err := cstore.ExportPubKey("john")
assert.Nil(t, err)
// Import it under a different name
err = cstore.ImportPubKey("john-pubkey-only", armor)
assert.Nil(t, err)
// Ensure consistency
john2, err := cstore.Get("john-pubkey-only")
assert.Nil(t, err)
assert.Equal(t, john2.PrivKeyArmor, "")
// Compare the public keys
assert.True(t, john.PubKey.Equals(john2.PubKey))
// Ensure the original key hasn't changed
john, err = cstore.Get("john")
assert.Nil(t, err)
assert.Equal(t, john.PubKey.Address(), addr)
assert.Equal(t, john.Name, "john")

// Ensure keys cannot be overwritten
err = cstore.ImportPubKey("john-pubkey-only", armor)
assert.NotNil(t, err)
}

// TestAdvancedKeyManagement verifies update, import, export functionality
func TestAdvancedKeyManagement(t *testing.T) {

Expand Down
26 changes: 21 additions & 5 deletions keys/mintkey.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,24 +13,40 @@ import (
const (
blockTypePrivKey = "TENDERMINT PRIVATE KEY"
blockTypeKeyInfo = "TENDERMINT KEY INFO"
blockTypePubKey = "TENDERMINT PUBLIC KEY"
)

func armorInfoBytes(bz []byte) string {
return armorBytes(bz, blockTypeKeyInfo)
}

func armorPubKeyBytes(bz []byte) string {
return armorBytes(bz, blockTypePubKey)
}

func armorBytes(bz []byte, blockType string) string {
header := map[string]string{
"type": "Info",
"version": "0.0.0",
}
armorStr := crypto.EncodeArmor(blockTypeKeyInfo, header, bz)
return armorStr
return crypto.EncodeArmor(blockType, header, bz)
}

func unarmorInfoBytes(armorStr string) (bz []byte, err error) {
blockType, header, bz, err := crypto.DecodeArmor(armorStr)
return unarmorBytes(armorStr, blockTypeKeyInfo)
}

func unarmorPubKeyBytes(armorStr string) (bz []byte, err error) {
return unarmorBytes(armorStr, blockTypePubKey)
}

func unarmorBytes(armorStr, blockType string) (bz []byte, err error) {
bType, header, bz, err := crypto.DecodeArmor(armorStr)
if err != nil {
return
}
if blockType != blockTypeKeyInfo {
err = fmt.Errorf("Unrecognized armor type: %v", blockType)
if bType != blockType {
err = fmt.Errorf("Unrecognized armor type %q, expected: %q", bType, blockType)
return
}
if header["version"] != "0.0.0" {
Expand Down
2 changes: 2 additions & 0 deletions keys/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,9 @@ type Keybase interface {
Delete(name, passphrase string) error

Import(name string, armor string) (err error)
ImportPubKey(name string, armor string) (err error)
Export(name string) (armor string, err error)
ExportPubKey(name string) (armor string, err error)
}

// Info is the public information about a key
Expand Down

0 comments on commit 9c02c8c

Please sign in to comment.