accounts, crypto: move keystore to package accounts

The account management API was originally implemented as a thin layer
around crypto.KeyStore, on the grounds that several kinds of key stores
would be implemented later on. It turns out that this won't happen so
KeyStore is a superflous abstraction.

In this commit crypto.KeyStore and everything related to it moves to
package accounts and is unexported.
This commit is contained in:
Felix Lange
2016-03-02 13:57:15 +01:00
parent dff9b4246f
commit 85e6c40c00
19 changed files with 256 additions and 241 deletions

View File

@ -17,8 +17,6 @@
package crypto
import (
"crypto/aes"
"crypto/cipher"
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
@ -30,7 +28,6 @@ import (
"os"
"encoding/hex"
"encoding/json"
"errors"
"github.com/ethereum/go-ethereum/common"
@ -38,8 +35,6 @@ import (
"github.com/ethereum/go-ethereum/crypto/secp256k1"
"github.com/ethereum/go-ethereum/crypto/sha3"
"github.com/ethereum/go-ethereum/rlp"
"github.com/pborman/uuid"
"golang.org/x/crypto/pbkdf2"
"golang.org/x/crypto/ripemd160"
)
@ -217,107 +212,6 @@ func Decrypt(prv *ecdsa.PrivateKey, ct []byte) ([]byte, error) {
return key.Decrypt(rand.Reader, ct, nil, nil)
}
// creates a Key and stores that in the given KeyStore by decrypting a presale key JSON
func ImportPreSaleKey(keyStore KeyStore, keyJSON []byte, password string) (*Key, error) {
key, err := decryptPreSaleKey(keyJSON, password)
if err != nil {
return nil, err
}
key.Id = uuid.NewRandom()
err = keyStore.StoreKey(key, password)
return key, err
}
func decryptPreSaleKey(fileContent []byte, password string) (key *Key, err error) {
preSaleKeyStruct := struct {
EncSeed string
EthAddr string
Email string
BtcAddr string
}{}
err = json.Unmarshal(fileContent, &preSaleKeyStruct)
if err != nil {
return nil, err
}
encSeedBytes, err := hex.DecodeString(preSaleKeyStruct.EncSeed)
iv := encSeedBytes[:16]
cipherText := encSeedBytes[16:]
/*
See https://github.com/ethereum/pyethsaletool
pyethsaletool generates the encryption key from password by
2000 rounds of PBKDF2 with HMAC-SHA-256 using password as salt (:().
16 byte key length within PBKDF2 and resulting key is used as AES key
*/
passBytes := []byte(password)
derivedKey := pbkdf2.Key(passBytes, passBytes, 2000, 16, sha256.New)
plainText, err := aesCBCDecrypt(derivedKey, cipherText, iv)
if err != nil {
return nil, err
}
ethPriv := Keccak256(plainText)
ecKey := ToECDSA(ethPriv)
key = &Key{
Id: nil,
Address: PubkeyToAddress(ecKey.PublicKey),
PrivateKey: ecKey,
}
derivedAddr := hex.EncodeToString(key.Address.Bytes()) // needed because .Hex() gives leading "0x"
expectedAddr := preSaleKeyStruct.EthAddr
if derivedAddr != expectedAddr {
err = fmt.Errorf("decrypted addr '%s' not equal to expected addr '%s'", derivedAddr, expectedAddr)
}
return key, err
}
// AES-128 is selected due to size of encryptKey
func aesCTRXOR(key, inText, iv []byte) ([]byte, error) {
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 {
err = errors.New("Decryption failed: PKCS7Unpad failed after AES decryption")
}
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)]
}
func PubkeyToAddress(p ecdsa.PublicKey) common.Address {
pubBytes := FromECDSAPub(&p)
return common.BytesToAddress(Keccak256(pubBytes[1:])[12:])

View File

@ -1,168 +0,0 @@
// Copyright 2014 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
package crypto
import (
"bytes"
"crypto/ecdsa"
"encoding/hex"
"encoding/json"
"io"
"strings"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto/secp256k1"
"github.com/pborman/uuid"
)
const (
version = 3
)
type Key struct {
Id uuid.UUID // Version 4 "random" for unique id not derived from key data
// to simplify lookups we also store the address
Address common.Address
// we only store privkey as pubkey/address can be derived from it
// privkey in this struct is always in plaintext
PrivateKey *ecdsa.PrivateKey
}
type plainKeyJSON struct {
Address string `json:"address"`
PrivateKey string `json:"privatekey"`
Id string `json:"id"`
Version int `json:"version"`
}
type encryptedKeyJSONV3 struct {
Address string `json:"address"`
Crypto cryptoJSON `json:"crypto"`
Id string `json:"id"`
Version int `json:"version"`
}
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"`
}
type scryptParamsJSON struct {
N int `json:"n"`
R int `json:"r"`
P int `json:"p"`
DkLen int `json:"dklen"`
Salt string `json:"salt"`
}
func (k *Key) MarshalJSON() (j []byte, err error) {
jStruct := plainKeyJSON{
hex.EncodeToString(k.Address[:]),
hex.EncodeToString(FromECDSA(k.PrivateKey)),
k.Id.String(),
version,
}
j, err = json.Marshal(jStruct)
return j, err
}
func (k *Key) UnmarshalJSON(j []byte) (err error) {
keyJSON := new(plainKeyJSON)
err = json.Unmarshal(j, &keyJSON)
if err != nil {
return err
}
u := new(uuid.UUID)
*u = uuid.Parse(keyJSON.Id)
k.Id = *u
addr, err := hex.DecodeString(keyJSON.Address)
if err != nil {
return err
}
privkey, err := hex.DecodeString(keyJSON.PrivateKey)
if err != nil {
return err
}
k.Address = common.BytesToAddress(addr)
k.PrivateKey = ToECDSA(privkey)
return nil
}
func NewKeyFromECDSA(privateKeyECDSA *ecdsa.PrivateKey) *Key {
id := uuid.NewRandom()
key := &Key{
Id: id,
Address: PubkeyToAddress(privateKeyECDSA.PublicKey),
PrivateKey: privateKeyECDSA,
}
return key
}
func NewKey(rand io.Reader) *Key {
randBytes := make([]byte, 64)
_, err := rand.Read(randBytes)
if err != nil {
panic("key generation: could not read from random source: " + err.Error())
}
reader := bytes.NewReader(randBytes)
privateKeyECDSA, err := ecdsa.GenerateKey(secp256k1.S256(), reader)
if err != nil {
panic("key generation: ecdsa.GenerateKey failed: " + err.Error())
}
return NewKeyFromECDSA(privateKeyECDSA)
}
// generate key whose address fits into < 155 bits so it can fit into
// the Direct ICAP spec. for simplicity and easier compatibility with
// other libs, we retry until the first byte is 0.
func NewKeyForDirectICAP(rand io.Reader) *Key {
randBytes := make([]byte, 64)
_, err := rand.Read(randBytes)
if err != nil {
panic("key generation: could not read from random source: " + err.Error())
}
reader := bytes.NewReader(randBytes)
privateKeyECDSA, err := ecdsa.GenerateKey(secp256k1.S256(), reader)
if err != nil {
panic("key generation: ecdsa.GenerateKey failed: " + err.Error())
}
key := NewKeyFromECDSA(privateKeyECDSA)
if !strings.HasPrefix(key.Address.Hex(), "0x00") {
return NewKeyForDirectICAP(rand)
}
return key
}

