From 91f9a47afaf12626e07053da331a7e1974aee4c9 Mon Sep 17 00:00:00 2001 From: ricmoo Date: Sun, 28 May 2017 15:46:57 -0500 Subject: [PATCH] Added support for PBKD2 encrypted wallets (which Parity uses). --- tests/make-tests/make-wallets.js | 17 +- .../test-wallets/wallet-parity-.json | 1 + tests/make-tests/utils.js | 2 +- tests/tests/keep.txt | 3 + wallet/secret-storage.js | 171 +++++++++++------- 5 files changed, 125 insertions(+), 69 deletions(-) create mode 100644 tests/make-tests/test-wallets/wallet-parity-.json create mode 100644 tests/tests/keep.txt diff --git a/tests/make-tests/make-wallets.js b/tests/make-tests/make-wallets.js index 32230c304..435405b5c 100644 --- a/tests/make-tests/make-wallets.js +++ b/tests/make-tests/make-wallets.js @@ -9,7 +9,7 @@ function prefixAddress(address) { if (address.substring(0, 2) !== '0x') { address = '0x' + address; } - return address; + return address.toLowerCase(); } @@ -25,6 +25,15 @@ Output.push({ }); */ +var privateKeys = { + '0x0b88d4b324ec24c8c078551e6e5075547157e5b6': '0xd4375d2a931db84ea8825b69a3128913597744d9236cacec675cc18e1bda4446', + '0x2e326fa404fc3661de4f4361776ed9bbabdc26e3': '0xcf367fc32bf789b3339c6664af4a12263e9db0e0eb70f247da1d1165e150c487', + '0x00a329c0648769a73afac7f9381e08fb43dbea72': '0x4d5db4107d237df6a3d58ee5f70ae63d73d7658d4026f2eefd2f204c81682cb7', + '0x4a9cf99357f5789251a8d7fad5b86d0f31eeb938': '0xa016182717223d01f776149ec0b4a217d0e9930cad263f205427c6d3cd5560e7', + '0x88a5c2d9919e46f883eb62f7b8dd9d0cc45bc290': '0xf03e581353c794928373fb0893bc731aefc4c4e234e643f3a46998b03cd4d7c5', + '0x17c5185167401ed00cf5f5b2fc97d9bbfdb7d025': '0x4242424242424242424242424242424242424242424242424242424242424242', +} + var walletPath = path.join(__dirname, 'test-wallets'); fs.readdirSync(walletPath).forEach(function(filename) { var data = require(path.join(walletPath, filename)); @@ -33,7 +42,7 @@ fs.readdirSync(walletPath).forEach(function(filename) { var password = filename.substring(0, filename.length - 5).split('-'); password = password[password.length - 1]; - if (password === 'life') { password = 'foobar42'; } + if (password === 'life') { password = 'foo'; } if (data.ethaddr) { Output.push({ @@ -41,7 +50,7 @@ fs.readdirSync(walletPath).forEach(function(filename) { address: prefixAddress(data.ethaddr), json: JSON.stringify(data), password: password, - privateKey: '', + privateKey: privateKeys[prefixAddress(data.ethaddr)], }); } else { @@ -50,7 +59,7 @@ fs.readdirSync(walletPath).forEach(function(filename) { address: prefixAddress(data.address), json: JSON.stringify(data), password: password, - privateKey: '', + privateKey: privateKeys[prefixAddress(data.address)], }); } }); diff --git a/tests/make-tests/test-wallets/wallet-parity-.json b/tests/make-tests/test-wallets/wallet-parity-.json new file mode 100644 index 000000000..079a14344 --- /dev/null +++ b/tests/make-tests/test-wallets/wallet-parity-.json @@ -0,0 +1 @@ +{"id":"a9b7570b-fd36-8a32-f4ea-26080a941143","version":3,"crypto":{"cipher":"aes-128-ctr","cipherparams":{"iv":"1de99c07c6592eaac911ae9cf07db88d"},"ciphertext":"951ca34a268314c0cde0409566bcac4d4907dc87f34764fe8542786415cb5726","kdf":"pbkdf2","kdfparams":{"c":10240,"dklen":32,"prf":"hmac-sha256","salt":"7d2ebe49878f15e031bcdf9f0be47e7d49542252145850b7862a08a0c209a0e9"},"mac":"dc595787a3d08d98e2d808a981b8e9566d8ed1087c0257481ffc1196e13d8c0a"},"address":"00a329c0648769a73afac7f9381e08fb43dbea72","name":"Blank","meta":"{\"passwordHint\":\"\",\"timestamp\":1495227675346}"} \ No newline at end of file diff --git a/tests/make-tests/utils.js b/tests/make-tests/utils.js index f0853c764..68a9ed43e 100644 --- a/tests/make-tests/utils.js +++ b/tests/make-tests/utils.js @@ -2,7 +2,7 @@ var fs = require('fs'); var path = require('path'); -var utils = require('ethers-utils'); +var utils = require('../../utils'); function randomBytes(seed, lower, upper) { if (!upper) { upper = lower; } diff --git a/tests/tests/keep.txt b/tests/tests/keep.txt new file mode 100644 index 000000000..f33864b9d --- /dev/null +++ b/tests/tests/keep.txt @@ -0,0 +1,3 @@ +This file just ensures this directory exists. + +Run the files in make-tests to create the individual test cases. diff --git a/wallet/secret-storage.js b/wallet/secret-storage.js index 4648ab2b5..f750fc0b2 100644 --- a/wallet/secret-storage.js +++ b/wallet/secret-storage.js @@ -4,6 +4,8 @@ var aes = require('aes-js'); var scrypt = require('scrypt-js'); var uuid = require('uuid'); +var hmac = require('ethers-utils/hmac'); +var pbkdf2 = require('ethers-utils/pbkdf2'); var utils = require('ethers-utils'); var SigningKey = require('./signing-key.js'); @@ -69,6 +71,7 @@ utils.defineProperty(secretStorage, 'isValidWallet', function(json) { if (!data.version || parseInt(data.version) !== data.version || parseInt(data.version) !== 3) { return false; } + // @TODO: Put more checks to make sure it has kdf, iv and all that good stuff return true; }); @@ -89,7 +92,7 @@ utils.defineProperty(secretStorage, 'decryptCrowdsale', function(json, password) throw new Error('invalid encseed'); } - var key = utils.pbkdf2(password, password, 2000, 32, utils.hmac.createSha256Hmac).slice(0, 16); + var key = pbkdf2(password, password, 2000, 32, hmac.createSha256Hmac).slice(0, 16); var iv = encseed.slice(0, 16); var encryptedSeed = encseed.slice(16); @@ -140,72 +143,112 @@ utils.defineProperty(secretStorage, 'decrypt', function(json, password, progress return utils.keccak256(utils.concat([derivedHalf, ciphertext])); } - return new Promise(function(resolve, reject) { - var kdf = searchPath(data, 'crypto/kdf'); - if (kdf && typeof(kdf) === 'string' && kdf.toLowerCase() === 'scrypt') { - var salt = arrayify(searchPath(data, 'crypto/kdfparams/salt'), 'crypto/kdfparams/salt'); - var N = parseInt(searchPath(data, 'crypto/kdfparams/n')); - var r = parseInt(searchPath(data, 'crypto/kdfparams/r')); - var p = parseInt(searchPath(data, 'crypto/kdfparams/p')); - if (!N || !r || !p) { - reject(new Error('unsupported key-derivation function parameters')); - return; - } + var getSigningKey = function(key) { + var ciphertext = arrayify(searchPath(data, 'crypto/ciphertext')); - // Make sure N is a power of 2 - if ((N & (N - 1)) !== 0) { - reject(new Error('unsupported key-derivation function parameter value for N')); - return; - } - - var dkLen = parseInt(searchPath(data, 'crypto/kdfparams/dklen')); - if (dkLen !== 32) { - reject( new Error('unsupported key-derivation derived-key length')); - return; - } - - scrypt(password, salt, N, r, p, dkLen, function(error, progress, key) { - if (error) { - error.progress = progress; - reject(error); - - } else if (key) { - key = arrayify(key); - - var ciphertext = arrayify(searchPath(data, 'crypto/ciphertext')); - - var computedMAC = utils.hexlify(computeMAC(key.slice(16, 32), ciphertext)).substring(2); - if (computedMAC !== searchPath(data, 'crypto/mac').toLowerCase()) { - reject(new Error('invalid password')); - return; - } - - var privateKey = decrypt(key.slice(0, 16), ciphertext); - - if (!privateKey) { - reject(new Error('unsupported cipher')); - return; - } - - var signingKey = new SigningKey(privateKey); - if (signingKey.address !== utils.getAddress(data.address)) { - reject(new Error('address mismatch')); - return; - } - - if (progressCallback) { progressCallback(1); } - resolve(signingKey); - - } else if (progressCallback) { - return progressCallback(progress); - } - }); - - } else { - // @TOOD: Support pbkdf2 kdf - reject(new Error('unsupported key-derivation function')); + var computedMAC = utils.hexlify(computeMAC(key.slice(16, 32), ciphertext)).substring(2); + if (computedMAC !== searchPath(data, 'crypto/mac').toLowerCase()) { + reject(new Error('invalid password')); + return null; } + var privateKey = decrypt(key.slice(0, 16), ciphertext); + + if (!privateKey) { + reject(new Error('unsupported cipher')); + return null; + } + + var signingKey = new SigningKey(privateKey); + if (signingKey.address !== utils.getAddress(data.address)) { + reject(new Error('address mismatch')); + return null; + } + + return signingKey; + } + + + return new Promise(function(resolve, reject) { + var kdf = searchPath(data, 'crypto/kdf'); + if (kdf && typeof(kdf) === 'string') { + if (kdf.toLowerCase() === 'scrypt') { + var salt = arrayify(searchPath(data, 'crypto/kdfparams/salt'), 'crypto/kdfparams/salt'); + var N = parseInt(searchPath(data, 'crypto/kdfparams/n')); + var r = parseInt(searchPath(data, 'crypto/kdfparams/r')); + var p = parseInt(searchPath(data, 'crypto/kdfparams/p')); + if (!N || !r || !p) { + reject(new Error('unsupported key-derivation function parameters')); + return; + } + + // Make sure N is a power of 2 + if ((N & (N - 1)) !== 0) { + reject(new Error('unsupported key-derivation function parameter value for N')); + return; + } + + var dkLen = parseInt(searchPath(data, 'crypto/kdfparams/dklen')); + if (dkLen !== 32) { + reject( new Error('unsupported key-derivation derived-key length')); + return; + } + + scrypt(password, salt, N, r, p, dkLen, function(error, progress, key) { + if (error) { + error.progress = progress; + reject(error); + + } else if (key) { + key = arrayify(key); + + var signingKey = getSigningKey(key); + if (!signingKey) { return; } + + if (progressCallback) { progressCallback(1); } + resolve(signingKey); + + } else if (progressCallback) { + return progressCallback(progress); + } + }); + + } else if (kdf.toLowerCase() === 'pbkdf2') { + var salt = arrayify(searchPath(data, 'crypto/kdfparams/salt'), 'crypto/kdfparams/salt'); + + var prfFunc = null; + var prf = searchPath(data, 'crypto/kdfparams/prf'); + if (prf === 'hmac-sha256') { + prfFunc = hmac.createSha256Hmac; + } else if (prf === 'hmac-sha512') { + prfFunc = hmac.createSha512Hmac; + } else { + reject(new Error('unsupported prf')); + return; + } + + var c = parseInt(searchPath(data, 'crypto/kdfparams/c')); + + var dkLen = parseInt(searchPath(data, 'crypto/kdfparams/dklen')); + if (dkLen !== 32) { + reject( new Error('unsupported key-derivation derived-key length')); + return; + } + + var key = pbkdf2(password, salt, c, dkLen, prfFunc); + + var signingKey = getSigningKey(key); + if (!signingKey) { return; } + + resolve(signingKey); + + } else { + reject(new Error('unsupported key-derivation function')); + } + + } else { + reject(new Error('unsupported key-derivation function')); + } }); });