github.com/core-coin/go-core/v2@v2.1.9/accounts/keystore/passphrase.go (about)

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