From 5ec311ba3e01bd9b0a9ff447fd7639d22a7b3d9c Mon Sep 17 00:00:00 2001 From: Csaba Solya Date: Thu, 22 Feb 2018 14:39:32 +0100 Subject: [PATCH 01/11] add edge support --- app/scripts/background.js | 8 ++++ app/scripts/edge-encryptor.js | 69 +++++++++++++++++++++++++++++++++++ package.json | 1 + 3 files changed, 78 insertions(+) create mode 100644 app/scripts/edge-encryptor.js diff --git a/app/scripts/background.js b/app/scripts/background.js index 6bf7707e8..7bececba1 100644 --- a/app/scripts/background.js +++ b/app/scripts/background.js @@ -15,6 +15,7 @@ const MetamaskController = require('./metamask-controller') const firstTimeState = require('./first-time-state') const setupRaven = require('./setupRaven') const setupMetamaskMeshMetrics = require('./lib/setupMetamaskMeshMetrics') +const EdgeEncryptor = require('./edge-encryptor') const STORAGE_KEY = 'metamask-config' const METAMASK_DEBUG = 'GULP_METAMASK_DEBUG' @@ -30,6 +31,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 // state persistence @@ -78,6 +85,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..9d6ac37b3 --- /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/package.json b/package.json index 74a9f15d0..c47c46004 100644 --- a/package.json +++ b/package.json @@ -51,6 +51,7 @@ ] }, "dependencies": { + "asmcrypto.js": "0.22.0", "async": "^2.5.0", "await-semaphore": "^0.1.1", "babel-runtime": "^6.23.0", From c1aa59f6eddac0a37f0084e37ab1281e109d1c80 Mon Sep 17 00:00:00 2001 From: Csaba Solya Date: Fri, 23 Feb 2018 10:08:23 +0100 Subject: [PATCH 02/11] adding tests --- test/unit/edge-encryptor-test.js | 66 ++++++++++++++++++++++++++++++++ 1 file changed, 66 insertions(+) create mode 100644 test/unit/edge-encryptor-test.js diff --git a/test/unit/edge-encryptor-test.js b/test/unit/edge-encryptor-test.js new file mode 100644 index 000000000..627386051 --- /dev/null +++ b/test/unit/edge-encryptor-test.js @@ -0,0 +1,66 @@ +const assert = require('assert') + +const EdgeEncryptor = require('../../app/scripts/edge-encryptor') + +var password = 'passw0rd1' +var data = 'some random data' + +// polyfill fetch +global.crypto = global.crypto || { + getRandomValues (array) { + for (let i = 0; i < array.length; i++) { + array[i] = Math.random() * 100; + } + } +} + +describe('EdgeEncryptor', function () { + const edgeEncryptor = new EdgeEncryptor() + + describe('encrypt', function () { + + it('should encrypt the data.', function () { + edgeEncryptor.encrypt(password, data) + .then(function (encryptedData) { + assert.notEqual(data, encryptedData) + assert.notEqual(encryptedData.length, 0) + done() + }).catch(function (err) { + done(err) + }) + }) + + it('should not return the same twice.', function () { + + 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) + }) + }) + }) + + describe('decrypt', function () { + it('should be able to decrypt the encrypted data.', function () { + + edgeEncryptor.encrypt(password, data) + .then(function (encryptedData) { + edgeEncryptor.decrypt(password, encryptedData) + .then(function (decryptedData) { + assert.equal(decryptedData, data) + }) + .catch(function (err) { + done(err) + }) + }) + .catch(function (err) { + done(err) + }) + }) + }) +}) From 73d9bfc52cfb4b63f0960d80a7b68f2bf6f7d88c Mon Sep 17 00:00:00 2001 From: Csaba Solya Date: Fri, 23 Feb 2018 10:09:16 +0100 Subject: [PATCH 03/11] make keyFromPassword private --- app/scripts/edge-encryptor.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/scripts/edge-encryptor.js b/app/scripts/edge-encryptor.js index 9d6ac37b3..24c0c93a8 100644 --- a/app/scripts/edge-encryptor.js +++ b/app/scripts/edge-encryptor.js @@ -6,7 +6,7 @@ class EdgeEncryptor { encrypt (password, dataObject) { var salt = this._generateSalt() - return this.keyFromPassword(password, salt) + return this._keyFromPassword(password, salt) .then(function (key) { var data = JSON.stringify(dataObject) @@ -29,7 +29,7 @@ class EdgeEncryptor { const payload = JSON.parse(text) const salt = payload.salt - return this.keyFromPassword(password, salt) + return this._keyFromPassword(password, salt) .then(function (key) { const encryptedData = Unibabel.base64ToBuffer(payload.data) const vector = Unibabel.base64ToBuffer(payload.iv) @@ -48,7 +48,7 @@ class EdgeEncryptor { }) } - keyFromPassword (password, salt) { + _keyFromPassword (password, salt) { var passBuffer = Unibabel.utf8ToBuffer(password) var saltBuffer = Unibabel.base64ToBuffer(salt) From cd05d77c3fd9fe8e49d38b43728ff90b72b1ca9d Mon Sep 17 00:00:00 2001 From: Csaba Solya Date: Fri, 23 Feb 2018 10:49:56 +0100 Subject: [PATCH 04/11] fix tests --- test/unit/edge-encryptor-test.js | 26 ++++++++++++++++++++------ 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/test/unit/edge-encryptor-test.js b/test/unit/edge-encryptor-test.js index 627386051..ef733a494 100644 --- a/test/unit/edge-encryptor-test.js +++ b/test/unit/edge-encryptor-test.js @@ -5,21 +5,21 @@ const EdgeEncryptor = require('../../app/scripts/edge-encryptor') var password = 'passw0rd1' var data = 'some random data' -// polyfill fetch global.crypto = global.crypto || { - getRandomValues (array) { + getRandomValues: function (array) { for (let i = 0; i < array.length; i++) { - array[i] = Math.random() * 100; + array[i] = Math.random() * 100 } + return array } } describe('EdgeEncryptor', function () { - const edgeEncryptor = new EdgeEncryptor() + const edgeEncryptor = new EdgeEncryptor() describe('encrypt', function () { - it('should encrypt the data.', function () { + it('should encrypt the data.', function (done) { edgeEncryptor.encrypt(password, data) .then(function (encryptedData) { assert.notEqual(data, encryptedData) @@ -30,6 +30,19 @@ describe('EdgeEncryptor', function () { }) }) + 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 () { const encryptPromises = [] @@ -46,13 +59,14 @@ describe('EdgeEncryptor', function () { }) describe('decrypt', function () { - it('should be able to decrypt the encrypted data.', 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) From 8292dabed56b858fa2ccec7497627f5e5aa65181 Mon Sep 17 00:00:00 2001 From: Csaba Solya Date: Fri, 23 Feb 2018 11:03:53 +0100 Subject: [PATCH 05/11] add negative decrypt test --- test/unit/edge-encryptor-test.js | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/test/unit/edge-encryptor-test.js b/test/unit/edge-encryptor-test.js index ef733a494..1dad4b91e 100644 --- a/test/unit/edge-encryptor-test.js +++ b/test/unit/edge-encryptor-test.js @@ -76,5 +76,25 @@ describe('EdgeEncryptor', function () { 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) + }) + }) }) }) From 1b367bc2150434f5d2324d4344dfbbeff0eb4967 Mon Sep 17 00:00:00 2001 From: Csaba Solya Date: Fri, 23 Feb 2018 11:11:14 +0100 Subject: [PATCH 06/11] fix test --- test/unit/edge-encryptor-test.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/unit/edge-encryptor-test.js b/test/unit/edge-encryptor-test.js index 1dad4b91e..d3f014d74 100644 --- a/test/unit/edge-encryptor-test.js +++ b/test/unit/edge-encryptor-test.js @@ -43,7 +43,7 @@ describe('EdgeEncryptor', function () { }) }) - it('should not return the same twice.', function () { + it('should not return the same twice.', function (done) { const encryptPromises = [] encryptPromises.push(edgeEncryptor.encrypt(password, data)) @@ -54,6 +54,7 @@ describe('EdgeEncryptor', function () { assert.notEqual(encryptedData[0], encryptedData[1]) assert.notEqual(encryptedData[0].length, 0) assert.notEqual(encryptedData[1].length, 0) + done() }) }) }) From d8038c0de046e7e34f019143eb74238a13a1d69e Mon Sep 17 00:00:00 2001 From: Csaba Solya Date: Tue, 6 Mar 2018 09:23:43 +0100 Subject: [PATCH 07/11] add browserify-unibabel to package.json --- package.json | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 4d1742d6a..145754d6b 100644 --- a/package.json +++ b/package.json @@ -64,6 +64,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 +79,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", From 9734969e5d09e73778f18e9842ecb4677589a722 Mon Sep 17 00:00:00 2001 From: Dan J Miller Date: Tue, 6 Mar 2018 10:51:21 -0330 Subject: [PATCH 08/11] Add missed changelog updates. (#3407) --- CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8fc9d2145..fdc7d7155 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,10 +2,15 @@ ## Current Master +- Allow adding custom tokens to classic ui when balance is 0 +- Allow editing of symbol and decimal info when adding custom token in new-ui +- NewUI shapeshift form can select all coins (not just BTC) + ## 4.1.3 2018-2-28 - Ensure MetaMask's inpage provider is named MetamaskInpageProvider to keep some sites from breaking. - Add retry transaction button back into classic ui. +- Add network dropdown styles to support long custom RPC urls ## 4.1.2 2018-2-28 From 5f8a632fec0e83b148e4e0b7fc95339fb870d804 Mon Sep 17 00:00:00 2001 From: Alexander Tseung Date: Tue, 6 Mar 2018 09:14:57 -0800 Subject: [PATCH 09/11] Fix seed phrase validation clearing form (#3417) * Fix seed phrase validation clearing form * Make new ui import seed error feedback live, and allow newlines with and without carriage returns. --- .../first-time/import-seed-phrase-screen.js | 197 ++++++++++-------- mascara/src/app/first-time/index.css | 16 +- ui/app/app.js | 8 +- ui/app/css/itcss/components/header.scss | 7 +- 4 files changed, 133 insertions(+), 95 deletions(-) diff --git a/mascara/src/app/first-time/import-seed-phrase-screen.js b/mascara/src/app/first-time/import-seed-phrase-screen.js index 93c3f9203..de8d675e1 100644 --- a/mascara/src/app/first-time/import-seed-phrase-screen.js +++ b/mascara/src/app/first-time/import-seed-phrase-screen.js @@ -1,13 +1,12 @@ import React, { Component } from 'react' import PropTypes from 'prop-types' import {connect} from 'react-redux' -import LoadingScreen from './loading-screen' +import classnames from 'classnames' import { createNewVaultAndRestore, hideWarning, displayWarning, unMarkPasswordForgotten, - clearNotices, } from '../../../../ui/app/actions' class ImportSeedPhraseScreen extends Component { @@ -17,8 +16,8 @@ class ImportSeedPhraseScreen extends Component { next: PropTypes.func.isRequired, createNewVaultAndRestore: PropTypes.func.isRequired, hideWarning: PropTypes.func.isRequired, - isLoading: PropTypes.bool.isRequired, displayWarning: PropTypes.func, + leaveImportSeedScreenState: PropTypes.func, }; state = { @@ -27,98 +26,130 @@ class ImportSeedPhraseScreen extends Component { confirmPassword: '', } + parseSeedPhrase = (seedPhrase) => { + return seedPhrase + .match(/\w+/g) + .join(' ') + } + + onChange = ({ seedPhrase, password, confirmPassword }) => { + const { + password: prevPassword, + confirmPassword: prevConfirmPassword, + } = this.state + const { displayWarning, hideWarning } = this.props + + let warning = null + + if (seedPhrase && this.parseSeedPhrase(seedPhrase).split(' ').length !== 12) { + warning = 'Seed Phrases are 12 words long' + } else if (password && password.length < 8) { + warning = 'Passwords require a mimimum length of 8' + } else if ((password || prevPassword) !== (confirmPassword || prevConfirmPassword)) { + warning = 'Confirmed password does not match' + } + + if (warning) { + displayWarning(warning) + } else { + hideWarning() + } + + seedPhrase && this.setState({ seedPhrase }) + password && this.setState({ password }) + confirmPassword && this.setState({ confirmPassword }) + } + onClick = () => { - const { password, seedPhrase, confirmPassword } = this.state - const { createNewVaultAndRestore, next, displayWarning, leaveImportSeedScreenState } = this.props + const { password, seedPhrase } = this.state + const { + createNewVaultAndRestore, + next, + displayWarning, + leaveImportSeedScreenState, + } = this.props - if (seedPhrase.split(' ').length !== 12) { - this.warning = 'Seed Phrases are 12 words long' - displayWarning(this.warning) - return - } - - if (password.length < 8) { - this.warning = 'Passwords require a mimimum length of 8' - displayWarning(this.warning) - return - } - - if (password !== confirmPassword) { - this.warning = 'Confirmed password does not match' - displayWarning(this.warning) - return - } - this.warning = null leaveImportSeedScreenState() - createNewVaultAndRestore(password, seedPhrase) + createNewVaultAndRestore(password, this.parseSeedPhrase(seedPhrase)) .then(next) } render () { - return this.props.isLoading - ? - : ( -
- { - e.preventDefault() - this.props.back() - }} - href="#" - > - {`< Back`} - -
- Import an Account with Seed Phrase -
-
- Enter your secret twelve word phrase here to restore your vault. -
-
- -