View File

@ -1,315 +0,0 @@
// Copyright 2014 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
/*
This key store behaves as KeyStorePlain with the difference that
the private key is encrypted and on disk uses another JSON encoding.
The crypto is documented at https://github.com/ethereum/wiki/wiki/Web3-Secret-Storage-Definition
*/
package crypto
import (
"bytes"
"crypto/aes"
"crypto/sha256"
"encoding/hex"
"encoding/json"
"errors"
"fmt"
"io"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto/randentropy"
"github.com/pborman/uuid"
"golang.org/x/crypto/pbkdf2"
"golang.org/x/crypto/scrypt"
)
const (
keyHeaderKDF = "scrypt"
// n,r,p = 2^18, 8, 1 uses 256MB memory and approx 1s CPU time on a modern CPU.
StandardScryptN = 1 << 18
StandardScryptP = 1
// n,r,p = 2^12, 8, 6 uses 4MB memory and approx 100ms CPU time on a modern CPU.
LightScryptN = 1 << 12
LightScryptP = 6
scryptR = 8
scryptDKLen = 32
)
type keyStorePassphrase struct {
keysDirPath string
scryptN int
scryptP int
}
func NewKeyStorePassphrase(path string, scryptN int, scryptP int) KeyStore {
return &keyStorePassphrase{path, scryptN, scryptP}
}
func (ks keyStorePassphrase) GenerateNewKey(rand io.Reader, auth string) (key *Key, err error) {
return GenerateNewKeyDefault(ks, rand, auth)
}
func (ks keyStorePassphrase) GetKey(keyAddr common.Address, auth string) (key *Key, err error) {
return decryptKeyFromFile(ks.keysDirPath, keyAddr, auth)
}
func (ks keyStorePassphrase) Cleanup(keyAddr common.Address) (err error) {
return cleanup(ks.keysDirPath, keyAddr)
}
func (ks keyStorePassphrase) GetKeyAddresses() (addresses []common.Address, err error) {
return getKeyAddresses(ks.keysDirPath)
}
func (ks keyStorePassphrase) StoreKey(key *Key, auth string) error {
keyjson, err := EncryptKey(key, auth, ks.scryptN, ks.scryptP)
if err != nil {
return err
}
return writeKeyFile(key.Address, ks.keysDirPath, keyjson)
}
// EncryptKey encrypts a key using the specified scrypt parameters into a json
// blob that can be decrypted later on.
func EncryptKey(key *Key, auth string, scryptN, scryptP int) ([]byte, error) {
authArray := []byte(auth)
salt := randentropy.GetEntropyCSPRNG(32)
derivedKey, err := scrypt.Key(authArray, salt, scryptN, scryptR, scryptP, scryptDKLen)
if err != nil {
return nil, err
}
encryptKey := derivedKey[:16]
keyBytes := FromECDSA(key.PrivateKey)
iv := randentropy.GetEntropyCSPRNG(aes.BlockSize) // 16
cipherText, err := aesCTRXOR(encryptKey, keyBytes, iv)
if err != nil {
return nil, err
}
mac := Keccak256(derivedKey[16:32], cipherText)
scryptParamsJSON := make(map[string]interface{}, 5)
scryptParamsJSON["n"] = scryptN
scryptParamsJSON["r"] = scryptR
scryptParamsJSON["p"] = scryptP
scryptParamsJSON["dklen"] = scryptDKLen
scryptParamsJSON["salt"] = hex.EncodeToString(salt)
cipherParamsJSON := cipherparamsJSON{
IV: hex.EncodeToString(iv),
}
cryptoStruct := cryptoJSON{
Cipher: "aes-128-ctr",
CipherText: hex.EncodeToString(cipherText),
CipherParams: cipherParamsJSON,
KDF: "scrypt",
KDFParams: scryptParamsJSON,
MAC: hex.EncodeToString(mac),
}
encryptedKeyJSONV3 := encryptedKeyJSONV3{
hex.EncodeToString(key.Address[:]),
cryptoStruct,
key.Id.String(),
version,
}
return json.Marshal(encryptedKeyJSONV3)
}
func (ks keyStorePassphrase) DeleteKey(keyAddr common.Address, auth string) error {
// only delete if correct passphrase is given
if _, err := decryptKeyFromFile(ks.keysDirPath, keyAddr, auth); err != nil {
return err
}
return deleteKey(ks.keysDirPath, keyAddr)
}
// DecryptKey decrypts a key from a json blob, returning the private key itself.
func DecryptKey(keyjson []byte, auth string) (*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
)
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)
} else {
k := new(encryptedKeyJSONV3)
if err := json.Unmarshal(keyjson, k); err != nil {
return nil, err
}
keyBytes, keyId, err = decryptKeyV3(k, auth)
}
// Handle any decryption errors and return the key
if err != nil {
return nil, err
}
key := ToECDSA(keyBytes)
return &Key{
Id: uuid.UUID(keyId),
Address: PubkeyToAddress(key.PublicKey),
PrivateKey: key,
}, nil
}
func decryptKeyFromFile(keysDirPath string, keyAddr common.Address, auth string) (*Key, error) {
// Load the key from the keystore and decrypt its contents
keyjson, err := getKeyFile(keysDirPath, keyAddr)
if err != nil {
return nil, err
}
key, err := DecryptKey(keyjson, auth)
if err != nil {
return nil, err
}
// Make sure we're really operating on the requested key (no swap attacks)
if keyAddr != key.Address {
return nil, fmt.Errorf("key content mismatch: have account %x, want %x", key.Address, keyAddr)
}
return key, nil
}
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)
}
if keyProtected.Crypto.Cipher != "aes-128-ctr" {
return nil, nil, fmt.Errorf("Cipher not supported: %v", keyProtected.Crypto.Cipher)
}
keyId = uuid.Parse(keyProtected.Id)
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 := Keccak256(derivedKey[16:32], cipherText)
if !bytes.Equal(calculatedMAC, mac) {
return nil, nil, errors.New("Decryption failed: MAC mismatch")
}
plainText, err := aesCTRXOR(derivedKey[:16], cipherText, iv)
if err != nil {
return nil, nil, err
}
return plainText, keyId, err
}
func decryptKeyV1(keyProtected *encryptedKeyJSONV1, auth string) (keyBytes []byte, keyId []byte, err error) {
keyId = uuid.Parse(keyProtected.Id)
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 := Keccak256(derivedKey[16:32], cipherText)
if !bytes.Equal(calculatedMAC, mac) {
return nil, nil, errors.New("Decryption failed: MAC mismatch")
}
plainText, err := aesCBCDecrypt(Keccak256(derivedKey[:16])[:16], cipherText, iv)
if err != nil {
return nil, nil, err
}
return plainText, keyId, 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 == "scrypt" {
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: ", prf)
}
key := pbkdf2.Key(authArray, salt, c, dkLen, sha256.New)
return key, nil
}
return nil, fmt.Errorf("Unsupported KDF: ", 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
}

