2019-12-19 18:27:27 +00:00
|
|
|
// Imported from github.com/ethereum/go-ethereum/accounts/keystore/passphrase.go
|
|
|
|
// and github.com/ethereum/go-ethereum/accounts/keystore/presale.go
|
|
|
|
|
|
|
|
package keystore
|
|
|
|
|
|
|
|
import (
|
|
|
|
"bytes"
|
|
|
|
"crypto/aes"
|
|
|
|
"crypto/cipher"
|
|
|
|
"crypto/sha256"
|
|
|
|
"encoding/hex"
|
|
|
|
"encoding/json"
|
|
|
|
"fmt"
|
|
|
|
|
2021-06-30 11:40:54 +00:00
|
|
|
"github.com/google/uuid"
|
2020-01-02 09:10:19 +00:00
|
|
|
"golang.org/x/crypto/pbkdf2"
|
|
|
|
"golang.org/x/crypto/scrypt"
|
|
|
|
|
2019-12-19 18:27:27 +00:00
|
|
|
"github.com/status-im/status-go/eth-node/crypto"
|
|
|
|
"github.com/status-im/status-go/eth-node/types"
|
|
|
|
"github.com/status-im/status-go/extkeys"
|
|
|
|
)
|
|
|
|
|
|
|
|
const (
|
|
|
|
keyHeaderKDF = "scrypt"
|
|
|
|
)
|
|
|
|
|
|
|
|
type encryptedKeyJSONV3 struct {
|
|
|
|
Address string `json:"address"`
|
|
|
|
Crypto CryptoJSON `json:"crypto"`
|
|
|
|
Id string `json:"id"`
|
|
|
|
Version int `json:"version"`
|
|
|
|
ExtendedKey CryptoJSON `json:"extendedkey"`
|
|
|
|
SubAccountIndex uint32 `json:"subaccountindex"`
|
|
|
|
}
|
|
|
|
|
|
|
|
type encryptedKeyJSONV1 struct {
|
|
|
|
Address string `json:"address"`
|
|
|
|
Crypto CryptoJSON `json:"crypto"`
|
|
|
|
Id string `json:"id"`
|
|
|
|
Version string `json:"version"`
|
|
|
|
}
|
|
|
|
|
|
|
|
type CryptoJSON struct {
|
|
|
|
Cipher string `json:"cipher"`
|
|
|
|
CipherText string `json:"ciphertext"`
|
|
|
|
CipherParams cipherparamsJSON `json:"cipherparams"`
|
|
|
|
KDF string `json:"kdf"`
|
|
|
|
KDFParams map[string]interface{} `json:"kdfparams"`
|
|
|
|
MAC string `json:"mac"`
|
|
|
|
}
|
|
|
|
|
|
|
|
type cipherparamsJSON struct {
|
|
|
|
IV string `json:"iv"`
|
|
|
|
}
|
|
|
|
|
|
|
|
// DecryptKey decrypts a key from a json blob, returning the private key itself.
|
|
|
|
func DecryptKey(keyjson []byte, auth string) (*types.Key, error) {
|
|
|
|
// Parse the json into a simple map to fetch the key version
|
|
|
|
m := make(map[string]interface{})
|
|
|
|
if err := json.Unmarshal(keyjson, &m); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
// Depending on the version try to parse one way or another
|
|
|
|
var (
|
|
|
|
keyBytes, keyId []byte
|
|
|
|
err error
|
|
|
|
extKeyBytes []byte
|
|
|
|
extKey *extkeys.ExtendedKey
|
|
|
|
)
|
|
|
|
|
|
|
|
subAccountIndex, ok := m["subaccountindex"].(float64)
|
|
|
|
if !ok {
|
|
|
|
subAccountIndex = 0
|
|
|
|
}
|
|
|
|
|
|
|
|
if version, ok := m["version"].(string); ok && version == "1" {
|
|
|
|
k := new(encryptedKeyJSONV1)
|
|
|
|
if err := json.Unmarshal(keyjson, k); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
keyBytes, keyId, err = decryptKeyV1(k, auth)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
extKey, err = extkeys.NewKeyFromString(extkeys.EmptyExtendedKeyString)
|
|
|
|
} else {
|
|
|
|
k := new(encryptedKeyJSONV3)
|
|
|
|
if err := json.Unmarshal(keyjson, k); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
keyBytes, keyId, err = decryptKeyV3(k, auth)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
extKeyBytes, err = decryptExtendedKey(k, auth)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
extKey, err = extkeys.NewKeyFromString(string(extKeyBytes))
|
|
|
|
}
|
|
|
|
// Handle any decryption errors and return the key
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
key := crypto.ToECDSAUnsafe(keyBytes)
|
|
|
|
|
2021-06-30 11:40:54 +00:00
|
|
|
id, err := uuid.FromBytes(keyId)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2019-12-19 18:27:27 +00:00
|
|
|
return &types.Key{
|
2021-06-30 11:40:54 +00:00
|
|
|
ID: id,
|
2019-12-19 18:27:27 +00:00
|
|
|
Address: crypto.PubkeyToAddress(key.PublicKey),
|
|
|
|
PrivateKey: key,
|
|
|
|
ExtendedKey: extKey,
|
|
|
|
SubAccountIndex: uint32(subAccountIndex),
|
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func DecryptDataV3(cryptoJson CryptoJSON, auth string) ([]byte, error) {
|
|
|
|
if cryptoJson.Cipher != "aes-128-ctr" {
|
|
|
|
return nil, fmt.Errorf("Cipher not supported: %v", cryptoJson.Cipher)
|
|
|
|
}
|
|
|
|
mac, err := hex.DecodeString(cryptoJson.MAC)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
iv, err := hex.DecodeString(cryptoJson.CipherParams.IV)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
cipherText, err := hex.DecodeString(cryptoJson.CipherText)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
derivedKey, err := getKDFKey(cryptoJson, auth)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
calculatedMAC := crypto.Keccak256(derivedKey[16:32], cipherText)
|
|
|
|
if !bytes.Equal(calculatedMAC, mac) {
|
|
|
|
return nil, ErrDecrypt
|
|
|
|
}
|
|
|
|
|
|
|
|
plainText, err := aesCTRXOR(derivedKey[:16], cipherText, iv)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
return plainText, err
|
|
|
|
}
|
|
|
|
|
|
|
|
func decryptKeyV3(keyProtected *encryptedKeyJSONV3, auth string) (keyBytes []byte, keyId []byte, err error) {
|
|
|
|
if keyProtected.Version != version {
|
|
|
|
return nil, nil, fmt.Errorf("Version not supported: %v", keyProtected.Version)
|
|
|
|
}
|
2021-06-30 11:40:54 +00:00
|
|
|
id, err := uuid.Parse(keyProtected.Id)
|
|
|
|
if err != nil {
|
|
|
|
return nil, nil, err
|
|
|
|
}
|
|
|
|
keyId = id[:]
|
2019-12-19 18:27:27 +00:00
|
|
|
plainText, err := DecryptDataV3(keyProtected.Crypto, auth)
|
|
|
|
if err != nil {
|
|
|
|
return nil, nil, err
|
|
|
|
}
|
|
|
|
return plainText, keyId, err
|
|
|
|
}
|
|
|
|
|
|
|
|
func decryptKeyV1(keyProtected *encryptedKeyJSONV1, auth string) (keyBytes []byte, keyId []byte, err error) {
|
2021-06-30 11:40:54 +00:00
|
|
|
id, err := uuid.Parse(keyProtected.Id)
|
|
|
|
if err != nil {
|
|
|
|
return nil, nil, err
|
|
|
|
}
|
|
|
|
keyId = id[:]
|
|
|
|
|
2019-12-19 18:27:27 +00:00
|
|
|
mac, err := hex.DecodeString(keyProtected.Crypto.MAC)
|
|
|
|
if err != nil {
|
|
|
|
return nil, nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
iv, err := hex.DecodeString(keyProtected.Crypto.CipherParams.IV)
|
|
|
|
if err != nil {
|
|
|
|
return nil, nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
cipherText, err := hex.DecodeString(keyProtected.Crypto.CipherText)
|
|
|
|
if err != nil {
|
|
|
|
return nil, nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
derivedKey, err := getKDFKey(keyProtected.Crypto, auth)
|
|
|
|
if err != nil {
|
|
|
|
return nil, nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
calculatedMAC := crypto.Keccak256(derivedKey[16:32], cipherText)
|
|
|
|
if !bytes.Equal(calculatedMAC, mac) {
|
|
|
|
return nil, nil, ErrDecrypt
|
|
|
|
}
|
|
|
|
|
|
|
|
plainText, err := aesCBCDecrypt(crypto.Keccak256(derivedKey[:16])[:16], cipherText, iv)
|
|
|
|
if err != nil {
|
|
|
|
return nil, nil, err
|
|
|
|
}
|
|
|
|
return plainText, keyId, err
|
|
|
|
}
|
|
|
|
|
|
|
|
func decryptExtendedKey(keyProtected *encryptedKeyJSONV3, auth string) (plainText []byte, err error) {
|
|
|
|
if len(keyProtected.ExtendedKey.CipherText) == 0 {
|
|
|
|
return []byte(extkeys.EmptyExtendedKeyString), nil
|
|
|
|
}
|
|
|
|
|
|
|
|
if keyProtected.Version != version {
|
|
|
|
return nil, fmt.Errorf("Version not supported: %v", keyProtected.Version)
|
|
|
|
}
|
|
|
|
|
|
|
|
if keyProtected.ExtendedKey.Cipher != "aes-128-ctr" {
|
|
|
|
return nil, fmt.Errorf("Cipher not supported: %v", keyProtected.ExtendedKey.Cipher)
|
|
|
|
}
|
|
|
|
|
|
|
|
mac, err := hex.DecodeString(keyProtected.ExtendedKey.MAC)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
iv, err := hex.DecodeString(keyProtected.ExtendedKey.CipherParams.IV)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
cipherText, err := hex.DecodeString(keyProtected.ExtendedKey.CipherText)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
derivedKey, err := getKDFKey(keyProtected.ExtendedKey, auth)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
calculatedMAC := crypto.Keccak256(derivedKey[16:32], cipherText)
|
|
|
|
if !bytes.Equal(calculatedMAC, mac) {
|
|
|
|
return nil, ErrDecrypt
|
|
|
|
}
|
|
|
|
|
|
|
|
plainText, err = aesCTRXOR(derivedKey[:16], cipherText, iv)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
return plainText, err
|
|
|
|
}
|
|
|
|
|
|
|
|
func getKDFKey(cryptoJSON CryptoJSON, auth string) ([]byte, error) {
|
|
|
|
authArray := []byte(auth)
|
|
|
|
salt, err := hex.DecodeString(cryptoJSON.KDFParams["salt"].(string))
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
dkLen := ensureInt(cryptoJSON.KDFParams["dklen"])
|
|
|
|
|
|
|
|
if cryptoJSON.KDF == keyHeaderKDF {
|
|
|
|
n := ensureInt(cryptoJSON.KDFParams["n"])
|
|
|
|
r := ensureInt(cryptoJSON.KDFParams["r"])
|
|
|
|
p := ensureInt(cryptoJSON.KDFParams["p"])
|
|
|
|
return scrypt.Key(authArray, salt, n, r, p, dkLen)
|
|
|
|
|
|
|
|
} else if cryptoJSON.KDF == "pbkdf2" {
|
|
|
|
c := ensureInt(cryptoJSON.KDFParams["c"])
|
|
|
|
prf := cryptoJSON.KDFParams["prf"].(string)
|
|
|
|
if prf != "hmac-sha256" {
|
|
|
|
return nil, fmt.Errorf("Unsupported PBKDF2 PRF: %s", prf)
|
|
|
|
}
|
|
|
|
key := pbkdf2.Key(authArray, salt, c, dkLen, sha256.New)
|
|
|
|
return key, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil, fmt.Errorf("Unsupported KDF: %s", cryptoJSON.KDF)
|
|
|
|
}
|
|
|
|
|
|
|
|
// TODO: can we do without this when unmarshalling dynamic JSON?
|
|
|
|
// why do integers in KDF params end up as float64 and not int after
|
|
|
|
// unmarshal?
|
|
|
|
func ensureInt(x interface{}) int {
|
|
|
|
res, ok := x.(int)
|
|
|
|
if !ok {
|
|
|
|
res = int(x.(float64))
|
|
|
|
}
|
|
|
|
return res
|
|
|
|
}
|
|
|
|
|
|
|
|
func aesCTRXOR(key, inText, iv []byte) ([]byte, error) {
|
|
|
|
// AES-128 is selected due to size of encryptKey.
|
|
|
|
aesBlock, err := aes.NewCipher(key)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
stream := cipher.NewCTR(aesBlock, iv)
|
|
|
|
outText := make([]byte, len(inText))
|
|
|
|
stream.XORKeyStream(outText, inText)
|
|
|
|
return outText, err
|
|
|
|
}
|
|
|
|
|
|
|
|
func aesCBCDecrypt(key, cipherText, iv []byte) ([]byte, error) {
|
|
|
|
aesBlock, err := aes.NewCipher(key)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
decrypter := cipher.NewCBCDecrypter(aesBlock, iv)
|
|
|
|
paddedPlaintext := make([]byte, len(cipherText))
|
|
|
|
decrypter.CryptBlocks(paddedPlaintext, cipherText)
|
|
|
|
plaintext := pkcs7Unpad(paddedPlaintext)
|
|
|
|
if plaintext == nil {
|
|
|
|
return nil, ErrDecrypt
|
|
|
|
}
|
|
|
|
return plaintext, err
|
|
|
|
}
|
|
|
|
|
|
|
|
// From https://leanpub.com/gocrypto/read#leanpub-auto-block-cipher-modes
|
|
|
|
func pkcs7Unpad(in []byte) []byte {
|
|
|
|
if len(in) == 0 {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
padding := in[len(in)-1]
|
|
|
|
if int(padding) > len(in) || padding > aes.BlockSize {
|
|
|
|
return nil
|
|
|
|
} else if padding == 0 {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
for i := len(in) - 1; i > len(in)-int(padding)-1; i-- {
|
|
|
|
if in[i] != padding {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return in[:len(in)-int(padding)]
|
|
|
|
}
|
2021-06-23 09:21:21 +00:00
|
|
|
|
2021-06-30 11:40:54 +00:00
|
|
|
func RawKeyToCryptoJSON(rawKeyFile []byte) (cj CryptoJSON, e error) {
|
2021-06-23 09:21:21 +00:00
|
|
|
var keyJSON encryptedKeyJSONV3
|
|
|
|
if e := json.Unmarshal(rawKeyFile, &keyJSON); e != nil {
|
|
|
|
return cj, fmt.Errorf("failed to read key file: %s", e)
|
|
|
|
}
|
|
|
|
|
|
|
|
return keyJSON.Crypto, e
|
|
|
|
}
|