github.com/cosmos/cosmos-sdk@v0.50.1/crypto/armor_test.go (about) 1 package crypto_test 2 3 import ( 4 "bytes" 5 "errors" 6 "fmt" 7 "io" 8 "testing" 9 10 cmtcrypto "github.com/cometbft/cometbft/crypto" 11 "github.com/stretchr/testify/assert" 12 "github.com/stretchr/testify/require" 13 14 "cosmossdk.io/core/address" 15 "cosmossdk.io/depinject" 16 "cosmossdk.io/log" 17 18 "github.com/cosmos/cosmos-sdk/codec" 19 addresscodec "github.com/cosmos/cosmos-sdk/codec/address" 20 "github.com/cosmos/cosmos-sdk/codec/legacy" 21 "github.com/cosmos/cosmos-sdk/crypto" 22 "github.com/cosmos/cosmos-sdk/crypto/hd" 23 "github.com/cosmos/cosmos-sdk/crypto/keyring" 24 "github.com/cosmos/cosmos-sdk/crypto/keys/bcrypt" 25 "github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1" 26 cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" 27 "github.com/cosmos/cosmos-sdk/crypto/xsalsa20symmetric" 28 "github.com/cosmos/cosmos-sdk/runtime" 29 "github.com/cosmos/cosmos-sdk/testutil/configurator" 30 "github.com/cosmos/cosmos-sdk/types" 31 ) 32 33 func TestArmorUnarmorPrivKey(t *testing.T) { 34 priv := secp256k1.GenPrivKey() 35 armored := crypto.EncryptArmorPrivKey(priv, "passphrase", "") 36 _, _, err := crypto.UnarmorDecryptPrivKey(armored, "wrongpassphrase") 37 require.Error(t, err) 38 decrypted, algo, err := crypto.UnarmorDecryptPrivKey(armored, "passphrase") 39 require.NoError(t, err) 40 require.Equal(t, string(hd.Secp256k1Type), algo) 41 require.True(t, priv.Equals(decrypted)) 42 43 // empty string 44 decrypted, algo, err = crypto.UnarmorDecryptPrivKey("", "passphrase") 45 require.Error(t, err) 46 require.True(t, errors.Is(io.EOF, err)) 47 require.Nil(t, decrypted) 48 require.Empty(t, algo) 49 50 // wrong key type 51 armored = crypto.ArmorPubKeyBytes(priv.PubKey().Bytes(), "") 52 _, _, err = crypto.UnarmorDecryptPrivKey(armored, "passphrase") 53 require.Error(t, err) 54 require.Contains(t, err.Error(), "unrecognized armor type") 55 56 // armor key manually 57 encryptPrivKeyFn := func(privKey cryptotypes.PrivKey, passphrase string) (saltBytes, encBytes []byte) { 58 saltBytes = cmtcrypto.CRandBytes(16) 59 key, err := bcrypt.GenerateFromPassword(saltBytes, []byte(passphrase), crypto.BcryptSecurityParameter) 60 require.NoError(t, err) 61 key = cmtcrypto.Sha256(key) // get 32 bytes 62 privKeyBytes := legacy.Cdc.Amino.MustMarshalBinaryBare(privKey) 63 return saltBytes, xsalsa20symmetric.EncryptSymmetric(privKeyBytes, key) 64 } 65 saltBytes, encBytes := encryptPrivKeyFn(priv, "passphrase") 66 67 // wrong kdf header 68 headerWrongKdf := map[string]string{ 69 "kdf": "wrong", 70 "salt": fmt.Sprintf("%X", saltBytes), 71 "type": "secp256k", 72 } 73 armored = crypto.EncodeArmor("TENDERMINT PRIVATE KEY", headerWrongKdf, encBytes) 74 _, _, err = crypto.UnarmorDecryptPrivKey(armored, "passphrase") 75 require.Error(t, err) 76 require.Equal(t, "unrecognized KDF type: wrong", err.Error()) 77 } 78 79 func TestArmorUnarmorPubKey(t *testing.T) { 80 // Select the encryption and storage for your cryptostore 81 var cdc codec.Codec 82 83 err := depinject.Inject(depinject.Configs( 84 configurator.NewAppConfig(), 85 depinject.Supply(log.NewNopLogger(), 86 func() address.Codec { return addresscodec.NewBech32Codec("cosmos") }, 87 func() runtime.ValidatorAddressCodec { return addresscodec.NewBech32Codec("cosmosvaloper") }, 88 func() runtime.ConsensusAddressCodec { return addresscodec.NewBech32Codec("cosmosvalcons") }, 89 ), 90 ), &cdc) 91 require.NoError(t, err) 92 93 cstore := keyring.NewInMemory(cdc) 94 95 // Add keys and see they return in alphabetical order 96 k, _, err := cstore.NewMnemonic("Bob", keyring.English, types.FullFundraiserPath, keyring.DefaultBIP39Passphrase, hd.Secp256k1) 97 require.NoError(t, err) 98 key, err := k.GetPubKey() 99 require.NoError(t, err) 100 armored := crypto.ArmorPubKeyBytes(legacy.Cdc.Amino.MustMarshalBinaryBare(key), "") 101 pubBytes, algo, err := crypto.UnarmorPubKeyBytes(armored) 102 require.NoError(t, err) 103 pub, err := legacy.PubKeyFromBytes(pubBytes) 104 require.NoError(t, err) 105 require.Equal(t, string(hd.Secp256k1Type), algo) 106 require.True(t, pub.Equals(key)) 107 108 armored = crypto.ArmorPubKeyBytes(legacy.Cdc.Amino.MustMarshalBinaryBare(key), "unknown") 109 pubBytes, algo, err = crypto.UnarmorPubKeyBytes(armored) 110 require.NoError(t, err) 111 pub, err = legacy.PubKeyFromBytes(pubBytes) 112 require.NoError(t, err) 113 require.Equal(t, "unknown", algo) 114 require.True(t, pub.Equals(key)) 115 116 armored, err = cstore.ExportPrivKeyArmor("Bob", "passphrase") 117 require.NoError(t, err) 118 _, _, err = crypto.UnarmorPubKeyBytes(armored) 119 require.Error(t, err) 120 require.Equal(t, `couldn't unarmor bytes: unrecognized armor type "TENDERMINT PRIVATE KEY", expected: "TENDERMINT PUBLIC KEY"`, err.Error()) 121 122 // armor pubkey manually 123 header := map[string]string{ 124 "version": "0.0.0", 125 "type": "unknown", 126 } 127 armored = crypto.EncodeArmor("TENDERMINT PUBLIC KEY", header, pubBytes) 128 _, algo, err = crypto.UnarmorPubKeyBytes(armored) 129 require.NoError(t, err) 130 // return secp256k1 if version is 0.0.0 131 require.Equal(t, "secp256k1", algo) 132 133 // missing version header 134 header = map[string]string{ 135 "type": "unknown", 136 } 137 armored = crypto.EncodeArmor("TENDERMINT PUBLIC KEY", header, pubBytes) 138 bz, algo, err := crypto.UnarmorPubKeyBytes(armored) 139 require.Nil(t, bz) 140 require.Empty(t, algo) 141 require.Error(t, err) 142 require.Equal(t, "header's version field is empty", err.Error()) 143 144 // unknown version header 145 header = map[string]string{ 146 "type": "unknown", 147 "version": "unknown", 148 } 149 armored = crypto.EncodeArmor("TENDERMINT PUBLIC KEY", header, pubBytes) 150 bz, algo, err = crypto.UnarmorPubKeyBytes(armored) 151 require.Nil(t, bz) 152 require.Empty(t, algo) 153 require.Error(t, err) 154 require.Equal(t, "unrecognized version: unknown", err.Error()) 155 } 156 157 func TestArmorInfoBytes(t *testing.T) { 158 bs := []byte("test") 159 armoredString := crypto.ArmorInfoBytes(bs) 160 unarmoredBytes, err := crypto.UnarmorInfoBytes(armoredString) 161 require.NoError(t, err) 162 require.True(t, bytes.Equal(bs, unarmoredBytes)) 163 } 164 165 func TestUnarmorInfoBytesErrors(t *testing.T) { 166 unarmoredBytes, err := crypto.UnarmorInfoBytes("") 167 require.Error(t, err) 168 require.True(t, errors.Is(io.EOF, err)) 169 require.Nil(t, unarmoredBytes) 170 171 header := map[string]string{ 172 "type": "Info", 173 "version": "0.0.1", 174 } 175 unarmoredBytes, err = crypto.UnarmorInfoBytes(crypto.EncodeArmor( 176 "TENDERMINT KEY INFO", header, []byte("plain-text"))) 177 require.Error(t, err) 178 require.Equal(t, "unrecognized version: 0.0.1", err.Error()) 179 require.Nil(t, unarmoredBytes) 180 } 181 182 func BenchmarkBcryptGenerateFromPassword(b *testing.B) { 183 passphrase := []byte("passphrase") 184 for securityParam := uint32(9); securityParam < 16; securityParam++ { 185 param := securityParam 186 b.Run(fmt.Sprintf("benchmark-security-param-%d", param), func(b *testing.B) { 187 b.ReportAllocs() 188 saltBytes := cmtcrypto.CRandBytes(16) 189 b.ResetTimer() 190 for i := 0; i < b.N; i++ { 191 _, err := bcrypt.GenerateFromPassword(saltBytes, passphrase, param) 192 require.Nil(b, err) 193 } 194 }) 195 } 196 } 197 198 func TestArmor(t *testing.T) { 199 blockType := "MINT TEST" 200 data := []byte("somedata") 201 armorStr := crypto.EncodeArmor(blockType, nil, data) 202 203 // Decode armorStr and test for equivalence. 204 blockType2, _, data2, err := crypto.DecodeArmor(armorStr) 205 require.Nil(t, err, "%+v", err) 206 assert.Equal(t, blockType, blockType2) 207 assert.Equal(t, data, data2) 208 } 209 210 func TestBcryptLegacyEncryption(t *testing.T) { 211 privKey := secp256k1.GenPrivKey() 212 saltBytes := cmtcrypto.CRandBytes(16) 213 passphrase := "passphrase" 214 privKeyBytes := legacy.Cdc.MustMarshal(privKey) 215 216 // Bcrypt + Aead 217 headerBcrypt := map[string]string{ 218 "kdf": "bcrypt", 219 "salt": fmt.Sprintf("%X", saltBytes), 220 } 221 keyBcrypt, _ := bcrypt.GenerateFromPassword(saltBytes, []byte(passphrase), 12) // Legacy key generation 222 keyBcrypt = cmtcrypto.Sha256(keyBcrypt) 223 224 // bcrypt + xsalsa20symmetric 225 encBytesBcryptXsalsa20symetric := xsalsa20symmetric.EncryptSymmetric(privKeyBytes, keyBcrypt) 226 227 type testCase struct { 228 description string 229 armor string 230 } 231 232 for _, scenario := range []testCase{ 233 { 234 description: "Argon2 + Aead", 235 armor: crypto.EncryptArmorPrivKey(privKey, "passphrase", ""), 236 }, 237 { 238 description: "Bcrypt + xsalsa20symmetric", 239 armor: crypto.EncodeArmor("TENDERMINT PRIVATE KEY", headerBcrypt, encBytesBcryptXsalsa20symetric), 240 }, 241 } { 242 t.Run(scenario.description, func(t *testing.T) { 243 _, _, err := crypto.UnarmorDecryptPrivKey(scenario.armor, "wrongpassphrase") 244 require.Error(t, err) 245 decryptedPrivKey, _, err := crypto.UnarmorDecryptPrivKey(scenario.armor, "passphrase") 246 require.NoError(t, err) 247 require.True(t, privKey.Equals(decryptedPrivKey)) 248 }) 249 } 250 251 // Test wrong kdf header 252 headerWithoutKdf := map[string]string{ 253 "kdf": "wrongKdf", 254 "salt": fmt.Sprintf("%X", saltBytes), 255 } 256 257 _, _, err := crypto.UnarmorDecryptPrivKey(crypto.EncodeArmor("TENDERMINT PRIVATE KEY", headerWithoutKdf, encBytesBcryptXsalsa20symetric), "passphrase") 258 require.Error(t, err) 259 require.Equal(t, "unrecognized KDF type: wrongKdf", err.Error()) 260 }