github.com/prysmaticlabs/prysm@v1.4.4/shared/keystore/keystore.go (about)

     1  // Copyright 2014 The go-ethereum Authors
     2  // This file is part of the go-ethereum library.
     3  //
     4  // Modified by Prysmatic Labs 2018
     5  //
     6  // The go-ethereum library is free software: you can redistribute it and/or modify
     7  // it under the terms of the GNU Lesser General Public License as published by
     8  // the Free Software Foundation, either version 3 of the License, or
     9  // (at your option) any later version.
    10  //
    11  // The go-ethereum library is distributed in the hope that it will be useful,
    12  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    13  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    14  // GNU Lesser General Public License for more details.
    15  //
    16  // You should have received a copy of the GNU Lesser General Public License
    17  // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
    18  
    19  package keystore
    20  
    21  import (
    22  	"bytes"
    23  	"crypto/aes"
    24  	"crypto/rand"
    25  	"encoding/hex"
    26  	"encoding/json"
    27  	"errors"
    28  	"fmt"
    29  	"io"
    30  	"io/ioutil"
    31  	"os"
    32  	"path/filepath"
    33  	"strings"
    34  
    35  	"github.com/minio/sha256-simd"
    36  	"github.com/pborman/uuid"
    37  	"github.com/prysmaticlabs/prysm/shared/bls"
    38  	log "github.com/sirupsen/logrus"
    39  	"golang.org/x/crypto/pbkdf2"
    40  	"golang.org/x/crypto/scrypt"
    41  )
    42  
    43  var (
    44  	// ErrDecrypt is the standard error message when decryption is a failure.
    45  	ErrDecrypt = errors.New("could not decrypt key with given passphrase")
    46  )
    47  
    48  // Keystore defines a keystore with a directory path and scrypt values.
    49  type Keystore struct {
    50  	keysDirPath string
    51  	scryptN     int
    52  	scryptP     int
    53  }
    54  
    55  // GetKey from file using the filename path and a decryption password.
    56  func (ks Keystore) GetKey(filename, password string) (*Key, error) {
    57  	// Load the key from the keystore and decrypt its contents
    58  	// #nosec G304
    59  	keyJSON, err := ioutil.ReadFile(filename)
    60  	if err != nil {
    61  		return nil, err
    62  	}
    63  	return DecryptKey(keyJSON, password)
    64  }
    65  
    66  // GetKeys from directory using the prefix to filter relevant files
    67  // and a decryption password.
    68  func (ks Keystore) GetKeys(directory, filePrefix, password string, warnOnFail bool) (map[string]*Key, error) {
    69  	// Load the key from the keystore and decrypt its contents
    70  	// #nosec G304
    71  	files, err := ioutil.ReadDir(directory)
    72  	if err != nil {
    73  		return nil, err
    74  	}
    75  	keys := make(map[string]*Key)
    76  	for _, f := range files {
    77  		n := f.Name()
    78  		filePath := filepath.Join(directory, n)
    79  		filePath = filepath.Clean(filePath)
    80  		if f.Mode()&os.ModeSymlink == os.ModeSymlink {
    81  			if targetFilePath, err := filepath.EvalSymlinks(filePath); err == nil {
    82  				filePath = targetFilePath
    83  				// Override link stats with target file's stats.
    84  				if f, err = os.Stat(filePath); err != nil {
    85  					return nil, err
    86  				}
    87  			}
    88  		}
    89  		cp := strings.Contains(n, strings.TrimPrefix(filePrefix, "/"))
    90  		if f.Mode().IsRegular() && cp {
    91  			// #nosec G304
    92  			keyJSON, err := ioutil.ReadFile(filePath)
    93  			if err != nil {
    94  				return nil, err
    95  			}
    96  			key, err := DecryptKey(keyJSON, password)
    97  			if err != nil {
    98  				if warnOnFail {
    99  					log.WithError(err).WithField("keyfile", string(keyJSON)).Warn("Failed to decrypt key")
   100  				}
   101  				continue
   102  			}
   103  			keys[hex.EncodeToString(key.PublicKey.Marshal())] = key
   104  		}
   105  	}
   106  	return keys, nil
   107  }
   108  
   109  // StoreKey in filepath and encrypt it with a password.
   110  func (ks Keystore) StoreKey(filename string, key *Key, auth string) error {
   111  	keyJSON, err := EncryptKey(key, auth, ks.scryptN, ks.scryptP)
   112  	if err != nil {
   113  		return err
   114  	}
   115  	return writeKeyFile(filename, keyJSON)
   116  }
   117  
   118  // JoinPath joins the filename with the keystore directory path.
   119  func (ks Keystore) JoinPath(filename string) string {
   120  	if filepath.IsAbs(filename) {
   121  		return filename
   122  	}
   123  	return filepath.Join(ks.keysDirPath, filename)
   124  }
   125  
   126  // EncryptKey encrypts a key using the specified scrypt parameters into a JSON
   127  // blob that can be decrypted later on.
   128  func EncryptKey(key *Key, password string, scryptN, scryptP int) ([]byte, error) {
   129  	authArray := []byte(password)
   130  	salt := make([]byte, 32)
   131  	if _, err := io.ReadFull(rand.Reader, salt); err != nil {
   132  		panic("reading from crypto/rand failed: " + err.Error())
   133  	}
   134  
   135  	derivedKey, err := scrypt.Key(authArray, salt, scryptN, scryptR, scryptP, scryptDKLen)
   136  	if err != nil {
   137  		return nil, err
   138  	}
   139  
   140  	encryptKey := derivedKey[:16]
   141  	keyBytes := key.SecretKey.Marshal()
   142  
   143  	iv := make([]byte, aes.BlockSize) // 16
   144  	if _, err := io.ReadFull(rand.Reader, iv); err != nil {
   145  		return nil, errors.New("reading from crypto/rand failed: " + err.Error())
   146  	}
   147  
   148  	cipherText, err := aesCTRXOR(encryptKey, keyBytes, iv)
   149  	if err != nil {
   150  		return nil, err
   151  	}
   152  
   153  	mac := Keccak256(derivedKey[16:32], cipherText)
   154  
   155  	scryptParamsJSON := make(map[string]interface{}, 5)
   156  	scryptParamsJSON["n"] = scryptN
   157  	scryptParamsJSON["r"] = scryptR
   158  	scryptParamsJSON["p"] = scryptP
   159  	scryptParamsJSON["dklen"] = scryptDKLen
   160  	scryptParamsJSON["salt"] = hex.EncodeToString(salt)
   161  
   162  	cipherParamsJSON := cipherparamsJSON{
   163  		IV: hex.EncodeToString(iv),
   164  	}
   165  
   166  	cryptoStruct := cryptoJSON{
   167  		Cipher:       "aes-128-ctr",
   168  		CipherText:   hex.EncodeToString(cipherText),
   169  		CipherParams: cipherParamsJSON,
   170  		KDF:          keyHeaderKDF,
   171  		KDFParams:    scryptParamsJSON,
   172  		MAC:          hex.EncodeToString(mac),
   173  	}
   174  	encryptedJSON := encryptedKeyJSON{
   175  		hex.EncodeToString(key.PublicKey.Marshal()),
   176  		cryptoStruct,
   177  		key.ID.String(),
   178  	}
   179  	return json.Marshal(encryptedJSON)
   180  }
   181  
   182  // DecryptKey decrypts a key from a JSON blob, returning the private key itself.
   183  func DecryptKey(keyJSON []byte, password string) (*Key, error) {
   184  	var keyBytes, keyID []byte
   185  	var err error
   186  
   187  	k := new(encryptedKeyJSON)
   188  	if err := json.Unmarshal(keyJSON, k); err != nil {
   189  		return nil, err
   190  	}
   191  
   192  	keyBytes, keyID, err = decryptKeyJSON(k, password)
   193  	// Handle any decryption errors and return the key
   194  	if err != nil {
   195  		return nil, err
   196  	}
   197  
   198  	secretKey, err := bls.SecretKeyFromBytes(keyBytes)
   199  	if err != nil {
   200  		return nil, err
   201  	}
   202  
   203  	return &Key{
   204  		ID:        keyID,
   205  		PublicKey: secretKey.PublicKey(),
   206  		SecretKey: secretKey,
   207  	}, nil
   208  }
   209  
   210  func decryptKeyJSON(keyProtected *encryptedKeyJSON, auth string) (keyBytes, keyID []byte, err error) {
   211  	keyID = uuid.Parse(keyProtected.ID)
   212  	if keyProtected.Crypto.Cipher != "aes-128-ctr" {
   213  		return nil, nil, fmt.Errorf("cipher not supported: %v", keyProtected.Crypto.Cipher)
   214  	}
   215  
   216  	mac, err := hex.DecodeString(keyProtected.Crypto.MAC)
   217  	if err != nil {
   218  		return nil, nil, err
   219  	}
   220  
   221  	iv, err := hex.DecodeString(keyProtected.Crypto.CipherParams.IV)
   222  	if err != nil {
   223  		return nil, nil, err
   224  	}
   225  
   226  	cipherText, err := hex.DecodeString(keyProtected.Crypto.CipherText)
   227  	if err != nil {
   228  		return nil, nil, err
   229  	}
   230  
   231  	derivedKey, err := kdfKey(keyProtected.Crypto, auth)
   232  	if err != nil {
   233  		return nil, nil, err
   234  	}
   235  
   236  	calculatedMAC := Keccak256(derivedKey[16:32], cipherText)
   237  	if !bytes.Equal(calculatedMAC, mac) {
   238  		return nil, nil, ErrDecrypt
   239  	}
   240  
   241  	plainText, err := aesCTRXOR(derivedKey[:16], cipherText, iv)
   242  	if err != nil {
   243  		return nil, nil, err
   244  	}
   245  	return plainText, keyID, nil
   246  }
   247  
   248  func kdfKey(cryptoJSON cryptoJSON, auth string) ([]byte, error) {
   249  	authArray := []byte(auth)
   250  	salt, err := hex.DecodeString(cryptoJSON.KDFParams["salt"].(string))
   251  	if err != nil {
   252  		return nil, err
   253  	}
   254  	dkLen := ensureInt(cryptoJSON.KDFParams["dklen"])
   255  
   256  	if cryptoJSON.KDF == keyHeaderKDF {
   257  		n := ensureInt(cryptoJSON.KDFParams["n"])
   258  		r := ensureInt(cryptoJSON.KDFParams["r"])
   259  		p := ensureInt(cryptoJSON.KDFParams["p"])
   260  		return scrypt.Key(authArray, salt, n, r, p, dkLen)
   261  
   262  	} else if cryptoJSON.KDF == "pbkdf2" {
   263  		c := ensureInt(cryptoJSON.KDFParams["c"])
   264  		prf, ok := cryptoJSON.KDFParams["prf"].(string)
   265  		if !ok {
   266  			return nil, errors.New("KDFParams are not type string")
   267  		}
   268  		if prf != "hmac-sha256" {
   269  			return nil, fmt.Errorf("unsupported PBKDF2 PRF: %s", prf)
   270  		}
   271  		key := pbkdf2.Key(authArray, salt, c, dkLen, sha256.New)
   272  		return key, nil
   273  	}
   274  
   275  	return nil, fmt.Errorf("unsupported KDF: %s", cryptoJSON.KDF)
   276  }