OSDN Git Service

Hulk did something
[bytom/vapor.git] / blockchain / pseudohsm / keystore_passphrase.go
1 /*
2 This key store behaves as KeyStorePlain with the difference that
3 the private key is encrypted and on disk uses another JSON encoding.
4 */
5
6 package pseudohsm
7
8 import (
9         "bytes"
10         "crypto/aes"
11         "crypto/cipher"
12         "crypto/sha256"
13         "encoding/hex"
14         "encoding/json"
15         "fmt"
16         "io/ioutil"
17         "path/filepath"
18
19         "github.com/pborman/uuid"
20         "github.com/vapor/crypto"
21         "github.com/vapor/crypto/ed25519/chainkd"
22         "github.com/vapor/crypto/randentropy"
23         "golang.org/x/crypto/pbkdf2"
24         "golang.org/x/crypto/scrypt"
25 )
26
27 const (
28         keyHeaderKDF = "scrypt"
29
30         // StandardScryptN n,r,p = 2^18, 8, 1 uses 256MB memory and approx 1s CPU time on a modern CPU.
31         StandardScryptN = 1 << 18
32         // StandardScryptP fit above
33         StandardScryptP = 1
34
35         // LightScryptN n,r,p = 2^12, 8, 6 uses 4MB memory and approx 100ms CPU time on a modern CPU.
36         LightScryptN = 1 << 12
37         //LightScryptP fit above
38         LightScryptP = 6
39
40         scryptR     = 8
41         scryptDKLen = 32
42 )
43
44 type keyStorePassphrase struct {
45         keysDirPath string
46         scryptN     int
47         scryptP     int
48 }
49
50 func (ks keyStorePassphrase) GetKey(alias string, filename, auth string) (*XKey, error) {
51         // Load the key from the keystore and decrypt its contents
52         keyjson, err := ioutil.ReadFile(filename)
53         if err != nil {
54                 return nil, err
55         }
56         key, err := DecryptKey(keyjson, auth)
57         if err != nil {
58                 return nil, err
59         }
60         // Make sure we're really operating on the requested key (no swap attacks)
61         if key.Alias != alias {
62                 return nil, fmt.Errorf("key content mismatch: have account %x, want %x", key.Alias, alias)
63         }
64         return key, nil
65 }
66
67 func (ks keyStorePassphrase) StoreKey(filename string, key *XKey, auth string) error {
68         keyjson, err := EncryptKey(key, auth, ks.scryptN, ks.scryptP)
69         if err != nil {
70                 return err
71         }
72         return writeKeyFile(filename, keyjson)
73 }
74
75 func (ks keyStorePassphrase) JoinPath(filename string) string {
76         if filepath.IsAbs(filename) {
77                 return filename
78         }
79         return filepath.Join(ks.keysDirPath, filename)
80 }
81
82 // EncryptKey encrypts a key using the specified scrypt parameters into a json
83 // blob that can be decrypted later on.
84 func EncryptKey(key *XKey, auth string, scryptN, scryptP int) ([]byte, error) {
85         authArray := []byte(auth)
86         salt := randentropy.GetEntropyCSPRNG(32)
87         derivedKey, err := scrypt.Key(authArray, salt, scryptN, scryptR, scryptP, scryptDKLen)
88         if err != nil {
89                 return nil, err
90         }
91         encryptKey := derivedKey[:16]
92         keyBytes := key.XPrv[:]
93
94         iv := randentropy.GetEntropyCSPRNG(aes.BlockSize) // 16
95         cipherText, err := aesCTRXOR(encryptKey, keyBytes, iv)
96         if err != nil {
97                 return nil, err
98         }
99         mac := crypto.Sha256(derivedKey[16:32], cipherText)
100         scryptParamsJSON := make(map[string]interface{}, 5)
101         scryptParamsJSON["n"] = scryptN
102         scryptParamsJSON["r"] = scryptR
103         scryptParamsJSON["p"] = scryptP
104         scryptParamsJSON["dklen"] = scryptDKLen
105         scryptParamsJSON["salt"] = hex.EncodeToString(salt)
106
107         cipherParamsJSON := cipherparamsJSON{
108                 IV: hex.EncodeToString(iv),
109         }
110         cryptoStruct := cryptoJSON{
111                 Cipher:       "aes-128-ctr",
112                 CipherText:   hex.EncodeToString(cipherText),
113                 CipherParams: cipherParamsJSON,
114                 KDF:          "scrypt",
115                 KDFParams:    scryptParamsJSON,
116                 MAC:          hex.EncodeToString(mac),
117         }
118         encryptedKeyJSON := encryptedKeyJSON{
119                 cryptoStruct,
120                 key.ID.String(),
121                 key.KeyType,
122                 version,
123                 key.Alias,
124                 hex.EncodeToString(key.XPub[:]),
125         }
126         return json.Marshal(encryptedKeyJSON)
127 }
128
129 // DecryptKey decrypts a key from a json blob, returning the private key itself.
130 func DecryptKey(keyjson []byte, auth string) (*XKey, error) {
131         // Parse the json into a simple map to fetch the key version
132         m := make(map[string]interface{})
133         if err := json.Unmarshal(keyjson, &m); err != nil {
134                 return nil, err
135         }
136         // Depending on the version try to parse one way or another
137         var (
138                 keyBytes, keyID []byte
139                 err             error
140         )
141         k := new(encryptedKeyJSON)
142         if err := json.Unmarshal(keyjson, k); err != nil {
143                 return nil, err
144         }
145
146         keyBytes, keyID, err = decryptKey(k, auth)
147         // Handle any decryption errors and return the key
148         if err != nil {
149                 return nil, err
150         }
151         var xprv chainkd.XPrv
152         copy(xprv[:], keyBytes[:])
153         xpub := xprv.XPub()
154
155         //key := crypto.ToECDSA(keyBytes)
156         return &XKey{
157                 ID:      uuid.UUID(keyID),
158                 XPrv:    xprv,
159                 XPub:    xpub,
160                 KeyType: k.Type,
161                 Alias:   k.Alias,
162         }, nil
163 }
164
165 func decryptKey(keyProtected *encryptedKeyJSON, auth string) (keyBytes []byte, keyID []byte, err error) {
166         if keyProtected.Version != version {
167                 return nil, nil, fmt.Errorf("Version not supported: %v", keyProtected.Version)
168         }
169
170         if keyProtected.Type != keytype {
171                 return nil, nil, fmt.Errorf("Key type not supported: %v", keyProtected.Type)
172         }
173
174         if keyProtected.Crypto.Cipher != "aes-128-ctr" {
175                 return nil, nil, fmt.Errorf("Cipher not supported: %v", keyProtected.Crypto.Cipher)
176         }
177
178         keyID = uuid.Parse(keyProtected.ID)
179         mac, err := hex.DecodeString(keyProtected.Crypto.MAC)
180         if err != nil {
181                 return nil, nil, err
182         }
183
184         iv, err := hex.DecodeString(keyProtected.Crypto.CipherParams.IV)
185         if err != nil {
186                 return nil, nil, err
187         }
188
189         cipherText, err := hex.DecodeString(keyProtected.Crypto.CipherText)
190         if err != nil {
191                 return nil, nil, err
192         }
193
194         derivedKey, err := getKDFKey(keyProtected.Crypto, auth)
195         if err != nil {
196                 return nil, nil, err
197         }
198
199         calculatedMAC := crypto.Sha256(derivedKey[16:32], cipherText)
200
201         if !bytes.Equal(calculatedMAC, mac) {
202                 return nil, nil, ErrDecrypt
203         }
204
205         plainText, err := aesCTRXOR(derivedKey[:16], cipherText, iv)
206         if err != nil {
207                 return nil, nil, err
208         }
209         return plainText, keyID, err
210 }
211
212 func getKDFKey(cryptoJSON cryptoJSON, auth string) ([]byte, error) {
213         authArray := []byte(auth)
214         salt, err := hex.DecodeString(cryptoJSON.KDFParams["salt"].(string))
215         if err != nil {
216                 return nil, err
217         }
218         dkLen := ensureInt(cryptoJSON.KDFParams["dklen"])
219
220         if cryptoJSON.KDF == "scrypt" {
221                 n := ensureInt(cryptoJSON.KDFParams["n"])
222                 r := ensureInt(cryptoJSON.KDFParams["r"])
223                 p := ensureInt(cryptoJSON.KDFParams["p"])
224                 return scrypt.Key(authArray, salt, n, r, p, dkLen)
225
226         } else if cryptoJSON.KDF == "pbkdf2" {
227                 c := ensureInt(cryptoJSON.KDFParams["c"])
228                 prf := cryptoJSON.KDFParams["prf"].(string)
229                 if prf != "hmac-sha256" {
230                         return nil, fmt.Errorf("Unsupported PBKDF2 PRF: %s", prf)
231                 }
232                 key := pbkdf2.Key(authArray, salt, c, dkLen, sha256.New)
233                 return key, nil
234         }
235
236         return nil, fmt.Errorf("Unsupported KDF: %s", cryptoJSON.KDF)
237 }
238
239 // TODO: can we do without this when unmarshalling dynamic JSON?
240 // why do integers in KDF params end up as float64 and not int after
241 // unmarshal?
242 func ensureInt(x interface{}) int {
243         res, ok := x.(int)
244         if !ok {
245                 res = int(x.(float64))
246         }
247         return res
248 }
249
250 func aesCTRXOR(key, inText, iv []byte) ([]byte, error) {
251         // AES-128 is selected due to size of encryptKey.
252         aesBlock, err := aes.NewCipher(key)
253         if err != nil {
254                 return nil, err
255         }
256         stream := cipher.NewCTR(aesBlock, iv)
257         outText := make([]byte, len(inText))
258         stream.XORKeyStream(outText, inText)
259         return outText, err
260 }
261
262 func aesCBCDecrypt(key, cipherText, iv []byte) ([]byte, error) {
263         aesBlock, err := aes.NewCipher(key)
264         if err != nil {
265                 return nil, err
266         }
267         decrypter := cipher.NewCBCDecrypter(aesBlock, iv)
268         paddedPlaintext := make([]byte, len(cipherText))
269         decrypter.CryptBlocks(paddedPlaintext, cipherText)
270         plaintext := pkcs7Unpad(paddedPlaintext)
271         if plaintext == nil {
272                 return nil, ErrDecrypt
273         }
274         return plaintext, err
275 }
276
277 // From https://leanpub.com/gocrypto/read#leanpub-auto-block-cipher-modes
278 func pkcs7Unpad(in []byte) []byte {
279         if len(in) == 0 {
280                 return nil
281         }
282
283         padding := in[len(in)-1]
284         if int(padding) > len(in) || padding > aes.BlockSize {
285                 return nil
286         } else if padding == 0 {
287                 return nil
288         }
289
290         for i := len(in) - 1; i > len(in)-int(padding)-1; i-- {
291                 if in[i] != padding {
292                         return nil
293                 }
294         }
295         return in[:len(in)-int(padding)]
296 }