View File

@ -1,51 +0,0 @@
// Copyright 2016 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
package crypto
import (
"testing"
"github.com/ethereum/go-ethereum/common"
)
// Tests that a json key file can be decrypted and encrypted in multiple rounds.
func TestKeyEncryptDecrypt(t *testing.T) {
address := common.HexToAddress("f626acac23772cbe04dd578bee681b06bdefb9fa")
keyjson := []byte("{\"address\":\"f626acac23772cbe04dd578bee681b06bdefb9fa\",\"crypto\":{\"cipher\":\"aes-128-ctr\",\"ciphertext\":\"1bcf0ab9b14459795ce59f63e63255ffd84dc38d31614a5a78e37144d7e4a17f\",\"cipherparams\":{\"iv\":\"df4c7e225ee2d81adef522013e3fbe24\"},\"kdf\":\"scrypt\",\"kdfparams\":{\"dklen\":32,\"n\":262144,\"p\":1,\"r\":8,\"salt\":\"2909a99dd2bfa7079a4b40991773b1083f8512c0c55b9b63402ab0e3dc8db8b3\"},\"mac\":\"4ecf6a4ad92ae2c016cb7c44abade74799480c3303eb024661270dfefdbc7510\"},\"id\":\"b4718210-9a30-4883-b8a6-dbdd08bd0ceb\",\"version\":3}")
password := ""
// Do a few rounds of decryption and encryption
for i := 0; i < 3; i++ {
// Try a bad password first
if _, err := DecryptKey(keyjson, password+"bad"); err == nil {
t.Error("test %d: json key decrypted with bad password", i)
}
// Decrypt with the correct password
key, err := DecryptKey(keyjson, password)
if err != nil {
t.Errorf("test %d: json key failed to decrypt: %v", i, err)
}
if key.Address != address {
t.Errorf("test %d: key address mismatch: have %x, want %x", i, key.Address, address)
}
// Recrypt with a new password and start over
password += "new data appended"
if keyjson, err = EncryptKey(key, password, LightScryptN, LightScryptP); err != nil {
t.Errorf("test %d: failed to recrypt key %v", err)
}
}
}

