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  }