github.com/Finschia/finschia-sdk@v0.48.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  	ostcrypto "github.com/Finschia/ostracon/crypto"
    11  	"github.com/Finschia/ostracon/crypto/armor"
    12  	"github.com/Finschia/ostracon/crypto/xsalsa20symmetric"
    13  	"github.com/stretchr/testify/require"
    14  	"github.com/tendermint/crypto/bcrypt"
    15  
    16  	"github.com/Finschia/finschia-sdk/codec/legacy"
    17  	"github.com/Finschia/finschia-sdk/crypto"
    18  	"github.com/Finschia/finschia-sdk/crypto/hd"
    19  	"github.com/Finschia/finschia-sdk/crypto/keyring"
    20  	"github.com/Finschia/finschia-sdk/crypto/keys/secp256k1"
    21  	cryptotypes "github.com/Finschia/finschia-sdk/crypto/types"
    22  	"github.com/Finschia/finschia-sdk/types"
    23  )
    24  
    25  func TestArmorUnarmorPrivKey(t *testing.T) {
    26  	priv := secp256k1.GenPrivKey()
    27  	armored := crypto.EncryptArmorPrivKey(priv, "passphrase", "")
    28  	_, _, err := crypto.UnarmorDecryptPrivKey(armored, "wrongpassphrase")
    29  	require.Error(t, err)
    30  	decrypted, algo, err := crypto.UnarmorDecryptPrivKey(armored, "passphrase")
    31  	require.NoError(t, err)
    32  	require.Equal(t, string(hd.Secp256k1Type), algo)
    33  	require.True(t, priv.Equals(decrypted))
    34  
    35  	// empty string
    36  	decrypted, algo, err = crypto.UnarmorDecryptPrivKey("", "passphrase")
    37  	require.Error(t, err)
    38  	require.True(t, errors.Is(io.EOF, err))
    39  	require.Nil(t, decrypted)
    40  	require.Empty(t, algo)
    41  
    42  	// wrong key type
    43  	armored = crypto.ArmorPubKeyBytes(priv.PubKey().Bytes(), "")
    44  	_, _, err = crypto.UnarmorDecryptPrivKey(armored, "passphrase")
    45  	require.Error(t, err)
    46  	require.Contains(t, err.Error(), "unrecognized armor type")
    47  
    48  	// armor key manually
    49  	encryptPrivKeyFn := func(privKey cryptotypes.PrivKey, passphrase string) (saltBytes []byte, encBytes []byte) {
    50  		saltBytes = ostcrypto.CRandBytes(16)
    51  		key, err := bcrypt.GenerateFromPassword(saltBytes, []byte(passphrase), crypto.BcryptSecurityParameter)
    52  		require.NoError(t, err)
    53  		key = ostcrypto.Sha256(key) // get 32 bytes
    54  		privKeyBytes := legacy.Cdc.Amino.MustMarshalBinaryBare(privKey)
    55  		return saltBytes, xsalsa20symmetric.EncryptSymmetric(privKeyBytes, key)
    56  	}
    57  	saltBytes, encBytes := encryptPrivKeyFn(priv, "passphrase")
    58  
    59  	// wrong kdf header
    60  	headerWrongKdf := map[string]string{
    61  		"kdf":  "wrong",
    62  		"salt": fmt.Sprintf("%X", saltBytes),
    63  		"type": "secp256k",
    64  	}
    65  	armored = armor.EncodeArmor("OSTRACON PRIVATE KEY", headerWrongKdf, encBytes)
    66  	_, _, err = crypto.UnarmorDecryptPrivKey(armored, "passphrase")
    67  	require.Error(t, err)
    68  	require.Equal(t, "unrecognized KDF type: wrong", err.Error())
    69  }
    70  
    71  func TestArmorUnarmorPubKey(t *testing.T) {
    72  	// Select the encryption and storage for your cryptostore
    73  	cstore := keyring.NewInMemory()
    74  
    75  	// Add keys and see they return in alphabetical order
    76  	info, _, err := cstore.NewMnemonic("Bob", keyring.English, types.FullFundraiserPath, keyring.DefaultBIP39Passphrase, hd.Secp256k1)
    77  	require.NoError(t, err)
    78  	armored := crypto.ArmorPubKeyBytes(legacy.Cdc.Amino.MustMarshalBinaryBare(info.GetPubKey()), "")
    79  	pubBytes, algo, err := crypto.UnarmorPubKeyBytes(armored)
    80  	require.NoError(t, err)
    81  	pub, err := legacy.PubKeyFromBytes(pubBytes)
    82  	require.NoError(t, err)
    83  	require.Equal(t, string(hd.Secp256k1Type), algo)
    84  	require.True(t, pub.Equals(info.GetPubKey()))
    85  
    86  	armored = crypto.ArmorPubKeyBytes(legacy.Cdc.Amino.MustMarshalBinaryBare(info.GetPubKey()), "unknown")
    87  	pubBytes, algo, err = crypto.UnarmorPubKeyBytes(armored)
    88  	require.NoError(t, err)
    89  	pub, err = legacy.PubKeyFromBytes(pubBytes)
    90  	require.NoError(t, err)
    91  	require.Equal(t, "unknown", algo)
    92  	require.True(t, pub.Equals(info.GetPubKey()))
    93  
    94  	armored, err = cstore.ExportPrivKeyArmor("Bob", "passphrase")
    95  	require.NoError(t, err)
    96  	_, _, err = crypto.UnarmorPubKeyBytes(armored)
    97  	require.Error(t, err)
    98  	require.Equal(t, `couldn't unarmor bytes: unrecognized armor type "OSTRACON PRIVATE KEY", expected: "OSTRACON PUBLIC KEY"`, err.Error())
    99  
   100  	// armor pubkey manually
   101  	header := map[string]string{
   102  		"version": "0.0.0",
   103  		"type":    "unknown",
   104  	}
   105  	armored = armor.EncodeArmor("OSTRACON PUBLIC KEY", header, pubBytes)
   106  	_, algo, err = crypto.UnarmorPubKeyBytes(armored)
   107  	require.NoError(t, err)
   108  	// return secp256k1 if version is 0.0.0
   109  	require.Equal(t, "secp256k1", algo)
   110  
   111  	// missing version header
   112  	header = map[string]string{
   113  		"type": "unknown",
   114  	}
   115  	armored = armor.EncodeArmor("OSTRACON PUBLIC KEY", header, pubBytes)
   116  	bz, algo, err := crypto.UnarmorPubKeyBytes(armored)
   117  	require.Nil(t, bz)
   118  	require.Empty(t, algo)
   119  	require.Error(t, err)
   120  	require.Equal(t, "header's version field is empty", err.Error())
   121  
   122  	// unknown version header
   123  	header = map[string]string{
   124  		"type":    "unknown",
   125  		"version": "unknown",
   126  	}
   127  	armored = armor.EncodeArmor("OSTRACON PUBLIC KEY", header, pubBytes)
   128  	bz, algo, err = crypto.UnarmorPubKeyBytes(armored)
   129  	require.Nil(t, bz)
   130  	require.Empty(t, algo)
   131  	require.Error(t, err)
   132  	require.Equal(t, "unrecognized version: unknown", err.Error())
   133  }
   134  
   135  func TestArmorInfoBytes(t *testing.T) {
   136  	bs := []byte("test")
   137  	armoredString := crypto.ArmorInfoBytes(bs)
   138  	unarmoredBytes, err := crypto.UnarmorInfoBytes(armoredString)
   139  	require.NoError(t, err)
   140  	require.True(t, bytes.Equal(bs, unarmoredBytes))
   141  }
   142  
   143  func TestUnarmorInfoBytesErrors(t *testing.T) {
   144  	unarmoredBytes, err := crypto.UnarmorInfoBytes("")
   145  	require.Error(t, err)
   146  	require.True(t, errors.Is(io.EOF, err))
   147  	require.Nil(t, unarmoredBytes)
   148  
   149  	header := map[string]string{
   150  		"type":    "Info",
   151  		"version": "0.0.1",
   152  	}
   153  	unarmoredBytes, err = crypto.UnarmorInfoBytes(armor.EncodeArmor(
   154  		"OSTRACON KEY INFO", header, []byte("plain-text")))
   155  	require.Error(t, err)
   156  	require.Equal(t, "unrecognized version: 0.0.1", err.Error())
   157  	require.Nil(t, unarmoredBytes)
   158  }
   159  
   160  func BenchmarkBcryptGenerateFromPassword(b *testing.B) {
   161  	passphrase := []byte("passphrase")
   162  	for securityParam := 9; securityParam < 16; securityParam++ {
   163  		param := securityParam
   164  		b.Run(fmt.Sprintf("benchmark-security-param-%d", param), func(b *testing.B) {
   165  			b.ReportAllocs()
   166  			saltBytes := ostcrypto.CRandBytes(16)
   167  			b.ResetTimer()
   168  			for i := 0; i < b.N; i++ {
   169  				_, err := bcrypt.GenerateFromPassword(saltBytes, passphrase, param)
   170  				require.Nil(b, err)
   171  			}
   172  		})
   173  	}
   174  }