View File

@ -1,209 +0,0 @@
// Copyright 2014 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
package crypto
import (
"encoding/hex"
"encoding/json"
"fmt"
"io"
"io/ioutil"
"os"
"path/filepath"
"time"
"github.com/ethereum/go-ethereum/common"
)
type KeyStore interface {
// create new key using io.Reader entropy source and optionally using auth string
GenerateNewKey(io.Reader, string) (*Key, error)
GetKey(common.Address, string) (*Key, error) // get key from addr and auth string
GetKeyAddresses() ([]common.Address, error) // get all addresses
StoreKey(*Key, string) error // store key optionally using auth string
DeleteKey(common.Address, string) error // delete key by addr and auth string
Cleanup(keyAddr common.Address) (err error)
}
type keyStorePlain struct {
keysDirPath string
}
func NewKeyStorePlain(path string) KeyStore {
return &keyStorePlain{path}
}
func (ks keyStorePlain) GenerateNewKey(rand io.Reader, auth string) (key *Key, err error) {
return GenerateNewKeyDefault(ks, rand, auth)
}
func GenerateNewKeyDefault(ks KeyStore, rand io.Reader, auth string) (key *Key, err error) {
defer func() {
if r := recover(); r != nil {
err = fmt.Errorf("GenerateNewKey error: %v", r)
}
}()
key = NewKey(rand)
err = ks.StoreKey(key, auth)
return key, err
}
func (ks keyStorePlain) GetKey(keyAddr common.Address, auth string) (*Key, error) {
keyjson, err := getKeyFile(ks.keysDirPath, keyAddr)
if err != nil {
return nil, err
}
key := new(Key)
if err := json.Unmarshal(keyjson, key); err != nil {
return nil, err
}
return key, nil
}
func (ks keyStorePlain) GetKeyAddresses() (addresses []common.Address, err error) {
return getKeyAddresses(ks.keysDirPath)
}
func (ks keyStorePlain) Cleanup(keyAddr common.Address) (err error) {
return cleanup(ks.keysDirPath, keyAddr)
}
func (ks keyStorePlain) StoreKey(key *Key, auth string) (err error) {
keyJSON, err := json.Marshal(key)
if err != nil {
return
}
err = writeKeyFile(key.Address, ks.keysDirPath, keyJSON)
return
}
func (ks keyStorePlain) DeleteKey(keyAddr common.Address, auth string) (err error) {
return deleteKey(ks.keysDirPath, keyAddr)
}
func deleteKey(keysDirPath string, keyAddr common.Address) (err error) {
var path string
path, err = getKeyFilePath(keysDirPath, keyAddr)
if err == nil {
addrHex := hex.EncodeToString(keyAddr[:])
if path == filepath.Join(keysDirPath, addrHex, addrHex) {
path = filepath.Join(keysDirPath, addrHex)
}
err = os.RemoveAll(path)
}
return
}
func getKeyFilePath(keysDirPath string, keyAddr common.Address) (keyFilePath string, err error) {
addrHex := hex.EncodeToString(keyAddr[:])
matches, err := filepath.Glob(filepath.Join(keysDirPath, fmt.Sprintf("*--%s", addrHex)))
if len(matches) > 0 {
if err == nil {
keyFilePath = matches[len(matches)-1]
}
return
}
keyFilePath = filepath.Join(keysDirPath, addrHex, addrHex)
_, err = os.Stat(keyFilePath)
return
}
func cleanup(keysDirPath string, keyAddr common.Address) (err error) {
fileInfos, err := ioutil.ReadDir(keysDirPath)
if err != nil {
return
}
var paths []string
account := hex.EncodeToString(keyAddr[:])
for _, fileInfo := range fileInfos {
path := filepath.Join(keysDirPath, fileInfo.Name())
if len(path) >= 40 {
addr := path[len(path)-40 : len(path)]
if addr == account {
if path == filepath.Join(keysDirPath, addr, addr) {
path = filepath.Join(keysDirPath, addr)
}
paths = append(paths, path)
}
}
}
if len(paths) > 1 {
for i := 0; err == nil && i < len(paths)-1; i++ {
err = os.RemoveAll(paths[i])
if err != nil {
break
}
}
}
return
}
func getKeyFile(keysDirPath string, keyAddr common.Address) (fileContent []byte, err error) {
var keyFilePath string
keyFilePath, err = getKeyFilePath(keysDirPath, keyAddr)
if err == nil {
fileContent, err = ioutil.ReadFile(keyFilePath)
}
return
}
func writeKeyFile(addr common.Address, keysDirPath string, content []byte) (err error) {
filename := keyFileName(addr)
// read, write and dir search for user
err = os.MkdirAll(keysDirPath, 0700)
if err != nil {
return err
}
// read, write for user
return ioutil.WriteFile(filepath.Join(keysDirPath, filename), content, 0600)
}
// keyFilePath implements the naming convention for keyfiles:
// UTC--<created_at UTC ISO8601>-<address hex>
func keyFileName(keyAddr common.Address) string {
ts := time.Now().UTC()
return fmt.Sprintf("UTC--%s--%s", toISO8601(ts), hex.EncodeToString(keyAddr[:]))
}
func toISO8601(t time.Time) string {
var tz string
name, offset := t.Zone()
if name == "UTC" {
tz = "Z"
} else {
tz = fmt.Sprintf("%03d00", offset/3600)
}
return fmt.Sprintf("%04d-%02d-%02dT%02d-%02d-%02d.%09d%s", t.Year(), t.Month(), t.Day(), t.Hour(), t.Minute(), t.Second(), t.Nanosecond(), tz)
}
func getKeyAddresses(keysDirPath string) (addresses []common.Address, err error) {
fileInfos, err := ioutil.ReadDir(keysDirPath)
if err != nil {
return nil, err
}
for _, fileInfo := range fileInfos {
filename := fileInfo.Name()
if len(filename) >= 40 {
addr := filename[len(filename)-40 : len(filename)]
address, err := hex.DecodeString(addr)
if err == nil {
addresses = append(addresses, common.BytesToAddress(address))
}
}
}
return addresses, err
}

