Merge branch 'develop' of github.com-obscure:ethereum/go-ethereum into develop
This commit is contained in:
		
						commit
						4dd7be7ed0
					
				
							
								
								
									
										5
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										5
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @ -10,3 +10,8 @@ | |||||||
| .DS_Store | .DS_Store | ||||||
| */**/.DS_Store | */**/.DS_Store | ||||||
| .ethtest | .ethtest | ||||||
|  | 
 | ||||||
|  | #* | ||||||
|  | .#* | ||||||
|  | *# | ||||||
|  | *~ | ||||||
|  | |||||||
							
								
								
									
										107
									
								
								crypto/key.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										107
									
								
								crypto/key.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,107 @@ | |||||||
|  | /* | ||||||
|  | 	This file is part of go-ethereum | ||||||
|  | 
 | ||||||
|  | 	go-ethereum 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. | ||||||
|  | 
 | ||||||
|  | 	go-ethereum 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 General Public License for more details. | ||||||
|  | 
 | ||||||
|  | 	You should have received a copy of the GNU Lesser General Public License | ||||||
|  | 	along with go-ethereum.  If not, see <http://www.gnu.org/licenses/>.
 | ||||||
|  | */ | ||||||
|  | /** | ||||||
|  |  * @authors | ||||||
|  |  * 	Gustav Simonsson <gustav.simonsson@gmail.com> | ||||||
|  |  * @date 2015 | ||||||
|  |  * | ||||||
|  |  */ | ||||||
|  | 
 | ||||||
