github.com/ava-labs/subnet-evm@v0.6.4/accounts/keystore/passphrase.go (about) 1 // (c) 2019-2020, Ava Labs, Inc. 2 // 3 // This file is a derived work, based on the go-ethereum library whose original 4 // notices appear below. 5 // 6 // It is distributed under a license compatible with the licensing terms of the 7 // original code from which it is derived. 8 // 9 // Much love to the original authors for their work. 10 // ********** 11 // Copyright 2014 The go-ethereum Authors 12 // This file is part of the go-ethereum library. 13 // 14 // The go-ethereum library is free software: you can redistribute it and/or modify 15 // it under the terms of the GNU Lesser General Public License as published by 16 // the Free Software Foundation, either version 3 of the License, or 17 // (at your option) any later version. 18 // 19 // The go-ethereum library is distributed in the hope that it will be useful, 20 // but WITHOUT ANY WARRANTY; without even the implied warranty of 21 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 22 // GNU Lesser General Public License for more details. 23 // 24 // You should have received a copy of the GNU Lesser General Public License 25 // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>. 26 27 /* 28 29 This key store behaves as KeyStorePlain with the difference that 30 the private key is encrypted and on disk uses another JSON encoding. 31 32 The crypto is documented at https://github.com/ethereum/wiki/wiki/Web3-Secret-Storage-Definition 33 34 */ 35 36 package keystore 37 38 import ( 39 "bytes" 40 "crypto/aes" 41 "crypto/rand" 42 "crypto/sha256" 43 "encoding/hex" 44 "encoding/json" 45 "fmt" 46 "io" 47 "os" 48 "path/filepath" 49 50 "github.com/ava-labs/subnet-evm/accounts" 51 "github.com/ethereum/go-ethereum/common" 52 "github.com/ethereum/go-ethereum/common/math" 53 "github.com/ethereum/go-ethereum/crypto" 54 "github.com/google/uuid" 55 "golang.org/x/crypto/pbkdf2" 56 "golang.org/x/crypto/scrypt" 57 ) 58 59 const ( 60 keyHeaderKDF = "scrypt" 61 62 // StandardScryptN is the N parameter of Scrypt encryption algorithm, using 256MB 63 // memory and taking approximately 1s CPU time on a modern processor. 64 StandardScryptN = 1 << 18 65 66 // StandardScryptP is the P parameter of Scrypt encryption algorithm, using 256MB 67 // memory and taking approximately 1s CPU time on a modern processor. 68 StandardScryptP = 1 69 70 // LightScryptN is the N parameter of Scrypt encryption algorithm, using 4MB 71 // memory and taking approximately 100ms CPU time on a modern processor. 72 LightScryptN = 1 << 12 73 74 // LightScryptP is the P parameter of Scrypt encryption algorithm, using 4MB 75 // memory and taking approximately 100ms CPU time on a modern processor. 76 LightScryptP = 6 77 78 scryptR = 8 79 scryptDKLen = 32 80 ) 81 82 type keyStorePassphrase struct { 83 keysDirPath string 84 scryptN int 85 scryptP int 86 // skipKeyFileVerification disables the security-feature which does 87 // reads and decrypts any newly created keyfiles. This should be 'false' in all 88 // cases except tests -- setting this to 'true' is not recommended. 89 skipKeyFileVerification bool 90 } 91 92 func (ks keyStorePassphrase) GetKey(addr common.Address, filename, auth string) (*Key, error) { 93 // Load the key from the keystore and decrypt its contents 94 keyjson, err := os.ReadFile(filename) 95 if err != nil { 96 return nil, err 97 } 98 key, err := DecryptKey(keyjson, auth) 99 if err != nil { 100 return nil, err 101 } 102 // Make sure we're really operating on the requested key (no swap attacks) 103 if key.Address != addr { 104 return nil, fmt.Errorf("key content mismatch: have account %x, want %x", key.Address, addr) 105 } 106 return key, nil 107 } 108 109 // StoreKey generates a key, encrypts with 'auth' and stores in the given directory 110 func StoreKey(dir, auth string, scryptN, scryptP int) (accounts.Account, error) { 111 _, a, err := storeNewKey(&keyStorePassphrase{dir, scryptN, scryptP, false}, rand.Reader, auth) 112 return a, err 113 } 114 115 func (ks keyStorePassphrase) StoreKey(filename string, key *Key, auth string) error { 116 keyjson, err := EncryptKey(key, auth, ks.scryptN, ks.scryptP) 117 if err != nil { 118 return err 119 } 120 // Write into temporary file 121 tmpName, err := writeTemporaryKeyFile(filename, keyjson) 122 if err != nil { 123 return err 124 } 125 if !ks.skipKeyFileVerification { 126 // Verify that we can decrypt the file with the given password. 127 _, err = ks.GetKey(key.Address, tmpName, auth) 128 if err != nil { 129 msg := "An error was encountered when saving and verifying the keystore file. \n" + 130 "This indicates that the keystore is corrupted. \n" + 131 "The corrupted file is stored at \n%v\n" + 132 "Please file a ticket at:\n\n" + 133 "https://github.com/ethereum/go-ethereum/issues." + 134 "The error was : %s" 135 //lint:ignore ST1005 This is a message for the user 136 return fmt.Errorf(msg, tmpName, err) 137 } 138 } 139 return os.Rename(tmpName, filename) 140 } 141 142 func (ks keyStorePassphrase) JoinPath(filename string) string { 143 if filepath.IsAbs(filename) { 144 return filename 145 } 146 return filepath.Join(ks.keysDirPath, filename) 147 } 148 149 // Encryptdata encrypts the data given as 'data' with the password 'auth'. 150 func EncryptDataV3(data, auth []byte, scryptN, scryptP int) (CryptoJSON, error) { 151 salt := make([]byte, 32) 152 if _, err := io.ReadFull(rand.Reader, salt); err != nil { 153 panic("reading from crypto/rand failed: " + err.Error()) 154 } 155 derivedKey, err := scrypt.Key(auth, salt, scryptN, scryptR, scryptP, scryptDKLen) 156 if err != nil { 157 return CryptoJSON{}, err 158 } 159 encryptKey := derivedKey[:16] 160 161 iv := make([]byte, aes.BlockSize) // 16 162 if _, err := io.ReadFull(rand.Reader, iv); err != nil { 163 panic("reading from crypto/rand failed: " + err.Error()) 164 } 165 cipherText, err := aesCTRXOR(encryptKey, data, iv) 166 if err != nil { 167 return CryptoJSON{}, err 168 } 169 mac := crypto.Keccak256(derivedKey[16:32], cipherText) 170 171 scryptParamsJSON := make(map[string]interface{}, 5) 172 scryptParamsJSON["n"] = scryptN 173 scryptParamsJSON["r"] = scryptR 174 scryptParamsJSON["p"] = scryptP 175 scryptParamsJSON["dklen"] = scryptDKLen 176 scryptParamsJSON["salt"] = hex.EncodeToString(salt) 177 cipherParamsJSON := cipherparamsJSON{ 178 IV: hex.EncodeToString(iv), 179 } 180 181 cryptoStruct := CryptoJSON{ 182 Cipher: "aes-128-ctr", 183 CipherText: hex.EncodeToString(cipherText), 184 CipherParams: cipherParamsJSON, 185 KDF: keyHeaderKDF, 186 KDFParams: scryptParamsJSON, 187 MAC: hex.EncodeToString(mac), 188 } 189 return cryptoStruct, nil 190 } 191 192 // EncryptKey encrypts a key using the specified scrypt parameters into a json 193 // blob that can be decrypted later on. 194 func EncryptKey(key *Key, auth string, scryptN, scryptP int) ([]byte, error) { 195 keyBytes := math.PaddedBigBytes(key.PrivateKey.D, 32) 196 cryptoStruct, err := EncryptDataV3(keyBytes, []byte(auth), scryptN, scryptP) 197 if err != nil { 198 return nil, err 199 } 200 encryptedKeyJSONV3 := encryptedKeyJSONV3{ 201 hex.EncodeToString(key.Address[:]), 202 cryptoStruct, 203 key.Id.String(), 204 version, 205 } 206 return json.Marshal(encryptedKeyJSONV3) 207 } 208 209 // DecryptKey decrypts a key from a json blob, returning the private key itself. 210 func DecryptKey(keyjson []byte, auth string) (*Key, error) { 211 // Parse the json into a simple map to fetch the key version 212 m := make(map[string]interface{}) 213 if err := json.Unmarshal(keyjson, &m); err != nil { 214 return nil, err 215 } 216 // Depending on the version try to parse one way or another 217 var ( 218 keyBytes, keyId []byte 219 err error 220 ) 221 if version, ok := m["version"].(string); ok && version == "1" { 222 k := new(encryptedKeyJSONV1) 223 if err := json.Unmarshal(keyjson, k); err != nil { 224 return nil, err 225 } 226 keyBytes, keyId, err = decryptKeyV1(k, auth) 227 } else { 228 k := new(encryptedKeyJSONV3) 229 if err := json.Unmarshal(keyjson, k); err != nil { 230 return nil, err 231 } 232 keyBytes, keyId, err = decryptKeyV3(k, auth) 233 } 234 // Handle any decryption errors and return the key 235 if err != nil { 236 return nil, err 237 } 238 key, err := crypto.ToECDSA(keyBytes) 239 if err != nil { 240 return nil, fmt.Errorf("invalid key: %w", err) 241 } 242 id, err := uuid.FromBytes(keyId) 243 if err != nil { 244 return nil, fmt.Errorf("invalid UUID: %w", err) 245 } 246 return &Key{ 247 Id: id, 248 Address: crypto.PubkeyToAddress(key.PublicKey), 249 PrivateKey: key, 250 }, nil 251 } 252 253 func DecryptDataV3(cryptoJson CryptoJSON, auth string) ([]byte, error) { 254 if cryptoJson.Cipher != "aes-128-ctr" { 255 return nil, fmt.Errorf("cipher not supported: %v", cryptoJson.Cipher) 256 } 257 mac, err := hex.DecodeString(cryptoJson.MAC) 258 if err != nil { 259 return nil, err 260 } 261 262 iv, err := hex.DecodeString(cryptoJson.CipherParams.IV) 263 if err != nil { 264 return nil, err 265 } 266 267 cipherText, err := hex.DecodeString(cryptoJson.CipherText) 268 if err != nil { 269 return nil, err 270 } 271 272 derivedKey, err := getKDFKey(cryptoJson, auth) 273 if err != nil { 274 return nil, err 275 } 276 277 calculatedMAC := crypto.Keccak256(derivedKey[16:32], cipherText) 278 if !bytes.Equal(calculatedMAC, mac) { 279 return nil, ErrDecrypt 280 } 281 282 plainText, err := aesCTRXOR(derivedKey[:16], cipherText, iv) 283 if err != nil { 284 return nil, err 285 } 286 return plainText, err 287 } 288 289 func decryptKeyV3(keyProtected *encryptedKeyJSONV3, auth string) (keyBytes []byte, keyId []byte, err error) { 290 if keyProtected.Version != version { 291 return nil, nil, fmt.Errorf("version not supported: %v", keyProtected.Version) 292 } 293 keyUUID, err := uuid.Parse(keyProtected.Id) 294 if err != nil { 295 return nil, nil, err 296 } 297 keyId = keyUUID[:] 298 plainText, err := DecryptDataV3(keyProtected.Crypto, auth) 299 if err != nil { 300 return nil, nil, err 301 } 302 return plainText, keyId, err 303 } 304 305 func decryptKeyV1(keyProtected *encryptedKeyJSONV1, auth string) (keyBytes []byte, keyId []byte, err error) { 306 keyUUID, err := uuid.Parse(keyProtected.Id) 307 if err != nil { 308 return nil, nil, err 309 } 310 keyId = keyUUID[:] 311 mac, err := hex.DecodeString(keyProtected.Crypto.MAC) 312 if err != nil { 313 return nil, nil, err 314 } 315 316 iv, err := hex.DecodeString(keyProtected.Crypto.CipherParams.IV) 317 if err != nil { 318 return nil, nil, err 319 } 320 321 cipherText, err := hex.DecodeString(keyProtected.Crypto.CipherText) 322 if err != nil { 323 return nil, nil, err 324 } 325 326 derivedKey, err := getKDFKey(keyProtected.Crypto, auth) 327 if err != nil { 328 return nil, nil, err 329 } 330 331 calculatedMAC := crypto.Keccak256(derivedKey[16:32], cipherText) 332 if !bytes.Equal(calculatedMAC, mac) { 333 return nil, nil, ErrDecrypt 334 } 335 336 plainText, err := aesCBCDecrypt(crypto.Keccak256(derivedKey[:16])[:16], cipherText, iv) 337 if err != nil { 338 return nil, nil, err 339 } 340 return plainText, keyId, err 341 } 342 343 func getKDFKey(cryptoJSON CryptoJSON, auth string) ([]byte, error) { 344 authArray := []byte(auth) 345 salt, err := hex.DecodeString(cryptoJSON.KDFParams["salt"].(string)) 346 if err != nil { 347 return nil, err 348 } 349 dkLen := ensureInt(cryptoJSON.KDFParams["dklen"]) 350 351 if cryptoJSON.KDF == keyHeaderKDF { 352 n := ensureInt(cryptoJSON.KDFParams["n"]) 353 r := ensureInt(cryptoJSON.KDFParams["r"]) 354 p := ensureInt(cryptoJSON.KDFParams["p"]) 355 return scrypt.Key(authArray, salt, n, r, p, dkLen) 356 } else if cryptoJSON.KDF == "pbkdf2" { 357 c := ensureInt(cryptoJSON.KDFParams["c"]) 358 prf := cryptoJSON.KDFParams["prf"].(string) 359 if prf != "hmac-sha256" { 360 return nil, fmt.Errorf("unsupported PBKDF2 PRF: %s", prf) 361 } 362 key := pbkdf2.Key(authArray, salt, c, dkLen, sha256.New) 363 return key, nil 364 } 365 366 return nil, fmt.Errorf("unsupported KDF: %s", cryptoJSON.KDF) 367 } 368 369 // TODO: can we do without this when unmarshalling dynamic JSON? 370 // why do integers in KDF params end up as float64 and not int after 371 // unmarshal? 372 func ensureInt(x interface{}) int { 373 res, ok := x.(int) 374 if !ok { 375 res = int(x.(float64)) 376 } 377 return res 378 }