View File

@ -1,233 +0,0 @@
// Copyright 2014 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
package crypto
import (
"encoding/hex"
"fmt"
"reflect"
"strings"
"testing"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto/randentropy"
)
func TestKeyStorePlain(t *testing.T) {
ks := NewKeyStorePlain(common.DefaultDataDir())
pass := "" // not used but required by API
k1, err := ks.GenerateNewKey(randentropy.Reader, pass)
if err != nil {
t.Fatal(err)
}
k2 := new(Key)
k2, err = ks.GetKey(k1.Address, pass)
if err != nil {
t.Fatal(err)
}
if !reflect.DeepEqual(k1.Address, k2.Address) {
t.Fatal(err)
}
if !reflect.DeepEqual(k1.PrivateKey, k2.PrivateKey) {
t.Fatal(err)
}
err = ks.DeleteKey(k2.Address, pass)
if err != nil {
t.Fatal(err)
}
}
func TestKeyStorePassphrase(t *testing.T) {
ks := NewKeyStorePassphrase(common.DefaultDataDir(), LightScryptN, LightScryptP)
pass := "foo"
k1, err := ks.GenerateNewKey(randentropy.Reader, pass)
if err != nil {
t.Fatal(err)
}
k2 := new(Key)
k2, err = ks.GetKey(k1.Address, pass)
if err != nil {
t.Fatal(err)
}
if !reflect.DeepEqual(k1.Address, k2.Address) {
t.Fatal(err)
}
if !reflect.DeepEqual(k1.PrivateKey, k2.PrivateKey) {
t.Fatal(err)
}
err = ks.DeleteKey(k2.Address, pass) // also to clean up created files
if err != nil {
t.Fatal(err)
}
}
func TestKeyStorePassphraseDecryptionFail(t *testing.T) {
ks := NewKeyStorePassphrase(common.DefaultDataDir(), LightScryptN, LightScryptP)
pass := "foo"
k1, err := ks.GenerateNewKey(randentropy.Reader, pass)
if err != nil {
t.Fatal(err)
}
_, err = ks.GetKey(k1.Address, "bar") // wrong passphrase
if err == nil {
t.Fatal(err)
}
err = ks.DeleteKey(k1.Address, "bar") // wrong passphrase
if err == nil {
t.Fatal(err)
}
err = ks.DeleteKey(k1.Address, pass) // to clean up
if err != nil {
t.Fatal(err)
}
}
func TestImportPreSaleKey(t *testing.T) {
// file content of a presale key file generated with:
// python pyethsaletool.py genwallet
// with password "foo"
fileContent := "{\"encseed\": \"26d87f5f2bf9835f9a47eefae571bc09f9107bb13d54ff12a4ec095d01f83897494cf34f7bed2ed34126ecba9db7b62de56c9d7cd136520a0427bfb11b8954ba7ac39b90d4650d3448e31185affcd74226a68f1e94b1108e6e0a4a91cdd83eba\", \"ethaddr\": \"d4584b5f6229b7be90727b0fc8c6b91bb427821f\", \"email\": \"gustav.simonsson@gmail.com\", \"btcaddr\": \"1EVknXyFC68kKNLkh6YnKzW41svSRoaAcx\"}"
ks := NewKeyStorePassphrase(common.DefaultDataDir(), LightScryptN, LightScryptP)
pass := "foo"
_, err := ImportPreSaleKey(ks, []byte(fileContent), pass)
if err != nil {
t.Fatal(err)
}
}
// Test and utils for the key store tests in the Ethereum JSON tests;
// tests/KeyStoreTests/basic_tests.json
type KeyStoreTestV3 struct {
Json encryptedKeyJSONV3
Password string
Priv string
}
type KeyStoreTestV1 struct {
Json encryptedKeyJSONV1
Password string
Priv string
}
func TestV3_PBKDF2_1(t *testing.T) {
tests := loadKeyStoreTestV3("tests/v3_test_vector.json", t)
testDecryptV3(tests["wikipage_test_vector_pbkdf2"], t)
}
func TestV3_PBKDF2_2(t *testing.T) {
tests := loadKeyStoreTestV3("../tests/files/KeyStoreTests/basic_tests.json", t)
testDecryptV3(tests["test1"], t)
}
func TestV3_PBKDF2_3(t *testing.T) {
tests := loadKeyStoreTestV3("../tests/files/KeyStoreTests/basic_tests.json", t)
testDecryptV3(tests["python_generated_test_with_odd_iv"], t)
}
func TestV3_PBKDF2_4(t *testing.T) {
tests := loadKeyStoreTestV3("../tests/files/KeyStoreTests/basic_tests.json", t)
testDecryptV3(tests["evilnonce"], t)
}
func TestV3_Scrypt_1(t *testing.T) {
tests := loadKeyStoreTestV3("tests/v3_test_vector.json", t)
testDecryptV3(tests["wikipage_test_vector_scrypt"], t)
}
func TestV3_Scrypt_2(t *testing.T) {
tests := loadKeyStoreTestV3("../tests/files/KeyStoreTests/basic_tests.json", t)
testDecryptV3(tests["test2"], t)
}
func TestV1_1(t *testing.T) {
tests := loadKeyStoreTestV1("tests/v1_test_vector.json", t)
testDecryptV1(tests["test1"], t)
}
func TestV1_2(t *testing.T) {
ks := NewKeyStorePassphrase("tests/v1", LightScryptN, LightScryptP)
addr := common.HexToAddress("cb61d5a9c4896fb9658090b597ef0e7be6f7b67e")
k, err := ks.GetKey(addr, "g")
if err != nil {
t.Fatal(err)
}
if k.Address != addr {
t.Fatal(fmt.Errorf("Unexpected address: %v, expected %v", k.Address, addr))
}
privHex := hex.EncodeToString(FromECDSA(k.PrivateKey))
expectedHex := "d1b1178d3529626a1a93e073f65028370d14c7eb0936eb42abef05db6f37ad7d"
if privHex != expectedHex {
t.Fatal(fmt.Errorf("Unexpected privkey: %v, expected %v", privHex, expectedHex))
}
}
func testDecryptV3(test KeyStoreTestV3, t *testing.T) {
privBytes, _, err := decryptKeyV3(&test.Json, test.Password)
if err != nil {
t.Fatal(err)
}
privHex := hex.EncodeToString(privBytes)
if test.Priv != privHex {
t.Fatal(fmt.Errorf("Decrypted bytes not equal to test, expected %v have %v", test.Priv, privHex))
}
}
func testDecryptV1(test KeyStoreTestV1, t *testing.T) {
privBytes, _, err := decryptKeyV1(&test.Json, test.Password)
if err != nil {
t.Fatal(err)
}
privHex := hex.EncodeToString(privBytes)
if test.Priv != privHex {
t.Fatal(fmt.Errorf("Decrypted bytes not equal to test, expected %v have %v", test.Priv, privHex))
}
}
func loadKeyStoreTestV3(file string, t *testing.T) map[string]KeyStoreTestV3 {
tests := make(map[string]KeyStoreTestV3)
err := common.LoadJSON(file, &tests)
if err != nil {
t.Fatal(err)
}
return tests
}
func loadKeyStoreTestV1(file string, t *testing.T) map[string]KeyStoreTestV1 {
tests := make(map[string]KeyStoreTestV1)
err := common.LoadJSON(file, &tests)
if err != nil {
t.Fatal(err)
}
return tests
}
func TestKeyForDirectICAP(t *testing.T) {
key := NewKeyForDirectICAP(randentropy.Reader)
if !strings.HasPrefix(key.Address.Hex(), "0x00") {
t.Errorf("Expected first address byte to be zero, have: %s", key.Address.Hex())
}
}

