Skip to content

Commit fcc9ca8

Browse files
authored
Merge pull request #638 from MetaMask/i555-SaltVault
Add new eth-lightwallet salting to vault.
2 parents 3e836ab + 56b9b17 commit fcc9ca8

File tree

8 files changed

+151
-89
lines changed

8 files changed

+151
-89
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
- MetaMask logo now renders as super lightweight SVG, improving compatibility and performance.
1111
- Now showing loading indication during vault unlocking, to clarify behavior for users who are experience slow unlocks.
1212
- Now only initially creates one wallet when restoring a vault, to reduce some users' confusion.
13+
- Improved the security of vault encryption by ensuring passwords are always uniquely salted.
1314

1415
## 2.10.2 2016-09-02
1516

app/scripts/lib/idStore.js

Lines changed: 64 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ const inherits = require('util').inherits
33
const async = require('async')
44
const ethUtil = require('ethereumjs-util')
55
const EthQuery = require('eth-query')
6-
const LightwalletKeyStore = require('eth-lightwallet').keystore
6+
const KeyStore = require('eth-lightwallet').keystore
77
const clone = require('clone')
88
const extend = require('xtend')
99
const createId = require('web3-provider-engine/util/random-id')
@@ -50,15 +50,16 @@ IdentityStore.prototype.createNewVault = function (password, entropy, cb) {
5050
if (serializedKeystore) {
5151
this.configManager.setData({})
5252
}
53+
5354
this._createIdmgmt(password, null, entropy, (err) => {
5455
if (err) return cb(err)
5556

56-
this._loadIdentities()
57-
this._didUpdate()
5857
this._autoFaucet()
5958

6059
this.configManager.setShowSeedWords(true)
6160
var seedWords = this._idmgmt.getSeed()
61+
62+
6263
cb(null, seedWords)
6364
})
6465
}
@@ -75,7 +76,6 @@ IdentityStore.prototype.recoverFromSeed = function (password, seed, cb) {
7576
if (err) return cb(err)
7677

7778
this._loadIdentities()
78-
this._didUpdate()
7979
cb(null, this.getState())
8080
})
8181
}
@@ -125,15 +125,20 @@ IdentityStore.prototype.getSelectedAddress = function () {
125125
return configManager.getSelectedAccount()
126126
}
127127