|  | package crypto | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"bytes" | ||||||
|  | 	"code.google.com/p/go-uuid/uuid" | ||||||
|  | 	"crypto/ecdsa" | ||||||
|  | 	"crypto/elliptic" | ||||||
|  | 	"encoding/json" | ||||||
|  | 	"io" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | type Key struct { | ||||||
|  | 	Id *uuid.UUID // Version 4 "random" for unique id not derived from key data
 | ||||||
|  | 	// 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 { | ||||||
|  | 	Id         []byte | ||||||
|  | 	PrivateKey []byte | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type cipherJSON struct { | ||||||
|  | 	Salt       []byte | ||||||
|  | 	IV         []byte | ||||||
|  | 	CipherText []byte | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type encryptedKeyJSON struct { | ||||||
|  | 	Id     []byte | ||||||
|  | 	Crypto cipherJSON | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (k *Key) Address() []byte { | ||||||
|  | 	pubBytes := FromECDSAPub(&k.PrivateKey.PublicKey) | ||||||
|  | 	return Sha3(pubBytes)[12:] | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (k *Key) MarshalJSON() (j []byte, err error) { | ||||||
|  | 	jStruct := plainKeyJSON{ | ||||||
|  | 		*k.Id, | ||||||
|  | 		FromECDSA(k.PrivateKey), | ||||||
|  | 	} | ||||||
|  | 	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 = keyJSON.Id | ||||||
|  | 	k.Id = u | ||||||
|  | 
 | ||||||
|  | 	k.PrivateKey = ToECDSA(keyJSON.PrivateKey) | ||||||
|  | 
 | ||||||
|  | 	return err | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func NewKey(rand io.Reader) *Key { | ||||||
|  | 	randBytes := make([]byte, 32) | ||||||
|  | 	_, err := rand.Read(randBytes) | ||||||
|  | 	if err != nil { | ||||||
|  | 		panic("key generation: could not read from random source: " + err.Error()) | ||||||
|  | 	} | ||||||
|  | 	reader := bytes.NewReader(randBytes) | ||||||
|  | 	_, x, y, err := elliptic.GenerateKey(S256(), reader) | ||||||
|  | 	if err != nil { | ||||||
|  | 		panic("key generation: elliptic.GenerateKey failed: " + err.Error()) | ||||||
|  | 	} | ||||||
|  | 	privateKeyMarshalled := elliptic.Marshal(S256(), x, y) | ||||||
|  | 	privateKeyECDSA := ToECDSA(privateKeyMarshalled) | ||||||
|  | 
 | ||||||
|  | 	key := new(Key) | ||||||
|  | 	id := uuid.NewRandom() | ||||||
|  | 	key.Id = &id | ||||||
|  | 	key.PrivateKey = privateKeyECDSA | ||||||
|  | 	return key | ||||||
|  | } | ||||||
							
								
								
									
										245
									
								
								crypto/key_store_passphrase.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										245
									
								
								crypto/key_store_passphrase.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,245 @@ | |||||||
|  | /* | ||||||
|  | 	This file is part of go-ethereum | ||||||
|  | 
 | ||||||
|  | 	go-ethereum 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. | ||||||
|  | 
 | ||||||
|  | 	go-ethereum 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 General Public License for more details. | ||||||
|  | 
 | ||||||
|  | 	You should have received a copy of the GNU Lesser General Public License | ||||||
|  | 	along with go-ethereum.  If not, see <http://www.gnu.org/licenses/>.
 | ||||||
|  | */ | ||||||
|  | /** | ||||||
|  |  * @authors | ||||||
|  |  * 	Gustav Simonsson <gustav.simonsson@gmail.com> | ||||||
|  |  * @date 2015 | ||||||
|  |  * | ||||||
|  |  */ | ||||||
|  | /* | ||||||
|  | 
 | ||||||
|  | This key store behaves as KeyStorePlain with the difference that | ||||||
|  | the private key is encrypted and on disk uses another JSON encoding. | ||||||
|  | 
 | ||||||
|  | Cryptography: | ||||||
|  | 
 | ||||||
|  | 1. Encryption key is scrypt derived key from user passphrase. Scrypt parameters | ||||||
|  |    (work factors) [1][2] are defined as constants below. | ||||||
|  | 2. Scrypt salt is 32 random bytes from CSPRNG. It is appended to ciphertext. | ||||||
|  | 3. Checksum is SHA3 of the private key bytes. | ||||||
|  | 4. Plaintext is concatenation of private key bytes and checksum. | ||||||
|  | 5. Encryption algo is AES 256 CBC [3][4] | ||||||
|  | 6. CBC IV is 16 random bytes from CSPRNG. It is appended to ciphertext. | ||||||
|  | 7. Plaintext padding is PKCS #7 [5][6] | ||||||
|  | 
 | ||||||
|  | Encoding: | ||||||
|  | 
 | ||||||
|  | 1. On disk, ciphertext, salt and IV are encoded in a nested JSON object. | ||||||
|  |    cat a key file to see the structure. | ||||||
|  | 2. byte arrays are base64 JSON strings. | ||||||
|  | 3. The EC private key bytes are in uncompressed form [7]. | ||||||
|  |    They are a big-endian byte slice of the absolute value of D [8][9]. | ||||||
|  | 4. The checksum is the last 32 bytes of the plaintext byte array and the | ||||||
|  |    private key is the preceeding bytes. | ||||||
|  | 
 | ||||||
|  | References: | ||||||
|  | 
 | ||||||
|  | 1. http://www.tarsnap.com/scrypt/scrypt-slides.pdf
 | ||||||
|  | 2. http://stackoverflow.com/questions/11126315/what-are-optimal-scrypt-work-factors
 | ||||||
|  | 3. http://en.wikipedia.org/wiki/Advanced_Encryption_Standard
 | ||||||
|  | 4. http://en.wikipedia.org/wiki/Block_cipher_mode_of_operation#Cipher-block_chaining_.28CBC.29
 | ||||||
|  | 5. https://leanpub.com/gocrypto/read#leanpub-auto-block-cipher-modes
 | ||||||
|  | 6. http://tools.ietf.org/html/rfc2315
 | ||||||
|  | 7. http://bitcoin.stackexchange.com/questions/3059/what-is-a-compressed-bitcoin-key
 | ||||||
|  | 8. http://golang.org/pkg/crypto/ecdsa/#PrivateKey
 | ||||||
|  | 9. https://golang.org/pkg/math/big/#Int.Bytes
 | ||||||
|  | 
 | ||||||
|  | */ | ||||||
|  | 
 | ||||||
|  | package crypto | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"bytes" | ||||||
|  | 	"code.google.com/p/go-uuid/uuid" | ||||||
|  | 	"code.google.com/p/go.crypto/scrypt" | ||||||
|  | 	"crypto/aes" | ||||||
|  | 	"crypto/cipher" | ||||||
|  | 	crand "crypto/rand" | ||||||
|  | 	"encoding/json" | ||||||
|  | 	"errors" | ||||||
|  | 	"io" | ||||||
|  | 	"os" | ||||||
|  | 	"path" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | const ( | ||||||
|  | 	// 2^18 / 8 / 1 uses 256MB memory and approx 1s CPU time on a modern CPU.
 | ||||||
|  | 	scryptN     = 1 << 18 | ||||||
|  | 	scryptr     = 8 | ||||||
|  | 	scryptp     = 1 | ||||||
|  | 	scryptdkLen = 32 | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | type keyStorePassphrase struct { | ||||||
|  | 	keysDirPath string | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func NewKeyStorePassphrase(path string) KeyStore2 { | ||||||
|  | 	return &keyStorePassphrase{path} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (ks keyStorePassphrase) GenerateNewKey(rand io.Reader, auth string) (key *Key, err error) { | ||||||
|  | 	return GenerateNewKeyDefault(ks, rand, auth) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (ks keyStorePassphrase) GetKey(keyId *uuid.UUID, auth string) (key *Key, err error) { | ||||||
|  | 	keyBytes, err := DecryptKey(ks, keyId, auth) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 	key = &Key{ | ||||||
|  | 		Id:         keyId, | ||||||
|  | 		PrivateKey: ToECDSA(keyBytes), | ||||||
|  | 	} | ||||||
|  | 	return key, err | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (ks keyStorePassphrase) StoreKey(key *Key, auth string) (err error) { | ||||||
|  | 	authArray := []byte(auth) | ||||||
|  | 	salt := getEntropyCSPRNG(32) | ||||||
|  | 	derivedKey, err := scrypt.Key(authArray, salt, scryptN, scryptr, scryptp, scryptdkLen) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	keyBytes := FromECDSA(key.PrivateKey) | ||||||
|  | 	keyBytesHash := Sha3(keyBytes) | ||||||
|  | 	toEncrypt := PKCS7Pad(append(keyBytes, keyBytesHash...)) | ||||||
|  | 
 | ||||||
|  | 	AES256Block, err := aes.NewCipher(derivedKey) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	iv := getEntropyCSPRNG(aes.BlockSize) // 16
 | ||||||
|  | 	AES256CBCEncrypter := cipher.NewCBCEncrypter(AES256Block, iv) | ||||||
|  | 	cipherText := make([]byte, len(toEncrypt)) | ||||||
|  | 	AES256CBCEncrypter.CryptBlocks(cipherText, toEncrypt) | ||||||
|  | 
 | ||||||
|  | 	cipherStruct := cipherJSON{ | ||||||
|  | 		salt, | ||||||
|  | 		iv, | ||||||
|  | 		cipherText, | ||||||
|  | 	} | ||||||
|  | 	keyStruct := encryptedKeyJSON{ | ||||||
|  | 		*key.Id, | ||||||
|  | 		cipherStruct, | ||||||
|  | 	} | ||||||
|  | 	keyJSON, err := json.Marshal(keyStruct) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return WriteKeyFile(key.Id.String(), ks.keysDirPath, keyJSON) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (ks keyStorePassphrase) DeleteKey(keyId *uuid.UUID, auth string) (err error) { | ||||||
|  | 	// only delete if correct passphrase is given
 | ||||||
|  | 	_, err = DecryptKey(ks, keyId, auth) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	keyDirPath := path.Join(ks.keysDirPath, keyId.String()) | ||||||
|  | 	return os.RemoveAll(keyDirPath) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func DecryptKey(ks keyStorePassphrase, keyId *uuid.UUID, auth string) (keyBytes []byte, err error) { | ||||||
|  | 	fileContent, err := GetKeyFile(ks.keysDirPath, keyId) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	keyProtected := new(encryptedKeyJSON) | ||||||
|  | 	err = json.Unmarshal(fileContent, keyProtected) | ||||||
|  | 
 | ||||||
|  | 	salt := keyProtected.Crypto.Salt | ||||||
|  | 
 | ||||||
|  | 	iv := keyProtected.Crypto.IV | ||||||
|  | 
 | ||||||
|  | 	cipherText := keyProtected.Crypto.CipherText | ||||||
|  | 
 | ||||||
|  | 	authArray := []byte(auth) | ||||||
|  | 	derivedKey, err := scrypt.Key(authArray, salt, scryptN, scryptr, scryptp, scryptdkLen) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	AES256Block, err := aes.NewCipher(derivedKey) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	AES256CBCDecrypter := cipher.NewCBCDecrypter(AES256Block, iv) | ||||||
|  | 	paddedPlainText := make([]byte, len(cipherText)) | ||||||
|  | 	AES256CBCDecrypter.CryptBlocks(paddedPlainText, cipherText) | ||||||
|  | 
 | ||||||
|  | 	plainText := PKCS7Unpad(paddedPlainText) | ||||||
|  | 	if plainText == nil { | ||||||
|  | 		err = errors.New("Decryption failed: PKCS7Unpad failed after decryption") | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	keyBytes = plainText[:len(plainText)-32] | ||||||
|  | 	keyBytesHash := plainText[len(plainText)-32:] | ||||||
|  | 	if !bytes.Equal(Sha3(keyBytes), keyBytesHash) { | ||||||
|  | 		err = errors.New("Decryption failed: checksum mismatch") | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 	return keyBytes, err | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func getEntropyCSPRNG(n int) []byte { | ||||||
|  | 	mainBuff := make([]byte, n) | ||||||
|  | 	_, err := io.ReadFull(crand.Reader, mainBuff) | ||||||
|  | 	if err != nil { | ||||||
|  | 		panic("key generation: reading from crypto/rand failed: " + err.Error()) | ||||||
|  | 	} | ||||||
|  | 	return mainBuff | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // From https://leanpub.com/gocrypto/read#leanpub-auto-block-cipher-modes
 | ||||||
|  | func PKCS7Pad(in []byte) []byte { | ||||||
|  | 	padding := 16 - (len(in) % 16) | ||||||
|  | 	if padding == 0 { | ||||||
|  | 		padding = 16 | ||||||
|  | 	} | ||||||
|  | 	for i := 0; i < padding; i++ { | ||||||
|  | 		in = append(in, byte(padding)) | ||||||
|  | 	} | ||||||
|  | 	return in | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | 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)] | ||||||
|  | } | ||||||
							
								
								
									
										114
									
								
								crypto/key_store_plain.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										114
									
								
								crypto/key_store_plain.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,114 @@ | |||||||
|  | /* | ||||||
|  | 	This file is part of go-ethereum | ||||||
|  | 
 | ||||||
|  | 	go-ethereum 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. | ||||||
|  | 
 | ||||||
|  | 	go-ethereum 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 General Public License for more details. | ||||||
|  | 
 | ||||||
|  | 	You should have received a copy of the GNU Lesser General Public License | ||||||
|  | 	along with go-ethereum.  If not, see <http://www.gnu.org/licenses/>.
 | ||||||
|  | */ | ||||||
|  | /** | ||||||
|  |  * @authors | ||||||
|  |  * 	Gustav Simonsson <gustav.simonsson@gmail.com> | ||||||
|  |  * @date 2015 | ||||||
|  |  * | ||||||
|  |  */ | ||||||
|  | 
 | ||||||
|  | package crypto | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"code.google.com/p/go-uuid/uuid" | ||||||
|  | 	"encoding/json" | ||||||
|  | 	"fmt" | ||||||
|  | 	"io" | ||||||
|  | 	"io/ioutil" | ||||||
|  | 	"os" | ||||||
|  | 	"os/user" | ||||||
|  | 	"path" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | // TODO: rename to KeyStore when replacing existing KeyStore
 | ||||||
|  | type KeyStore2 interface { | ||||||
|  | 	// create new key using io.Reader entropy source and optionally using auth string
 | ||||||
|  | 	GenerateNewKey(io.Reader, string) (*Key, error) | ||||||
|  | 	GetKey(*uuid.UUID, string) (*Key, error) // key from id and auth string
 | ||||||
|  | 	StoreKey(*Key, string) error             // store key optionally using auth string
 | ||||||
|  | 	DeleteKey(*uuid.UUID, string) error      // delete key by id and auth string
 | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type keyStorePlain struct { | ||||||
|  | 	keysDirPath string | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // TODO: copied from cmd/ethereum/flags.go
 | ||||||
|  | func DefaultDataDir() string { | ||||||
|  | 	usr, _ := user.Current() | ||||||
|  | 	return path.Join(usr.HomeDir, ".ethereum") | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func NewKeyStorePlain(path string) KeyStore2 { | ||||||
|  | 	return &keyStorePlain{path} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (ks keyStorePlain) GenerateNewKey(rand io.Reader, auth string) (key *Key, err error) { | ||||||
|  | 	return GenerateNewKeyDefault(ks, rand, auth) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func GenerateNewKeyDefault(ks KeyStore2, 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(keyId *uuid.UUID, auth string) (key *Key, err error) { | ||||||
|  | 	fileContent, err := GetKeyFile(ks.keysDirPath, keyId) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	key = new(Key) | ||||||
|  | 	err = json.Unmarshal(fileContent, key) | ||||||
|  | 	return key, err | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (ks keyStorePlain) StoreKey(key *Key, auth string) (err error) { | ||||||
|  | 	keyJSON, err := json.Marshal(key) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 	err = WriteKeyFile(key.Id.String(), ks.keysDirPath, keyJSON) | ||||||
|  | 	return err | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (ks keyStorePlain) DeleteKey(keyId *uuid.UUID, auth string) (err error) { | ||||||
|  | 	keyDirPath := path.Join(ks.keysDirPath, keyId.String()) | ||||||
|  | 	err = os.RemoveAll(keyDirPath) | ||||||
|  | 	return err | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func GetKeyFile(keysDirPath string, keyId *uuid.UUID) (fileContent []byte, err error) { | ||||||
|  | 	id := keyId.String() | ||||||
|  | 	return ioutil.ReadFile(path.Join(keysDirPath, id, id)) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func WriteKeyFile(id string, keysDirPath string, content []byte) (err error) { | ||||||
|  | 	keyDirPath := path.Join(keysDirPath, id) | ||||||
|  | 	keyFilePath := path.Join(keyDirPath, id) | ||||||
|  | 	err = os.MkdirAll(keyDirPath, 0700) // read, write and dir search for user
 | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 	return ioutil.WriteFile(keyFilePath, content, 0600) // read, write for user
 | ||||||
|  | } | ||||||
							
								
								
									
										85
									
								
								crypto/key_store_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										85
									
								
								crypto/key_store_test.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,85 @@ | |||||||
|  | package crypto | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	crand "crypto/rand" | ||||||
|  | 	"reflect" | ||||||
|  | 	"testing" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | func TestKeyStorePlain(t *testing.T) { | ||||||
|  | 	ks := NewKeyStorePlain(DefaultDataDir()) | ||||||
|  | 	pass := "" // not used but required by API
 | ||||||
|  | 	k1, err := ks.GenerateNewKey(crand.Reader, pass) | ||||||
|  | 	if err != nil { | ||||||
|  | 		t.Fatal(err) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	k2 := new(Key) | ||||||
|  | 	k2, err = ks.GetKey(k1.Id, pass) | ||||||
|  | 	if err != nil { | ||||||
|  | 		t.Fatal(err) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if !reflect.DeepEqual(k1.Id, k2.Id) { | ||||||
|  | 		t.Fatal(err) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if !reflect.DeepEqual(k1.PrivateKey, k2.PrivateKey) { | ||||||
|  | 		t.Fatal(err) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	err = ks.DeleteKey(k2.Id, pass) | ||||||
|  | 	if err != nil { | ||||||
|  | 		t.Fatal(err) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func TestKeyStorePassphrase(t *testing.T) { | ||||||
|  | 	ks := NewKeyStorePassphrase(DefaultDataDir()) | ||||||
|  | 	pass := "foo" | ||||||
|  | 	k1, err := ks.GenerateNewKey(crand.Reader, pass) | ||||||
|  | 	if err != nil { | ||||||
|  | 		t.Fatal(err) | ||||||
|  | 	} | ||||||
|  | 	k2 := new(Key) | ||||||
|  | 	k2, err = ks.GetKey(k1.Id, pass) | ||||||
|  | 	if err != nil { | ||||||
|  | 		t.Fatal(err) | ||||||
|  | 	} | ||||||
|  | 	if !reflect.DeepEqual(k1.Id, k2.Id) { | ||||||
|  | 		t.Fatal(err) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if !reflect.DeepEqual(k1.PrivateKey, k2.PrivateKey) { | ||||||
|  | 		t.Fatal(err) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	err = ks.DeleteKey(k2.Id, pass) // also to clean up created files
 | ||||||
|  | 	if err != nil { | ||||||
|  | 		t.Fatal(err) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func TestKeyStorePassphraseDecryptionFail(t *testing.T) { | ||||||
|  | 	ks := NewKeyStorePassphrase(DefaultDataDir()) | ||||||
|  | 	pass := "foo" | ||||||
|  | 	k1, err := ks.GenerateNewKey(crand.Reader, pass) | ||||||
|  | 	if err != nil { | ||||||
|  | 		t.Fatal(err) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	_, err = ks.GetKey(k1.Id, "bar") // wrong passphrase
 | ||||||
|  | 	if err == nil { | ||||||
|  | 		t.Fatal(err) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	err = ks.DeleteKey(k1.Id, "bar") // wrong passphrase
 | ||||||
|  | 	if err == nil { | ||||||
|  | 		t.Fatal(err) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	err = ks.DeleteKey(k1.Id, pass) // to clean up
 | ||||||
|  | 	if err != nil { | ||||||
|  | 		t.Fatal(err) | ||||||
|  | 	} | ||||||
|  | } | ||||||
		Loading…
	
		Reference in New Issue
	
	Block a user