View File

@ -1 +0,0 @@
{"address":"cb61d5a9c4896fb9658090b597ef0e7be6f7b67e","Crypto":{"cipher":"aes-128-cbc","ciphertext":"6143d3192db8b66eabd693d9c4e414dcfaee52abda451af79ccf474dafb35f1bfc7ea013aa9d2ee35969a1a2e8d752d0","cipherparams":{"iv":"35337770fc2117994ecdcad026bccff4"},"kdf":"scrypt","kdfparams":{"n":262144,"r":8,"p":1,"dklen":32,"salt":"9afcddebca541253a2f4053391c673ff9fe23097cd8555d149d929e4ccf1257f"},"mac":"3f3d5af884b17a100b0b3232c0636c230a54dc2ac8d986227219b0dd89197644","version":"1"},"id":"e25f7c1f-d318-4f29-b62c-687190d4d299","version":"1"}

View File

@ -1,28 +0,0 @@
{
"test1": {
"json": {
"Crypto": {
"cipher": "aes-128-cbc",
"cipherparams": {
"iv": "35337770fc2117994ecdcad026bccff4"
},
"ciphertext": "6143d3192db8b66eabd693d9c4e414dcfaee52abda451af79ccf474dafb35f1bfc7ea013aa9d2ee35969a1a2e8d752d0",
"kdf": "scrypt",
"kdfparams": {
"dklen": 32,
"n": 262144,
"p": 1,
"r": 8,
"salt": "9afcddebca541253a2f4053391c673ff9fe23097cd8555d149d929e4ccf1257f"
},
"mac": "3f3d5af884b17a100b0b3232c0636c230a54dc2ac8d986227219b0dd89197644",
"version": "1"
},
"address": "cb61d5a9c4896fb9658090b597ef0e7be6f7b67e",
"id": "e25f7c1f-d318-4f29-b62c-687190d4d299",
"version": "1"
},
"password": "g",
"priv": "d1b1178d3529626a1a93e073f65028370d14c7eb0936eb42abef05db6f37ad7d"
}
}