128-
IdentityStore.prototype.setSelectedAddress = function (address, cb) {
128+
IdentityStore.prototype.setSelectedAddressSync = function (address) {
129129
const configManager = this.configManager
130130
if (!address) {
131131
var addresses = this._getAddresses()
132132
address = addresses[0]
133133
}
134134

135135
configManager.setSelectedAccount(address)
136-
if (cb) return cb(null, address)
136+
return address
137+
}
138+
139+
IdentityStore.prototype.setSelectedAddress = function (address, cb) {
140+
const resultAddress = this.setSelectedAddressSync(address)
141+
if (cb) return cb(null, resultAddress)
137142
}
138143

139144
IdentityStore.prototype.revealAccount = function (cb) {
@@ -143,6 +148,7 @@ IdentityStore.prototype.revealAccount = function (cb) {
143148

144149
keyStore.setDefaultHdDerivationPath(this.hdPathString)
145150
keyStore.generateNewAddress(derivedKey, 1)
151+
146152
configManager.setWallet(keyStore.serialize())
147153

148154
this._loadIdentities()
@@ -393,7 +399,6 @@ IdentityStore.prototype._loadIdentities = function () {
393399
var addresses = this._getAddresses()
394400
addresses.forEach((address, i) => {
395401
// // add to ethStore
396-
this._ethStore.addAccount(address)
397402
// add to identities
398403
const defaultLabel = 'Wallet ' + (i + 1)
399404
const nickname = configManager.nicknameForWallet(address)
@@ -412,7 +417,6 @@ IdentityStore.prototype.saveAccountLabel = function (account, label, cb) {
412417
configManager.setNicknameForWallet(account, label)
413418
this._loadIdentities()
414419
cb(null, label)
415-
this._didUpdate()
416420
}
417421

418422
// mayBeFauceting
@@ -436,77 +440,76 @@ IdentityStore.prototype._mayBeFauceting = function (i) {
436440
//
437441

438442
IdentityStore.prototype.tryPassword = function (password, cb) {
439-
this._createIdmgmt(password, null, null, cb)
440-
}
441-
442-
IdentityStore.prototype._createIdmgmt = function (password, seed, entropy, cb) {
443-
const configManager = this.configManager
443+
var serializedKeystore = this.configManager.getWallet()
444+
var keyStore = KeyStore.deserialize(serializedKeystore)
444445

445-
var keyStore = null
446-
LightwalletKeyStore.deriveKeyFromPassword(password, (err, derivedKey) => {
446+
keyStore.keyFromPassword(password, (err, pwDerivedKey) => {
447447
if (err) return cb(err)
448-
var serializedKeystore = configManager.getWallet()
449-
450-
if (seed) {
451-
try {
452-
keyStore = this._restoreFromSeed(password, seed, derivedKey)
453-
} catch (e) {
454-
return cb(e)
455-
}
456-
457-
// returning user, recovering from storage
458-
} else if (serializedKeystore) {
459-
keyStore = LightwalletKeyStore.deserialize(serializedKeystore)
460-
var isCorrect = keyStore.isDerivedKeyCorrect(derivedKey)
461-
if (!isCorrect) return cb(new Error('Lightwallet - password incorrect'))
462-
463-
// first time here
464-
} else {
465-
keyStore = this._createFirstWallet(entropy, derivedKey)
466-
}
467448

468-
this._keyStore = keyStore
469-
this._idmgmt = new IdManagement({
470-
keyStore: keyStore,
471-
derivedKey: derivedKey,
472-
hdPathSTring: this.hdPathString,
473-
configManager: this.configManager,
474-
})
449+
const isCorrect = keyStore.isDerivedKeyCorrect(pwDerivedKey)
450+
if (!isCorrect) return cb(new Error('Lightwallet - password incorrect'))
475451

476452
cb()
477453
})
478454
}
479455

480-
IdentityStore.prototype._restoreFromSeed = function (password, seed, derivedKey) {
481-
const configManager = this.configManager
482-
var keyStore = new LightwalletKeyStore(seed, derivedKey, this.hdPathString)
483-
keyStore.addHdDerivationPath(this.hdPathString, derivedKey, {curve: 'secp256k1', purpose: 'sign'})
484-
keyStore.setDefaultHdDerivationPath(this.hdPathString)
456+
IdentityStore.prototype._createIdmgmt = function (password, seedPhrase, entropy, cb) {
457+
const opts = {
458+
password,
459+
hdPathString: this.hdPathString,
460+
}
485461

486-
keyStore.generateNewAddress(derivedKey, 1)
487-
configManager.setWallet(keyStore.serialize())
488-
if (global.METAMASK_DEBUG) {
489-
console.log('restored from seed. saved to keystore')
462+
if (seedPhrase) {
463+
opts.seedPhrase = seedPhrase
490464
}
491-
return keyStore
465+
466+
KeyStore.createVault(opts, (err, keyStore) => {
467+
if (err) return cb(err)
468+
469+
this._keyStore = keyStore
470+
471+
keyStore.keyFromPassword(password, (err, derivedKey) => {
472+
if (err) return cb(err)
473+
474+
this.purgeCache()
475+
476+
keyStore.addHdDerivationPath(this.hdPathString, derivedKey, {curve: 'secp256k1', purpose: 'sign'})
477+
478+
this._createFirstWallet(derivedKey)
479+
480+
this._idmgmt = new IdManagement({
481+
keyStore: keyStore,
482+
derivedKey: derivedKey,
483+
configManager: this.configManager,
484+
})
485+
486+
this.setSelectedAddressSync()
487+
488+
cb()
489+
})
490+
})
492491
}
493492

494-
IdentityStore.prototype._createFirstWallet = function (entropy, derivedKey) {
495-
const configManager = this.configManager
496-
var secretSeed = LightwalletKeyStore.generateRandomSeed(entropy)
497-
var keyStore = new LightwalletKeyStore(secretSeed, derivedKey, this.hdPathString)
498-
keyStore.addHdDerivationPath(this.hdPathString, derivedKey, {curve: 'secp256k1', purpose: 'sign'})
499-
keyStore.setDefaultHdDerivationPath(this.hdPathString)
493+
IdentityStore.prototype.purgeCache = function () {
494+
this._getAddresses().forEach((address) => {
495+
this._ethStore.del(address)
496+
})
497+
}
500498

499+
IdentityStore.prototype._createFirstWallet = function (derivedKey) {
500+
const keyStore = this._keyStore
501+
keyStore.setDefaultHdDerivationPath(this.hdPathString)
501502
keyStore.generateNewAddress(derivedKey, 1)
502-
configManager.setWallet(keyStore.serialize())
503-
console.log('saved to keystore')
504-
return keyStore
503+
this.configManager.setWallet(keyStore.serialize())
504+
var addresses = keyStore.getAddresses()
505+
this._ethStore.addAccount(ethUtil.addHexPrefix(addresses[0]))
505506
}
506507

507508
// get addresses and normalize address hexString
508509
IdentityStore.prototype._getAddresses = function () {
509-
return this._keyStore.getAddresses(this.hdPathString).map((address) => { return '0x' + address })
510+
return this._keyStore.getAddresses(this.hdPathString).map((address) => {
511+
return ethUtil.addHexPrefix(address)
512+
})
510513
}
511514

512515
IdentityStore.prototype._autoFaucet = function () {

test/unit/idStore-test.js

Lines changed: 71 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
var assert = require('assert')
22
var IdentityStore = require('../../app/scripts/lib/idStore')
33
var configManagerGen = require('../lib/mock-config-manager')
4+
const ethUtil = require('ethereumjs-util')
5+
const async = require('async')
46

57
describe('IdentityStore', function() {
68

@@ -18,11 +20,12 @@ describe('IdentityStore', function() {
1820
idStore = new IdentityStore({
1921
configManager: configManagerGen(),
2022
ethStore: {
21-
addAccount(acct) { accounts.push(acct) },
23+
addAccount(acct) { accounts.push(ethUtil.addHexPrefix(acct)) },
2224
},
2325
})
2426

2527
idStore.createNewVault(password, entropy, (err, seeds) => {
28+
assert.ifError(err, 'createNewVault threw error')
2629
seedWords = seeds
2730
originalKeystore = idStore._idmgmt.keyStore
2831
done()
@@ -38,7 +41,7 @@ describe('IdentityStore', function() {
3841
idStore = new IdentityStore({
3942
configManager: configManagerGen(),
4043
ethStore: {
41-
addAccount(acct) { newAccounts.push(acct) },
44+
addAccount(acct) { newAccounts.push(ethUtil.addHexPrefix(acct)) },
4245
},
4346
})
4447
})
@@ -57,33 +60,91 @@ describe('IdentityStore', function() {
5760
})
5861

5962
describe('#recoverFromSeed BIP44 compliance', function() {
60-
let seedWords = 'picnic injury awful upper eagle junk alert toss flower renew silly vague'
61-
let firstAccount = '0x5d8de92c205279c10e5669f797b853ccef4f739a'
63+
const salt = 'lightwalletSalt'
6264

6365
let password = 'secret!'
6466
let accounts = []
6567
let idStore
6668

69+
var assertions = [
70+
{
71+
seed: 'picnic injury awful upper eagle junk alert toss flower renew silly vague',
72+
account: '0x5d8de92c205279c10e5669f797b853ccef4f739a',
73+
},
74+
{
75+
seed: 'radar blur cabbage chef fix engine embark joy scheme fiction master release',
76+
account: '0xe15d894becb0354c501ae69429b05143679f39e0',
77+
},
78+
{
79+
seed: 'phone coyote caught pattern found table wedding list tumble broccoli chief swing',
80+
account: '0xb0e868f24bc7fec2bce2efc2b1c344d7569cd9d2',
81+
},
82+
{
83+
seed: 'recycle tag bird palace blue village anxiety census cook soldier example music',
84+
account: '0xab34a45920afe4af212b96ec51232aaa6a33f663',
85+
},
86+
{
87+
seed: 'half glimpse tape cute harvest sweet bike voyage actual floor poet lazy',
88+
account: '0x28e9044597b625ac4beda7250011670223de43b2',
89+
},
90+
{
91+
seed: 'flavor tiger carpet motor angry hungry document inquiry large critic usage liar',
92+
account: '0xb571be96558940c4e9292e1999461aa7499fb6cd',
93+
},
94+
]
95+
6796
before(function() {
6897
window.localStorage = {} // Hacking localStorage support into JSDom
6998

7099
idStore = new IdentityStore({
71100
configManager: configManagerGen(),
72101
ethStore: {
73-
addAccount(acct) { accounts.push(acct) },
102+
addAccount(acct) { accounts.push(ethUtil.addHexPrefix(acct)) },
74103
},
75104
})
76105
})
77106

78-
it('should return the expected first account', function (done) {
107+
beforeEach(function() {
108+
accounts = []
109+
})
79110

80-
idStore.recoverFromSeed(password, seedWords, (err) => {
81-
assert.ifError(err)
111+
it('should enforce seed compliance with TestRPC', function (done) {
112+
const tests = assertions.map((assertion) => {
113+
return function (cb) {
114+
accounts = []
115+
idStore.recoverFromSeed(password, assertion.seed, (err) => {
116+
assert.ifError(err)
117+
118+
var received = accounts[0].toLowerCase()
119+
var expected = assertion.account.toLowerCase()
120+
assert.equal(received, expected)
121+
cb()
122+
})
123+
}
124+
})
82125

83-
let newKeystore = idStore._idmgmt.keyStore
84-
assert.equal(accounts[0], firstAccount)
126+
async.series(tests, function(err, results) {
127+
assert.ifError(err)
85128
done()
86129
})
87130
})
131+
132+
it('should allow restoring and unlocking again', function (done) {
133+
const assertion = assertions[0]
134+
idStore.recoverFromSeed(password, assertion.seed, (err) => {
135+
assert.ifError(err)
136+
137+
var received = accounts[0].toLowerCase()
138+
var expected = assertion.account.toLowerCase()
139+
assert.equal(received, expected)
140+
141+
142+
idStore.submitPassword(password, function(err, account) {
143+
assert.ifError(err)
144+
assert.equal(account, expected)
145+
done()
146+
})
147+
})
148+
})
88149
})
89150
})

ui/app/accounts/account-list-item.js

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,14 @@ const EthBalance = require('../components/eth-balance')
77
const CopyButton = require('../components/copyButton')
88
const Identicon = require('../components/identicon')
99

10-
module.exports = NewComponent
10+
module.exports = AccountListItem
1111

12-
inherits(NewComponent, Component)
13-
function NewComponent () {
12+
inherits(AccountListItem, Component)
13+
function AccountListItem () {
1414
Component.call(this)
1515
}
1616

17-
NewComponent.prototype.render = function () {
17+
AccountListItem.prototype.render = function () {
1818
const identity = this.props.identity
1919
var isSelected = this.props.selectedAddress === identity.address
2020
var account = this.props.accounts[identity.address]
@@ -23,9 +23,6 @@ NewComponent.prototype.render = function () {
2323
return (
2424
h(`.accounts-list-option.flex-row.flex-space-between.pointer.hover-white${selectedClass}`, {
2525
key: `account-panel-${identity.address}`,
26-
style: {
27-
flex: '1 0 auto',
28-
},
2926
onClick: (event) => this.props.onShowDetail(identity.address, event),
3027
}, [
3128

@@ -73,7 +70,7 @@ NewComponent.prototype.render = function () {
7370
)
7471
}
7572

76-
NewComponent.prototype.pendingOrNot = function () {
73+
AccountListItem.prototype.pendingOrNot = function () {
7774
const pending = this.props.pending
7875
if (pending.length === 0) return null
7976
return h('.pending-dot', pending.length)

0 commit comments

Comments
 (0)