github.com/amazechain/amc@v0.1.3/accounts/keystore/passphrase.go (about)

     1  // Copyright 2023 The AmazeChain Authors
     2  // This file is part of the AmazeChain library.
     3  //
     4  // The AmazeChain library is free software: you can redistribute it and/or modify
     5  // it under the terms of the GNU Lesser General Public License as published by
     6  // the Free Software Foundation, either version 3 of the License, or
     7  // (at your option) any later version.
     8  //
     9  // The AmazeChain library is distributed in the hope that it will be useful,
    10  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    11  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    12  // GNU Lesser General Public License for more details.
    13  //
    14  // You should have received a copy of the GNU Lesser General Public License
    15  // along with the AmazeChain library. If not, see <http://www.gnu.org/licenses/>.
    16  /*
    17  
    18  This key store behaves as KeyStorePlain with the difference that
    19  the private key is encrypted and on disk uses another JSON encoding.
    20  
    21  The crypto is documented at https://github.com/ethereum/wiki/wiki/Web3-Secret-Storage-Definition
    22  
    23  */
    24  
    25  package keystore
    26  
    27  import (
    28  	"bytes"
    29  	"crypto/aes"
    30  	"crypto/rand"
    31  	"crypto/sha256"
    32  	"encoding/hex"
    33  	"encoding/json"
    34  	"fmt"
    35  	"io"
    36  	"os"
    37  	"path/filepath"
    38  
    39  	"github.com/amazechain/amc/accounts"
    40  	"github.com/amazechain/amc/common/crypto"
    41  	"github.com/amazechain/amc/common/math"
    42  	"github.com/amazechain/amc/common/types"
    43  	"github.com/google/uuid"
    44  	"golang.org/x/crypto/pbkdf2"
    45  	"golang.org/x/crypto/scrypt"
    46  )
    47  
    48  const (
    49  	keyHeaderKDF = "scrypt"
    50  
    51  	// StandardScryptN is the N parameter of Scrypt encryption algorithm, using 256MB
    52  	// memory and taking approximately 1s CPU time on a modern processor.
    53  	StandardScryptN = 1 << 18
    54  
    55  	// StandardScryptP is the P parameter of Scrypt encryption algorithm, using 256MB
    56  	// memory and taking approximately 1s CPU time on a modern processor.
    57  	StandardScryptP = 1
    58  
    59  	// LightScryptN is the N parameter of Scrypt encryption algorithm, using 4MB
    60  	// memory and taking approximately 100ms CPU time on a modern processor.
    61  	LightScryptN = 1 << 12
    62  
    63  	// LightScryptP is the P parameter of Scrypt encryption algorithm, using 4MB
    64  	// memory and taking approximately 100ms CPU time on a modern processor.
    65  	LightScryptP = 6
    66  
    67  	scryptR     = 8
    68  	scryptDKLen = 32
    69  )
    70  
    71  type keyStorePassphrase struct {
    72  	keysDirPath string
    73  	scryptN     int
    74  	scryptP     int
    75  	// skipKeyFileVerification disables the security-feature which does
    76  	// reads and decrypts any newly created keyfiles. This should be 'false' in all
    77  	// cases except tests -- setting this to 'true' is not recommended.
    78  	skipKeyFileVerification bool
    79  }
    80  
    81  func (ks keyStorePassphrase) GetKey(addr types.Address, filename, auth string) (*Key, error) {
    82  	// Load the key from the keystore and decrypt its contents
    83  	keyjson, err := os.ReadFile(filename)
    84  	if err != nil {
    85  		return nil, err
    86  	}
    87  	key, err := DecryptKey(keyjson, auth)
    88  	if err != nil {
    89  		return nil, err
    90  	}
    91  	// Make sure we're really operating on the requested key (no swap attacks)
    92  	if key.Address != addr {
    93  		return nil, fmt.Errorf("key content mismatch: have account %x, want %x", key.Address, addr)
    94  	}
    95  	return key, nil
    96  }
    97  
    98  // StoreKey generates a key, encrypts with 'auth' and stores in the given directory
    99  func StoreKey(dir, auth string, scryptN, scryptP int) (accounts.Account, error) {
   100  	_, a, err := storeNewKey(&keyStorePassphrase{dir, scryptN, scryptP, false}, rand.Reader, auth)
   101  	return a, err
   102  }
   103  
   104  func (ks keyStorePassphrase) StoreKey(filename string, key *Key, auth string) error {
   105  	keyjson, err := EncryptKey(key, auth, ks.scryptN, ks.scryptP)
   106  	if err != nil {
   107  		return err
   108  	}
   109  	// Write into temporary file
   110  	tmpName, err := writeTemporaryKeyFile(filename, keyjson)
   111  	if err != nil {
   112  		return err
   113  	}
   114  	if !ks.skipKeyFileVerification {
   115  		// Verify that we can decrypt the file with the given password.
   116  		_, err = ks.GetKey(key.Address, tmpName, auth)
   117  		if err != nil {
   118  			msg := "An error was encountered when saving and verifying the keystore file. \n" +
   119  				"This indicates that the keystore is corrupted. \n" +
   120  				"The corrupted file is stored at \n%v\n" +
   121  				"Please file a ticket at:\n\n" +
   122  				"https://github.com/amazechain/amc/issues." +
   123  				"The error was : %s"
   124  			//lint:ignore ST1005 This is a message for the user
   125  			return fmt.Errorf(msg, tmpName, err)
   126  		}
   127  	}
   128  	return os.Rename(tmpName, filename)
   129  }
   130  
   131  func (ks keyStorePassphrase) JoinPath(filename string) string {
   132  	if filepath.IsAbs(filename) {
   133  		return filename
   134  	}
   135  	return filepath.Join(ks.keysDirPath, filename)
   136  }
   137  
   138  // Encryptdata encrypts the data given as 'data' with the password 'auth'.
   139  func EncryptDataV3(data, auth []byte, scryptN, scryptP int) (CryptoJSON, error) {
   140  	salt := make([]byte, 32)
   141  	if _, err := io.ReadFull(rand.Reader, salt); err != nil {
   142  		panic("reading from crypto/rand failed: " + err.Error())
   143  	}
   144  	derivedKey, err := scrypt.Key(auth, salt, scryptN, scryptR, scryptP, scryptDKLen)
   145  	if err != nil {
   146  		return CryptoJSON{}, err
   147  	}
   148  	encryptKey := derivedKey[:16]
   149  
   150  	iv := make([]byte, aes.BlockSize) // 16
   151  	if _, err := io.ReadFull(rand.Reader, iv); err != nil {
   152  		panic("reading from crypto/rand failed: " + err.Error())
   153  	}
   154  	cipherText, err := aesCTRXOR(encryptKey, data, iv)
   155  	if err != nil {
   156  		return CryptoJSON{}, err
   157  	}
   158  	mac := crypto.Keccak256(derivedKey[16:32], cipherText)
   159  
   160  	scryptParamsJSON := make(map[string]interface{}, 5)
   161  	scryptParamsJSON["n"] = scryptN
   162  	scryptParamsJSON["r"] = scryptR
   163  	scryptParamsJSON["p"] = scryptP
   164  	scryptParamsJSON["dklen"] = scryptDKLen
   165  	scryptParamsJSON["salt"] = hex.EncodeToString(salt)
   166  	cipherParamsJSON := cipherparamsJSON{
   167  		IV: hex.EncodeToString(iv),
   168  	}
   169  
   170  	cryptoStruct := CryptoJSON{
   171  		Cipher:       "aes-128-ctr",
   172  		CipherText:   hex.EncodeToString(cipherText),
   173  		CipherParams: cipherParamsJSON,
   174  		KDF:          keyHeaderKDF,
   175  		KDFParams:    scryptParamsJSON,
   176  		MAC:          hex.EncodeToString(mac),
   177  	}
   178  	return cryptoStruct, nil
   179  }
   180  
   181  // EncryptKey encrypts a key using the specified scrypt parameters into a json
   182  // blob that can be decrypted later on.
   183  func EncryptKey(key *Key, auth string, scryptN, scryptP int) ([]byte, error) {
   184  	keyBytes := math.PaddedBigBytes(key.PrivateKey.D, 32)
   185  	cryptoStruct, err := EncryptDataV3(keyBytes, []byte(auth), scryptN, scryptP)
   186  	if err != nil {
   187  		return nil, err
   188  	}
   189  	encryptedKeyJSONV3 := encryptedKeyJSONV3{
   190  		hex.EncodeToString(key.Address[:]),
   191  		cryptoStruct,
   192  		key.Id.String(),
   193  		version,
   194  	}
   195  	return json.Marshal(encryptedKeyJSONV3)
   196  }
   197  
   198  // DecryptKey decrypts a key from a json blob, returning the private key itself.
   199  func DecryptKey(keyjson []byte, auth string) (*Key, error) {
   200  	// Parse the json into a simple map to fetch the key version
   201  	m := make(map[string]interface{})
   202  	if err := json.Unmarshal(keyjson, &m); err != nil {
   203  		return nil, err
   204  	}
   205  	// Depending on the version try to parse one way or another
   206  	var (
   207  		keyBytes, keyId []byte
   208  		err             error
   209  	)
   210  	if version, ok := m["version"].(string); ok && version == "1" {
   211  		k := new(encryptedKeyJSONV1)
   212  		if err := json.Unmarshal(keyjson, k); err != nil {
   213  			return nil, err
   214  		}
   215  		keyBytes, keyId, err = decryptKeyV1(k, auth)
   216  	} else {
   217  		k := new(encryptedKeyJSONV3)
   218  		if err := json.Unmarshal(keyjson, k); err != nil {
   219  			return nil, err
   220  		}
   221  		keyBytes, keyId, err = decryptKeyV3(k, auth)
   222  	}
   223  	// Handle any decryption errors and return the key
   224  	if err != nil {
   225  		return nil, err
   226  	}
   227  	key := crypto.ToECDSAUnsafe(keyBytes)
   228  	id, err := uuid.FromBytes(keyId)
   229  	if err != nil {
   230  		return nil, err
   231  	}
   232  	return &Key{
   233  		Id:         id,
   234  		Address:    crypto.PubkeyToAddress(key.PublicKey),
   235  		PrivateKey: key,
   236  	}, nil
   237  }
   238  
   239  func DecryptDataV3(cryptoJson CryptoJSON, auth string) ([]byte, error) {
   240  	if cryptoJson.Cipher != "aes-128-ctr" {
   241  		return nil, fmt.Errorf("cipher not supported: %v", cryptoJson.Cipher)
   242  	}
   243  	mac, err := hex.DecodeString(cryptoJson.MAC)
   244  	if err != nil {
   245  		return nil, err
   246  	}
   247  
   248  	iv, err := hex.DecodeString(cryptoJson.CipherParams.IV)
   249  	if err != nil {
   250  		return nil, err
   251  	}
   252  
   253  	cipherText, err := hex.DecodeString(cryptoJson.CipherText)
   254  	if err != nil {
   255  		return nil, err
   256  	}
   257  
   258  	derivedKey, err := getKDFKey(cryptoJson, auth)
   259  	if err != nil {
   260  		return nil, err
   261  	}
   262  
   263  	calculatedMAC := crypto.Keccak256(derivedKey[16:32], cipherText)
   264  	if !bytes.Equal(calculatedMAC, mac) {
   265  		return nil, ErrDecrypt
   266  	}
   267  
   268  	plainText, err := aesCTRXOR(derivedKey[:16], cipherText, iv)
   269  	if err != nil {
   270  		return nil, err
   271  	}
   272  	return plainText, err
   273  }
   274  
   275  func decryptKeyV3(keyProtected *encryptedKeyJSONV3, auth string) (keyBytes []byte, keyId []byte, err error) {
   276  	if keyProtected.Version != version {
   277  		return nil, nil, fmt.Errorf("version not supported: %v", keyProtected.Version)
   278  	}
   279  	keyUUID, err := uuid.Parse(keyProtected.Id)
   280  	if err != nil {
   281  		return nil, nil, err
   282  	}
   283  	keyId = keyUUID[:]
   284  	plainText, err := DecryptDataV3(keyProtected.Crypto, auth)
   285  	if err != nil {
   286  		return nil, nil, err
   287  	}
   288  	return plainText, keyId, err
   289  }
   290  
   291  func decryptKeyV1(keyProtected *encryptedKeyJSONV1, auth string) (keyBytes []byte, keyId []byte, err error) {
   292  	keyUUID, err := uuid.Parse(keyProtected.Id)
   293  	if err != nil {
   294  		return nil, nil, err
   295  	}
   296  	keyId = keyUUID[:]
   297  	mac, err := hex.DecodeString(keyProtected.Crypto.MAC)
   298  	if err != nil {
   299  		return nil, nil, err
   300  	}
   301  
   302  	iv, err := hex.DecodeString(keyProtected.Crypto.CipherParams.IV)
   303  	if err != nil {
   304  		return nil, nil, err
   305  	}
   306  
   307  	cipherText, err := hex.DecodeString(keyProtected.Crypto.CipherText)
   308  	if err != nil {
   309  		return nil, nil, err
   310  	}
   311  
   312  	derivedKey, err := getKDFKey(keyProtected.Crypto, auth)
   313  	if err != nil {
   314  		return nil, nil, err
   315  	}
   316  
   317  	calculatedMAC := crypto.Keccak256(derivedKey[16:32], cipherText)
   318  	if !bytes.Equal(calculatedMAC, mac) {
   319  		return nil, nil, ErrDecrypt
   320  	}
   321  
   322  	plainText, err := aesCBCDecrypt(crypto.Keccak256(derivedKey[:16])[:16], cipherText, iv)
   323  	if err != nil {
   324  		return nil, nil, err
   325  	}
   326  	return plainText, keyId, err
   327  }
   328  
   329  func getKDFKey(cryptoJSON CryptoJSON, auth string) ([]byte, error) {
   330  	authArray := []byte(auth)
   331  	salt, err := hex.DecodeString(cryptoJSON.KDFParams["salt"].(string))
   332  	if err != nil {
   333  		return nil, err
   334  	}
   335  	dkLen := ensureInt(cryptoJSON.KDFParams["dklen"])
   336  
   337  	if cryptoJSON.KDF == keyHeaderKDF {
   338  		n := ensureInt(cryptoJSON.KDFParams["n"])
   339  		r := ensureInt(cryptoJSON.KDFParams["r"])
   340  		p := ensureInt(cryptoJSON.KDFParams["p"])
   341  		return scrypt.Key(authArray, salt, n, r, p, dkLen)
   342  	} else if cryptoJSON.KDF == "pbkdf2" {
   343  		c := ensureInt(cryptoJSON.KDFParams["c"])
   344  		prf := cryptoJSON.KDFParams["prf"].(string)
   345  		if prf != "hmac-sha256" {
   346  			return nil, fmt.Errorf("unsupported PBKDF2 PRF: %s", prf)
   347  		}
   348  		key := pbkdf2.Key(authArray, salt, c, dkLen, sha256.New)
   349  		return key, nil
   350  	}
   351  
   352  	return nil, fmt.Errorf("unsupported KDF: %s", cryptoJSON.KDF)
   353  }
   354  
   355  // TODO: can we do without this when unmarshalling dynamic JSON?
   356  // why do integers in KDF params end up as float64 and not int after
   357  // unmarshal?
   358  func ensureInt(x interface{}) int {
   359  	res, ok := x.(int)
   360  	if !ok {
   361  		res = int(x.(float64))
   362  	}
   363  	return res
   364  }