Browse Source

Merge pull request #80 from alessio/keymanager

Add import/export of public keys #79
pull/1782/head
Ethan Buchman 6 years ago
committed by GitHub
parent
commit
aa3212180f
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 129 additions and 7 deletions
  1. +49
    -0
      keys/keybase.go
  2. +57
    -2
      keys/keybase_test.go
  3. +21
    -5
      keys/mintkey.go
  4. +2
    -0
      keys/types.go

+ 49
- 0
keys/keybase.go View File

@ -110,6 +110,10 @@ func (kb dbKeybase) Sign(name, passphrase string, msg []byte) (sig crypto.Signat
if err != nil { if err != nil {
return return
} }
if info.PrivKeyArmor == "" {
err = fmt.Errorf("private key not available")
return
}
priv, err := unarmorDecryptPrivKey(info.PrivKeyArmor, passphrase) priv, err := unarmorDecryptPrivKey(info.PrivKeyArmor, passphrase)
if err != nil { if err != nil {
return return
@ -127,6 +131,21 @@ func (kb dbKeybase) Export(name string) (armor string, err error) {
return armorInfoBytes(bz), nil return armorInfoBytes(bz), nil
} }
// ExportPubKey returns public keys in ASCII armored format.
// Retrieve a Info object by its name and return the public key in
// a portable 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
}
func (kb dbKeybase) Import(name string, armor string) (err error) { func (kb dbKeybase) Import(name string, armor string) (err error) {
bz := kb.db.Get(infoKey(name)) bz := kb.db.Get(infoKey(name))
if len(bz) > 0 { if len(bz) > 0 {
@ -140,6 +159,26 @@ func (kb dbKeybase) Import(name string, armor string) (err error) {
return nil return 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) 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 // Delete removes key forever, but we must present the
// proper passphrase before deleting it (for security). // proper passphrase before deleting it (for security).
func (kb dbKeybase) Delete(name, passphrase string) error { func (kb dbKeybase) Delete(name, passphrase string) error {
@ -174,6 +213,16 @@ func (kb dbKeybase) Update(name, oldpass, newpass string) error {
kb.writeKey(key, name, newpass) kb.writeKey(key, name, newpass)
return nil 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 { func (kb dbKeybase) writeKey(priv crypto.PrivKey, name, passphrase string) Info {
// generate the encrypted privkey // generate the encrypted privkey
privArmor := encryptArmorPrivKey(priv, passphrase) privArmor := encryptArmorPrivKey(priv, passphrase)


+ 57
- 2
keys/keybase_test.go View File

@ -91,8 +91,8 @@ func TestSignVerify(t *testing.T) {
) )
algo := keys.AlgoSecp256k1 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 // create two users and get their info
i1, _, err := cstore.Create(n1, p1, algo) i1, _, err := cstore.Create(n1, p1, algo)
@ -101,9 +101,18 @@ func TestSignVerify(t *testing.T) {
i2, _, err := cstore.Create(n2, p2, algo) i2, _, err := cstore.Create(n2, p2, algo)
require.Nil(t, err) 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 // let's try to sign some messages
d1 := []byte("my first message") d1 := []byte("my first message")
d2 := []byte("some other important info!") d2 := []byte("some other important info!")
d3 := []byte("feels like I forgot something...")
// try signing both data with both keys... // try signing both data with both keys...
s11, pub1, err := cstore.Sign(n1, p1, d1) s11, pub1, err := cstore.Sign(n1, p1, d1)
@ -145,6 +154,10 @@ func TestSignVerify(t *testing.T) {
valid := tc.key.VerifyBytes(tc.data, tc.sig) valid := tc.key.VerifyBytes(tc.data, tc.sig)
assert.Equal(t, tc.valid, valid, "%d", i) 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)
} }
/* /*
@ -243,6 +256,48 @@ func TestExportImport(t *testing.T) {
assert.Equal(t, john, john2) 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 // TestAdvancedKeyManagement verifies update, import, export functionality
func TestAdvancedKeyManagement(t *testing.T) { func TestAdvancedKeyManagement(t *testing.T) {


+ 21
- 5
keys/mintkey.go View File

@ -13,24 +13,40 @@ import (
const ( const (
blockTypePrivKey = "TENDERMINT PRIVATE KEY" blockTypePrivKey = "TENDERMINT PRIVATE KEY"
blockTypeKeyInfo = "TENDERMINT KEY INFO" blockTypeKeyInfo = "TENDERMINT KEY INFO"
blockTypePubKey = "TENDERMINT PUBLIC KEY"
) )
func armorInfoBytes(bz []byte) string { 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{ header := map[string]string{
"type": "Info", "type": "Info",
"version": "0.0.0", "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) { 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 { if err != nil {
return 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 return
} }
if header["version"] != "0.0.0" { if header["version"] != "0.0.0" {


+ 2
- 0
keys/types.go View File

@ -18,7 +18,9 @@ type Keybase interface {
Delete(name, passphrase string) error Delete(name, passphrase string) error
Import(name string, armor string) (err error) Import(name string, armor string) (err error)
ImportPubKey(name string, armor string) (err error)
Export(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 // Info is the public information about a key


Loading…
Cancel
Save