github.com/ava-labs/subnet-evm@v0.6.4/accounts/keystore/passphrase.go (about)

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