158 lines
5.2 KiB
Go
158 lines
5.2 KiB
Go
|
package mintkey
|
||
|
|
||
|
import (
|
||
|
"encoding/hex"
|
||
|
"fmt"
|
||
|
|
||
|
"github.com/tendermint/crypto/bcrypt"
|
||
|
|
||
|
emintEncoding "github.com/cosmos/ethermint/crypto/encoding"
|
||
|
"github.com/tendermint/tendermint/crypto"
|
||
|
"github.com/tendermint/tendermint/crypto/armor"
|
||
|
|
||
|
"github.com/tendermint/tendermint/crypto/xsalsa20symmetric"
|
||
|
|
||
|
cmn "github.com/tendermint/tendermint/libs/common"
|
||
|
|
||
|
"github.com/cosmos/cosmos-sdk/crypto/keys/keyerror"
|
||
|
)
|
||
|
|
||
|
const (
|
||
|
blockTypePrivKey = "ETHERMINT PRIVATE KEY"
|
||
|
blockTypeKeyInfo = "ETHERMINT KEY INFO"
|
||
|
blockTypePubKey = "ETHERMINT PUBLIC KEY"
|
||
|
)
|
||
|
|
||
|
// Make bcrypt security parameter var, so it can be changed within the lcd test
|
||
|
// Making the bcrypt security parameter a var shouldn't be a security issue:
|
||
|
// One can't verify an invalid key by maliciously changing the bcrypt
|
||
|
// parameter during a runtime vulnerability. The main security
|
||
|
// threat this then exposes would be something that changes this during
|
||
|
// runtime before the user creates their key. This vulnerability must
|
||
|
// succeed to update this to that same value before every subsequent call
|
||
|
// to the keys command in future startups / or the attacker must get access
|
||
|
// to the filesystem. However, with a similar threat model (changing
|
||
|
// variables in runtime), one can cause the user to sign a different tx
|
||
|
// than what they see, which is a significantly cheaper attack then breaking
|
||
|
// a bcrypt hash. (Recall that the nonce still exists to break rainbow tables)
|
||
|
// For further notes on security parameter choice, see README.md
|
||
|
var BcryptSecurityParameter = 12
|
||
|
|
||
|
//-----------------------------------------------------------------
|
||
|
// add armor
|
||
|
|
||
|
// Armor the InfoBytes
|
||
|
func ArmorInfoBytes(bz []byte) string {
|
||
|
return armorBytes(bz, blockTypeKeyInfo)
|
||
|
}
|
||
|
|
||
|
// Armor the PubKeyBytes
|
||
|
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",
|
||
|
}
|
||
|
return armor.EncodeArmor(blockType, header, bz)
|
||
|
}
|
||
|
|
||
|
//-----------------------------------------------------------------
|
||
|
// remove armor
|
||
|
|
||
|
// Unarmor the InfoBytes
|
||
|
func UnarmorInfoBytes(armorStr string) (bz []byte, err error) {
|
||
|
return unarmorBytes(armorStr, blockTypeKeyInfo)
|
||
|
}
|
||
|
|
||
|
// Unarmor the PubKeyBytes
|
||
|
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 := armor.DecodeArmor(armorStr)
|
||
|
if err != nil {
|
||
|
return
|
||
|
}
|
||
|
if bType != blockType {
|
||
|
err = fmt.Errorf("Unrecognized armor type %q, expected: %q", bType, blockType)
|
||
|
return
|
||
|
}
|
||
|
if header["version"] != "0.0.0" {
|
||
|
err = fmt.Errorf("Unrecognized version: %v", header["version"])
|
||
|
return
|
||
|
}
|
||
|
return
|
||
|
}
|
||
|
|
||
|
//-----------------------------------------------------------------
|
||
|
// encrypt/decrypt with armor
|
||
|
|
||
|
// Encrypt and armor the private key.
|
||
|
func EncryptArmorPrivKey(privKey crypto.PrivKey, passphrase string) string {
|
||
|
saltBytes, encBytes := encryptPrivKey(privKey, passphrase)
|
||
|
header := map[string]string{
|
||
|
"kdf": "bcrypt",
|
||
|
"salt": fmt.Sprintf("%X", saltBytes),
|
||
|
}
|
||
|
armorStr := armor.EncodeArmor(blockTypePrivKey, header, encBytes)
|
||
|
return armorStr
|
||
|
}
|
||
|
|
||
|
// encrypt the given privKey with the passphrase using a randomly
|
||
|
// generated salt and the xsalsa20 cipher. returns the salt and the
|
||
|
// encrypted priv key.
|
||
|
func encryptPrivKey(privKey crypto.PrivKey, passphrase string) (saltBytes []byte, encBytes []byte) {
|
||
|
saltBytes = crypto.CRandBytes(16)
|
||
|
key, err := bcrypt.GenerateFromPassword(saltBytes, []byte(passphrase), BcryptSecurityParameter)
|
||
|
if err != nil {
|
||
|
cmn.Exit("Error generating bcrypt key from passphrase: " + err.Error())
|
||
|
}
|
||
|
key = crypto.Sha256(key) // get 32 bytes
|
||
|
privKeyBytes := privKey.Bytes()
|
||
|
return saltBytes, xsalsa20symmetric.EncryptSymmetric(privKeyBytes, key)
|
||
|
}
|
||
|
|
||
|
// Unarmor and decrypt the private key.
|
||
|
func UnarmorDecryptPrivKey(armorStr string, passphrase string) (crypto.PrivKey, error) {
|
||
|
var privKey crypto.PrivKey
|
||
|
blockType, header, encBytes, err := armor.DecodeArmor(armorStr)
|
||
|
if err != nil {
|
||
|
return privKey, err
|
||
|
}
|
||
|
if blockType != blockTypePrivKey {
|
||
|
return privKey, fmt.Errorf("Unrecognized armor type: %v", blockType)
|
||
|
}
|
||
|
if header["kdf"] != "bcrypt" {
|
||
|
return privKey, fmt.Errorf("Unrecognized KDF type: %v", header["KDF"])
|
||
|
}
|
||
|
if header["salt"] == "" {
|
||
|
return privKey, fmt.Errorf("Missing salt bytes")
|
||
|
}
|
||
|
saltBytes, err := hex.DecodeString(header["salt"])
|
||
|
if err != nil {
|
||
|
return privKey, fmt.Errorf("Error decoding salt: %v", err.Error())
|
||
|
}
|
||
|
privKey, err = decryptPrivKey(saltBytes, encBytes, passphrase)
|
||
|
return privKey, err
|
||
|
}
|
||
|
|
||
|
func decryptPrivKey(saltBytes []byte, encBytes []byte, passphrase string) (privKey crypto.PrivKey, err error) {
|
||
|
key, err := bcrypt.GenerateFromPassword(saltBytes, []byte(passphrase), BcryptSecurityParameter)
|
||
|
if err != nil {
|
||
|
cmn.Exit("Error generating bcrypt key from passphrase: " + err.Error())
|
||
|
}
|
||
|
key = crypto.Sha256(key) // Get 32 bytes
|
||
|
privKeyBytes, err := xsalsa20symmetric.DecryptSymmetric(encBytes, key)
|
||
|
if err != nil && err.Error() == "Ciphertext decryption failed" {
|
||
|
return privKey, keyerror.NewErrWrongPassword()
|
||
|
} else if err != nil {
|
||
|
return privKey, err
|
||
|
}
|
||
|
privKey, err = emintEncoding.PrivKeyFromBytes(privKeyBytes)
|
||
|
return privKey, err
|
||
|
}
|