View File

@ -1,49 +0,0 @@
{
"wikipage_test_vector_scrypt": {
"json": {
"crypto" : {
"cipher" : "aes-128-ctr",
"cipherparams" : {
"iv" : "83dbcc02d8ccb40e466191a123791e0e"
},
"ciphertext" : "d172bf743a674da9cdad04534d56926ef8358534d458fffccd4e6ad2fbde479c",
"kdf" : "scrypt",
"kdfparams" : {
"dklen" : 32,
"n" : 262144,
"r" : 1,
"p" : 8,
"salt" : "ab0c7876052600dd703518d6fc3fe8984592145b591fc8fb5c6d43190334ba19"
},
"mac" : "2103ac29920d71da29f15d75b4a16dbe95cfd7ff8faea1056c33131d846e3097"
},
"id" : "3198bc9c-6672-5ab3-d995-4942343ae5b6",
"version" : 3
},
"password": "testpassword",
"priv": "7a28b5ba57c53603b0b07b56bba752f7784bf506fa95edc395f5cf6c7514fe9d"
},
"wikipage_test_vector_pbkdf2": {
"json": {
"crypto" : {
"cipher" : "aes-128-ctr",
"cipherparams" : {
"iv" : "6087dab2f9fdbbfaddc31a909735c1e6"
},
"ciphertext" : "5318b4d5bcd28de64ee5559e671353e16f075ecae9f99c7a79a38af5f869aa46",
"kdf" : "pbkdf2",
"kdfparams" : {
"c" : 262144,
"dklen" : 32,
"prf" : "hmac-sha256",
"salt" : "ae3cd4e7013836a3df6bd7241b12db061dbe2c6785853cce422d148a624ce0bd"
},
"mac" : "517ead924a9d0dc3124507e3393d175ce3ff7c1e96529c6c555ce9e51205e9b2"
},
"id" : "3198bc9c-6672-5ab3-d995-4942343ae5b6",
"version" : 3
},
"password": "testpassword",
"priv": "7a28b5ba57c53603b0b07b56bba752f7784bf506fa95edc395f5cf6c7514fe9d"
}
}