diff --git a/keys/keybase.go b/keys/keybase.go index 88a12000..876eef8f 100644 --- a/keys/keybase.go +++ b/keys/keybase.go @@ -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 @@ -127,6 +131,21 @@ func (kb dbKeybase) Export(name string) (armor string, err error) { 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) { bz := kb.db.Get(infoKey(name)) if len(bz) > 0 { @@ -140,6 +159,26 @@ func (kb dbKeybase) Import(name string, armor string) (err error) { 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 // proper passphrase before deleting it (for security). 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) 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) diff --git a/keys/keybase_test.go b/keys/keybase_test.go index 84c81c81..aaf3b92f 100644 --- a/keys/keybase_test.go +++ b/keys/keybase_test.go @@ -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) @@ -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) @@ -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) } /* @@ -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) { diff --git a/keys/mintkey.go b/keys/mintkey.go index 73263b83..264a9747 100644 --- a/keys/mintkey.go +++ b/keys/mintkey.go @@ -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" { diff --git a/keys/types.go b/keys/types.go index 40751ad7..e1df644a 100644 --- a/keys/types.go +++ b/keys/types.go @@ -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