OSDN Git Service

Hulk did something
[bytom/vapor.git] / blockchain / pseudohsm / keystore_passphrase.go
diff --git a/blockchain/pseudohsm/keystore_passphrase.go b/blockchain/pseudohsm/keystore_passphrase.go
new file mode 100644 (file)
index 0000000..17cba23
--- /dev/null
@@ -0,0 +1,296 @@
+/*
+This key store behaves as KeyStorePlain with the difference that
+the private key is encrypted and on disk uses another JSON encoding.
+*/
+
+package pseudohsm
+
+import (
+       "bytes"
+       "crypto/aes"
+       "crypto/cipher"
+       "crypto/sha256"
+       "encoding/hex"
+       "encoding/json"
+       "fmt"
+       "io/ioutil"
+       "path/filepath"
+
+       "github.com/pborman/uuid"
+       "github.com/vapor/crypto"
+       "github.com/vapor/crypto/ed25519/chainkd"
+       "github.com/vapor/crypto/randentropy"
+       "golang.org/x/crypto/pbkdf2"
+       "golang.org/x/crypto/scrypt"
+)
+
+const (
+       keyHeaderKDF = "scrypt"
+
+       // StandardScryptN n,r,p = 2^18, 8, 1 uses 256MB memory and approx 1s CPU time on a modern CPU.
+       StandardScryptN = 1 << 18
+       // StandardScryptP fit above
+       StandardScryptP = 1
+
+       // LightScryptN n,r,p = 2^12, 8, 6 uses 4MB memory and approx 100ms CPU time on a modern CPU.
+       LightScryptN = 1 << 12
+       //LightScryptP fit above
+       LightScryptP = 6
+
+       scryptR     = 8
+       scryptDKLen = 32
+)
+
+type keyStorePassphrase struct {
+       keysDirPath string
+       scryptN     int
+       scryptP     int
+}
+
+func (ks keyStorePassphrase) GetKey(alias string, filename, auth string) (*XKey, error) {
+       // Load the key from the keystore and decrypt its contents
+       keyjson, err := ioutil.ReadFile(filename)
+       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 key.Alias != alias {
+               return nil, fmt.Errorf("key content mismatch: have account %x, want %x", key.Alias, alias)
+       }
+       return key, nil
+}
+
+func (ks keyStorePassphrase) StoreKey(filename string, key *XKey, auth string) error {
+       keyjson, err := EncryptKey(key, auth, ks.scryptN, ks.scryptP)
+       if err != nil {
+               return err
+       }
+       return writeKeyFile(filename, keyjson)
+}
+
+func (ks keyStorePassphrase) JoinPath(filename string) string {
+       if filepath.IsAbs(filename) {
+               return filename
+       }
+       return filepath.Join(ks.keysDirPath, filename)
+}
+
+// EncryptKey encrypts a key using the specified scrypt parameters into a json
+// blob that can be decrypted later on.
+func EncryptKey(key *XKey, 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 := key.XPrv[:]
+
+       iv := randentropy.GetEntropyCSPRNG(aes.BlockSize) // 16
+       cipherText, err := aesCTRXOR(encryptKey, keyBytes, iv)
+       if err != nil {
+               return nil, err
+       }
+       mac := crypto.Sha256(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),
+       }
+       encryptedKeyJSON := encryptedKeyJSON{
+               cryptoStruct,
+               key.ID.String(),
+               key.KeyType,
+               version,
+               key.Alias,
+               hex.EncodeToString(key.XPub[:]),
+       }
+       return json.Marshal(encryptedKeyJSON)
+}
+
+// DecryptKey decrypts a key from a json blob, returning the private key itself.
+func DecryptKey(keyjson []byte, auth string) (*XKey, 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
+       )
+       k := new(encryptedKeyJSON)
+       if err := json.Unmarshal(keyjson, k); err != nil {
+               return nil, err
+       }
+
+       keyBytes, keyID, err = decryptKey(k, auth)
+       // Handle any decryption errors and return the key
+       if err != nil {
+               return nil, err
+       }
+       var xprv chainkd.XPrv
+       copy(xprv[:], keyBytes[:])
+       xpub := xprv.XPub()
+
+       //key := crypto.ToECDSA(keyBytes)
+       return &XKey{
+               ID:      uuid.UUID(keyID),
+               XPrv:    xprv,
+               XPub:    xpub,
+               KeyType: k.Type,
+               Alias:   k.Alias,
+       }, nil
+}
+
+func decryptKey(keyProtected *encryptedKeyJSON, 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.Type != keytype {
+               return nil, nil, fmt.Errorf("Key type not supported: %v", keyProtected.Type)
+       }
+
+       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 := crypto.Sha256(derivedKey[16:32], cipherText)
+
+       if !bytes.Equal(calculatedMAC, mac) {
+               return nil, nil, ErrDecrypt
+       }
+
+       plainText, err := aesCTRXOR(derivedKey[: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: %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)]
+}