aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--app/scripts/background.js8
-rw-r--r--app/scripts/edge-encryptor.js69
-rw-r--r--old-ui/app/info.js6
-rw-r--r--package.json6
-rw-r--r--test/unit/edge-encryptor-test.js101
-rw-r--r--ui/app/add-token.js2
-rw-r--r--ui/app/send-v2.js15
7 files changed, 196 insertions, 11 deletions
diff --git a/app/scripts/background.js b/app/scripts/background.js
index 4487ff318..601ae0372 100644
--- a/app/scripts/background.js
+++ b/app/scripts/background.js
@@ -16,6 +16,7 @@ const firstTimeState = require('./first-time-state')
const setupRaven = require('./lib/setupRaven')
const reportFailedTxToSentry = require('./lib/reportFailedTxToSentry')
const setupMetamaskMeshMetrics = require('./lib/setupMetamaskMeshMetrics')
+const EdgeEncryptor = require('./edge-encryptor')
const STORAGE_KEY = 'metamask-config'
@@ -32,6 +33,12 @@ global.METAMASK_NOTIFIER = notificationManager
const release = platform.getVersion()
const raven = setupRaven({ release })
+// browser check if it is Edge - https://stackoverflow.com/questions/9847580/how-to-detect-safari-chrome-ie-firefox-and-opera-browser
+// Internet Explorer 6-11
+const isIE = !!document.documentMode
+// Edge 20+
+const isEdge = !isIE && !!window.StyleMedia
+
let popupIsOpen = false
let openMetamaskTabsIDs = {}
@@ -81,6 +88,7 @@ function setupController (initState) {
initState,
// platform specific api
platform,
+ encryptor: isEdge ? new EdgeEncryptor() : undefined,
})
global.metamaskController = controller
diff --git a/app/scripts/edge-encryptor.js b/app/scripts/edge-encryptor.js
new file mode 100644
index 000000000..24c0c93a8
--- /dev/null
+++ b/app/scripts/edge-encryptor.js
@@ -0,0 +1,69 @@
+const asmcrypto = require('asmcrypto.js')
+const Unibabel = require('browserify-unibabel')
+
+class EdgeEncryptor {
+
+ encrypt (password, dataObject) {
+
+ var salt = this._generateSalt()
+ return this._keyFromPassword(password, salt)
+ .then(function (key) {
+
+ var data = JSON.stringify(dataObject)
+ var dataBuffer = Unibabel.utf8ToBuffer(data)
+ var vector = global.crypto.getRandomValues(new Uint8Array(16))
+ var resultbuffer = asmcrypto.AES_GCM.encrypt(dataBuffer, key, vector)
+
+ var buffer = new Uint8Array(resultbuffer)
+ var vectorStr = Unibabel.bufferToBase64(vector)
+ var vaultStr = Unibabel.bufferToBase64(buffer)
+ return JSON.stringify({
+ data: vaultStr,
+ iv: vectorStr,
+ salt: salt,
+ })
+ })
+ }
+
+ decrypt (password, text) {
+
+ const payload = JSON.parse(text)
+ const salt = payload.salt
+ return this._keyFromPassword(password, salt)
+ .then(function (key) {
+ const encryptedData = Unibabel.base64ToBuffer(payload.data)
+ const vector = Unibabel.base64ToBuffer(payload.iv)
+ return new Promise((resolve, reject) => {
+ var result
+ try {
+ result = asmcrypto.AES_GCM.decrypt(encryptedData, key, vector)
+ } catch (err) {
+ return reject(new Error('Incorrect password'))
+ }
+ const decryptedData = new Uint8Array(result)
+ const decryptedStr = Unibabel.bufferToUtf8(decryptedData)
+ const decryptedObj = JSON.parse(decryptedStr)
+ resolve(decryptedObj)
+ })
+ })
+ }
+
+ _keyFromPassword (password, salt) {
+
+ var passBuffer = Unibabel.utf8ToBuffer(password)
+ var saltBuffer = Unibabel.base64ToBuffer(salt)
+ return new Promise((resolve) => {
+ var key = asmcrypto.PBKDF2_HMAC_SHA256.bytes(passBuffer, saltBuffer, 10000)
+ resolve(key)
+ })
+ }
+
+ _generateSalt (byteCount = 32) {
+ var view = new Uint8Array(byteCount)
+ global.crypto.getRandomValues(view)
+ var b64encoded = btoa(String.fromCharCode.apply(null, view))
+ return b64encoded
+ }
+}
+
+module.exports = EdgeEncryptor
diff --git a/old-ui/app/info.js b/old-ui/app/info.js
index db9f30f23..d79b8a3d2 100644
--- a/old-ui/app/info.js
+++ b/old-ui/app/info.js
@@ -63,7 +63,7 @@ InfoScreen.prototype.render = function () {
h('a', {
href: 'https://metamask.io/privacy.html',
target: '_blank',
- onClick (event) { this.navigateTo(event.target.href) },
+ onClick: (event) => { this.navigateTo(event.target.href) },
}, [
h('div.info', 'Privacy Policy'),
]),
@@ -72,7 +72,7 @@ InfoScreen.prototype.render = function () {
h('a', {
href: 'https://metamask.io/terms.html',
target: '_blank',
- onClick (event) { this.navigateTo(event.target.href) },
+ onClick: (event) => { this.navigateTo(event.target.href) },
}, [
h('div.info', 'Terms of Use'),
]),
@@ -81,7 +81,7 @@ InfoScreen.prototype.render = function () {
h('a', {
href: 'https://metamask.io/attributions.html',
target: '_blank',
- onClick (event) { this.navigateTo(event.target.href) },
+ onClick: (event) => { this.navigateTo(event.target.href) },
}, [
h('div.info', 'Attributions'),
]),
diff --git a/package.json b/package.json
index d4b498bc8..80949901a 100644
--- a/package.json
+++ b/package.json
@@ -54,6 +54,7 @@
},
"dependencies": {
"abi-decoder": "^1.0.9",
+ "asmcrypto.js": "0.22.0",
"async": "^2.5.0",
"await-semaphore": "^0.1.1",
"babel-runtime": "^6.23.0",
@@ -64,6 +65,7 @@
"boron": "^0.2.3",
"browser-passworder": "^2.0.3",
"browserify-derequire": "^0.9.4",
+ "browserify-unibabel": "^3.0.0",
"classnames": "^2.2.5",
"client-sw-ready-event": "^3.3.0",
"clone": "^2.1.1",
@@ -78,11 +80,11 @@
"eslint-plugin-react": "^7.4.0",
"eth-bin-to-ops": "^1.0.1",
"eth-block-tracker": "^2.3.0",
+ "eth-contract-metadata": "^1.1.5",
+ "eth-hd-keyring": "^1.2.1",
"eth-json-rpc-filters": "^1.2.5",
"eth-json-rpc-infura": "^3.0.0",
"eth-keyring-controller": "^2.1.4",
- "eth-contract-metadata": "^1.1.5",
- "eth-hd-keyring": "^1.2.1",
"eth-phishing-detect": "^1.1.4",
"eth-query": "^2.1.2",
"eth-sig-util": "^1.4.2",
diff --git a/test/unit/edge-encryptor-test.js b/test/unit/edge-encryptor-test.js
new file mode 100644
index 000000000..d3f014d74
--- /dev/null
+++ b/test/unit/edge-encryptor-test.js
@@ -0,0 +1,101 @@
+const assert = require('assert')
+
+const EdgeEncryptor = require('../../app/scripts/edge-encryptor')
+
+var password = 'passw0rd1'
+var data = 'some random data'
+
+global.crypto = global.crypto || {
+ getRandomValues: function (array) {
+ for (let i = 0; i < array.length; i++) {
+ array[i] = Math.random() * 100
+ }
+ return array
+ }
+}
+
+describe('EdgeEncryptor', function () {
+
+ const edgeEncryptor = new EdgeEncryptor()
+ describe('encrypt', function () {
+
+ it('should encrypt the data.', function (done) {
+ edgeEncryptor.encrypt(password, data)
+ .then(function (encryptedData) {
+ assert.notEqual(data, encryptedData)
+ assert.notEqual(encryptedData.length, 0)
+ done()
+ }).catch(function (err) {
+ done(err)
+ })
+ })
+
+ it('should return proper format.', function (done) {
+ edgeEncryptor.encrypt(password, data)
+ .then(function (encryptedData) {
+ let encryptedObject = JSON.parse(encryptedData)
+ assert.ok(encryptedObject.data, 'there is no data')
+ assert.ok(encryptedObject.iv && encryptedObject.iv.length != 0, 'there is no iv')
+ assert.ok(encryptedObject.salt && encryptedObject.salt.length != 0, 'there is no salt')
+ done()
+ }).catch(function (err) {
+ done(err)
+ })
+ })
+
+ it('should not return the same twice.', function (done) {
+
+ const encryptPromises = []
+ encryptPromises.push(edgeEncryptor.encrypt(password, data))
+ encryptPromises.push(edgeEncryptor.encrypt(password, data))
+
+ Promise.all(encryptPromises).then((encryptedData) => {
+ assert.equal(encryptedData.length, 2)
+ assert.notEqual(encryptedData[0], encryptedData[1])
+ assert.notEqual(encryptedData[0].length, 0)
+ assert.notEqual(encryptedData[1].length, 0)
+ done()
+ })
+ })
+ })
+
+ describe('decrypt', function () {
+ it('should be able to decrypt the encrypted data.', function (done) {
+
+ edgeEncryptor.encrypt(password, data)
+ .then(function (encryptedData) {
+ edgeEncryptor.decrypt(password, encryptedData)
+ .then(function (decryptedData) {
+ assert.equal(decryptedData, data)
+ done()
+ })
+ .catch(function (err) {
+ done(err)
+ })
+ })
+ .catch(function (err) {
+ done(err)
+ })
+ })
+
+ it('cannot decrypt the encrypted data with wrong password.', function (done) {
+
+ edgeEncryptor.encrypt(password, data)
+ .then(function (encryptedData) {
+ edgeEncryptor.decrypt('wrong password', encryptedData)
+ .then(function (decryptedData) {
+ assert.fail('could decrypt with wrong password')
+ done()
+ })
+ .catch(function (err) {
+ assert.ok(err instanceof Error)
+ assert.equal(err.message, 'Incorrect password')
+ done()
+ })
+ })
+ .catch(function (err) {
+ done(err)
+ })
+ })
+ })
+})
diff --git a/ui/app/add-token.js b/ui/app/add-token.js
index a1729ba8e..51c577987 100644
--- a/ui/app/add-token.js
+++ b/ui/app/add-token.js
@@ -52,7 +52,7 @@ function AddTokenScreen () {
isShowingConfirmation: false,
customAddress: '',
customSymbol: '',
- customDecimals: null,
+ customDecimals: '',
searchQuery: '',
isCollapsed: true,
selectedTokens: {},
diff --git a/ui/app/send-v2.js b/ui/app/send-v2.js
index 1d67150e3..fc1df1f51 100644
--- a/ui/app/send-v2.js
+++ b/ui/app/send-v2.js
@@ -361,8 +361,9 @@ SendTransactionScreen.prototype.validateAmount = function (value) {
})
}
+ const verifyTokenBalance = selectedToken && tokenBalance !== null
let sufficientTokens
- if (selectedToken) {
+ if (verifyTokenBalance) {
sufficientTokens = isTokenBalanceSufficient({
tokenBalance,
amount,
@@ -377,7 +378,7 @@ SendTransactionScreen.prototype.validateAmount = function (value) {
if (conversionRate && !sufficientBalance) {
amountError = 'Insufficient funds.'
- } else if (selectedToken && !sufficientTokens) {
+ } else if (verifyTokenBalance && !sufficientTokens) {
amountError = 'Insufficient tokens.'
} else if (amountLessThanZero) {
amountError = 'Can not send negative amounts of ETH.'
@@ -396,14 +397,15 @@ SendTransactionScreen.prototype.renderAmountRow = function () {
amount,
setMaxModeTo,
maxModeOn,
+ gasTotal,
} = this.props
return h('div.send-v2__form-row', [
- h('div.send-v2__form-label', [
+ h('div.send-v2__form-label', [
'Amount:',
this.renderErrorMessage('amount'),
- !errors.amount && h('div.send-v2__amount-max', {
+ !errors.amount && gasTotal && h('div.send-v2__amount-max', {
onClick: (event) => {
event.preventDefault()
setMaxModeTo(true)
@@ -491,9 +493,12 @@ SendTransactionScreen.prototype.renderFooter = function () {
goHome,
clearSend,
gasTotal,
+ tokenBalance,
+ selectedToken,
errors: { amount: amountError, to: toError },
} = this.props
+ const missingTokenBalance = selectedToken && !tokenBalance
const noErrors = !amountError && toError === null
return h('div.page-container__footer', [
@@ -504,7 +509,7 @@ SendTransactionScreen.prototype.renderFooter = function () {
},
}, 'Cancel'),
h('button.btn-clear.page-container__footer-button', {
- disabled: !noErrors || !gasTotal,
+ disabled: !noErrors || !gasTotal || missingTokenBalance,
onClick: event => this.onSubmit(event),
}, 'Next'),
])