package crypto_test import ( "bytes" "errors" "fmt" "io" "testing" cmtcrypto "github.com/cometbft/cometbft/crypto" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "cosmossdk.io/core/address" "cosmossdk.io/depinject" "cosmossdk.io/log" "github.com/cosmos/cosmos-sdk/codec" addresscodec "github.com/cosmos/cosmos-sdk/codec/address" "github.com/cosmos/cosmos-sdk/codec/legacy" "github.com/cosmos/cosmos-sdk/crypto" "github.com/cosmos/cosmos-sdk/crypto/hd" "github.com/cosmos/cosmos-sdk/crypto/keyring" "github.com/cosmos/cosmos-sdk/crypto/keys/bcrypt" "github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1" cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" "github.com/cosmos/cosmos-sdk/crypto/xsalsa20symmetric" _ "github.com/cosmos/cosmos-sdk/runtime" "github.com/cosmos/cosmos-sdk/testutil/configurator" "github.com/cosmos/cosmos-sdk/types" ) func TestArmorUnarmorPrivKey(t *testing.T) { priv := secp256k1.GenPrivKey() armored := crypto.EncryptArmorPrivKey(priv, "passphrase", "") _, _, err := crypto.UnarmorDecryptPrivKey(armored, "wrongpassphrase") require.Error(t, err) decrypted, algo, err := crypto.UnarmorDecryptPrivKey(armored, "passphrase") require.NoError(t, err) require.Equal(t, string(hd.Secp256k1Type), algo) require.True(t, priv.Equals(decrypted)) // empty string decrypted, algo, err = crypto.UnarmorDecryptPrivKey("", "passphrase") require.Error(t, err) require.True(t, errors.Is(err, io.EOF)) require.Nil(t, decrypted) require.Empty(t, algo) // wrong key type armored = crypto.ArmorPubKeyBytes(priv.PubKey().Bytes(), "") _, _, err = crypto.UnarmorDecryptPrivKey(armored, "passphrase") require.Error(t, err) require.Contains(t, err.Error(), "unrecognized armor type") // armor key manually encryptPrivKeyFn := func(privKey cryptotypes.PrivKey, passphrase string) (saltBytes, encBytes []byte) { saltBytes = cmtcrypto.CRandBytes(16) key, err := bcrypt.GenerateFromPassword(saltBytes, []byte(passphrase), crypto.BcryptSecurityParameter) require.NoError(t, err) key = cmtcrypto.Sha256(key) // get 32 bytes privKeyBytes := legacy.Cdc.Amino.MustMarshalBinaryBare(privKey) return saltBytes, xsalsa20symmetric.EncryptSymmetric(privKeyBytes, key) } saltBytes, encBytes := encryptPrivKeyFn(priv, "passphrase") // wrong kdf header headerWrongKdf := map[string]string{ "kdf": "wrong", "salt": fmt.Sprintf("%X", saltBytes), "type": "secp256k", } armored = crypto.EncodeArmor("TENDERMINT PRIVATE KEY", headerWrongKdf, encBytes) _, _, err = crypto.UnarmorDecryptPrivKey(armored, "passphrase") require.Error(t, err) require.Equal(t, "unrecognized KDF type: wrong", err.Error()) } func TestArmorUnarmorPubKey(t *testing.T) { // Select the encryption and storage for your cryptostore var cdc codec.Codec err := depinject.Inject(depinject.Configs( configurator.NewAppConfig(), depinject.Supply(log.NewNopLogger(), func() address.Codec { return addresscodec.NewBech32Codec("cosmos") }, func() address.ValidatorAddressCodec { return addresscodec.NewBech32Codec("cosmosvaloper") }, func() address.ConsensusAddressCodec { return addresscodec.NewBech32Codec("cosmosvalcons") }, ), ), &cdc) require.NoError(t, err) cstore := keyring.NewInMemory(cdc) // Add keys and see they return in alphabetical order k, _, err := cstore.NewMnemonic("Bob", keyring.English, types.FullFundraiserPath, keyring.DefaultBIP39Passphrase, hd.Secp256k1) require.NoError(t, err) key, err := k.GetPubKey() require.NoError(t, err) armored := crypto.ArmorPubKeyBytes(legacy.Cdc.Amino.MustMarshalBinaryBare(key), "") pubBytes, algo, err := crypto.UnarmorPubKeyBytes(armored) require.NoError(t, err) pub, err := legacy.PubKeyFromBytes(pubBytes) require.NoError(t, err) require.Equal(t, string(hd.Secp256k1Type), algo) require.True(t, pub.Equals(key)) armored = crypto.ArmorPubKeyBytes(legacy.Cdc.Amino.MustMarshalBinaryBare(key), "unknown") pubBytes, algo, err = crypto.UnarmorPubKeyBytes(armored) require.NoError(t, err) pub, err = legacy.PubKeyFromBytes(pubBytes) require.NoError(t, err) require.Equal(t, "unknown", algo) require.True(t, pub.Equals(key)) armored, err = cstore.ExportPrivKeyArmor("Bob", "passphrase") require.NoError(t, err) _, _, err = crypto.UnarmorPubKeyBytes(armored) require.Error(t, err) require.Equal(t, `couldn't unarmor bytes: unrecognized armor type "TENDERMINT PRIVATE KEY", expected: "TENDERMINT PUBLIC KEY"`, err.Error()) // armor pubkey manually header := map[string]string{ "version": "0.0.0", "type": "unknown", } armored = crypto.EncodeArmor("TENDERMINT PUBLIC KEY", header, pubBytes) _, algo, err = crypto.UnarmorPubKeyBytes(armored) require.NoError(t, err) // return secp256k1 if version is 0.0.0 require.Equal(t, "secp256k1", algo) // missing version header header = map[string]string{ "type": "unknown", } armored = crypto.EncodeArmor("TENDERMINT PUBLIC KEY", header, pubBytes) bz, algo, err := crypto.UnarmorPubKeyBytes(armored) require.Nil(t, bz) require.Empty(t, algo) require.Error(t, err) require.Equal(t, "header's version field is empty", err.Error()) // unknown version header header = map[string]string{ "type": "unknown", "version": "unknown", } armored = crypto.EncodeArmor("TENDERMINT PUBLIC KEY", header, pubBytes) bz, algo, err = crypto.UnarmorPubKeyBytes(armored) require.Nil(t, bz) require.Empty(t, algo) require.Error(t, err) require.Equal(t, "unrecognized version: unknown", err.Error()) } func TestArmorInfoBytes(t *testing.T) { bs := []byte("test") armoredString := crypto.ArmorInfoBytes(bs) unarmoredBytes, err := crypto.UnarmorInfoBytes(armoredString) require.NoError(t, err) require.True(t, bytes.Equal(bs, unarmoredBytes)) } func TestUnarmorInfoBytesErrors(t *testing.T) { unarmoredBytes, err := crypto.UnarmorInfoBytes("") require.Error(t, err) require.True(t, errors.Is(err, io.EOF)) require.Nil(t, unarmoredBytes) header := map[string]string{ "type": "Info", "version": "0.0.1", } unarmoredBytes, err = crypto.UnarmorInfoBytes(crypto.EncodeArmor( "TENDERMINT KEY INFO", header, []byte("plain-text"))) require.Error(t, err) require.Equal(t, "unrecognized version: 0.0.1", err.Error()) require.Nil(t, unarmoredBytes) } func BenchmarkBcryptGenerateFromPassword(b *testing.B) { passphrase := []byte("passphrase") for securityParam := uint32(9); securityParam < 16; securityParam++ { param := securityParam b.Run(fmt.Sprintf("benchmark-security-param-%d", param), func(b *testing.B) { b.ReportAllocs() saltBytes := cmtcrypto.CRandBytes(16) b.ResetTimer() for i := 0; i < b.N; i++ { _, err := bcrypt.GenerateFromPassword(saltBytes, passphrase, param) require.Nil(b, err) } }) } } func TestArmor(t *testing.T) { blockType := "MINT TEST" data := []byte("somedata") armorStr := crypto.EncodeArmor(blockType, nil, data) // Decode armorStr and test for equivalence. blockType2, _, data2, err := crypto.DecodeArmor(armorStr) require.Nil(t, err, "%+v", err) assert.Equal(t, blockType, blockType2) assert.Equal(t, data, data2) } func TestBcryptLegacyEncryption(t *testing.T) { privKey := secp256k1.GenPrivKey() saltBytes := cmtcrypto.CRandBytes(16) passphrase := "passphrase" privKeyBytes := legacy.Cdc.MustMarshal(privKey) // Bcrypt + Aead headerBcrypt := map[string]string{ "kdf": "bcrypt", "salt": fmt.Sprintf("%X", saltBytes), } keyBcrypt, _ := bcrypt.GenerateFromPassword(saltBytes, []byte(passphrase), 12) // Legacy key generation keyBcrypt = cmtcrypto.Sha256(keyBcrypt) // bcrypt + xsalsa20symmetric encBytesBcryptXsalsa20symetric := xsalsa20symmetric.EncryptSymmetric(privKeyBytes, keyBcrypt) type testCase struct { description string armor string } for _, scenario := range []testCase{ { description: "Argon2 + Aead", armor: crypto.EncryptArmorPrivKey(privKey, "passphrase", ""), }, { description: "Bcrypt + xsalsa20symmetric", armor: crypto.EncodeArmor("TENDERMINT PRIVATE KEY", headerBcrypt, encBytesBcryptXsalsa20symetric), }, } { t.Run(scenario.description, func(t *testing.T) { _, _, err := crypto.UnarmorDecryptPrivKey(scenario.armor, "wrongpassphrase") require.Error(t, err) decryptedPrivKey, _, err := crypto.UnarmorDecryptPrivKey(scenario.armor, "passphrase") require.NoError(t, err) require.True(t, privKey.Equals(decryptedPrivKey)) }) } // Test wrong kdf header headerWithoutKdf := map[string]string{ "kdf": "wrongKdf", "salt": fmt.Sprintf("%X", saltBytes), } _, _, err := crypto.UnarmorDecryptPrivKey(crypto.EncodeArmor("TENDERMINT PRIVATE KEY", headerWithoutKdf, encBytesBcryptXsalsa20symetric), "passphrase") require.Error(t, err) require.Equal(t, "unrecognized KDF type: wrongKdf", err.Error()) }