diff options
author | Kevin Serrano <kevgagser@gmail.com> | 2016-10-20 02:17:29 +0800 |
---|---|---|
committer | Kevin Serrano <kevgagser@gmail.com> | 2016-10-20 02:17:29 +0800 |
commit | 17506fe14f84680bc6b5421eff4c797154a513bd (patch) | |
tree | 339b1562b84402f7a609ec1d5ab5aea1a2cab76d | |
parent | 1481a3ef8e3352eb74fa11c4f578d15d84c76de7 (diff) | |
download | tangerine-wallet-browser-17506fe14f84680bc6b5421eff4c797154a513bd.tar tangerine-wallet-browser-17506fe14f84680bc6b5421eff4c797154a513bd.tar.gz tangerine-wallet-browser-17506fe14f84680bc6b5421eff4c797154a513bd.tar.bz2 tangerine-wallet-browser-17506fe14f84680bc6b5421eff4c797154a513bd.tar.lz tangerine-wallet-browser-17506fe14f84680bc6b5421eff4c797154a513bd.tar.xz tangerine-wallet-browser-17506fe14f84680bc6b5421eff4c797154a513bd.tar.zst tangerine-wallet-browser-17506fe14f84680bc6b5421eff4c797154a513bd.zip |
Merge in crypto.
-rw-r--r-- | .gitignore | 3 | ||||
-rw-r--r-- | README.md | 4 | ||||
-rw-r--r-- | app/scripts/lib/encryptor.js | 119 | ||||
-rw-r--r-- | package.json | 3 | ||||
-rw-r--r-- | test/integration/index.html | 2 | ||||
-rw-r--r-- | test/integration/index.js | 21 | ||||
-rw-r--r-- | test/integration/lib/encryptor-test.js | 44 | ||||
-rw-r--r-- | test/integration/lib/first-time.js (renamed from test/integration/tests.js) | 3 | ||||
-rw-r--r-- | testem.yml | 1 |
9 files changed, 195 insertions, 5 deletions
diff --git a/.gitignore b/.gitignore index fa8a9151f..0b649d486 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,4 @@ dist - node_modules temp .tmp @@ -7,10 +6,10 @@ temp app/bower_components test/bower_components package - .DS_Store builds/ notes.txt app/.DS_Store development/bundle.js builds.zip +test/integration/bundle.js @@ -90,6 +90,10 @@ You can also test with a continuously watching process, via `npm run watch`. You can run the linter by itself with `gulp lint`. +#### Writing Browser Tests + +To write tests that will be run in the browser using QUnit, add your test files to `test/integration/lib`. + ### Deploying the UI You must be authorized already on the MetaMask plugin. diff --git a/app/scripts/lib/encryptor.js b/app/scripts/lib/encryptor.js new file mode 100644 index 000000000..91d6ed5ce --- /dev/null +++ b/app/scripts/lib/encryptor.js @@ -0,0 +1,119 @@ +var ethUtil = require('ethereumjs-util') + +module.exports = { + + // Simple encryption methods: + encrypt, + decrypt, + + // More advanced encryption methods: + keyFromPassword, + encryptWithKey, + decryptWithKey, + + // Buffer <-> String methods + convertArrayBufferViewtoString, + convertStringToArrayBufferView, + + // Buffer <-> Hex string methods + serializeBufferForStorage, + serializeBufferFromStorage, +} + +// Takes a Pojo, returns encrypted text. +function encrypt (password, dataObj) { + return keyFromPassword(password) + .then(function (passwordDerivedKey) { + return encryptWithKey(passwordDerivedKey, dataObj) + }) +} + +function encryptWithKey (key, dataObj) { + var data = JSON.stringify(dataObj) + var dataBuffer = convertStringToArrayBufferView(data) + var vector = global.crypto.getRandomValues(new Uint8Array(16)) + + return global.crypto.subtle.encrypt({ + name: 'AES-GCM', + iv: vector, + }, key, dataBuffer).then(function(buf){ + var buffer = new Uint8Array(buf) + var vectorStr = serializeBufferForStorage(vector) + return serializeBufferForStorage(buffer) + vectorStr + }) +} + +// Takes encrypted text, returns the restored Pojo. +function decrypt (password, text) { + return keyFromPassword(password) + .then(function (key) { + return decryptWithKey(key, text) + }) +} + +function decryptWithKey (key, text) { + const parts = text.split('0x') + const encryptedData = serializeBufferFromStorage(parts[1]) + const vector = serializeBufferFromStorage(parts[2]) + return crypto.subtle.decrypt({name: 'AES-GCM', iv: vector}, key, encryptedData) + .then(function(result){ + const decryptedData = new Uint8Array(result) + const decryptedStr = convertArrayBufferViewtoString(decryptedData) + const decryptedObj = JSON.parse(decryptedStr) + return decryptedObj + }) +} + +function convertStringToArrayBufferView (str) { + var bytes = new Uint8Array(str.length) + for (var i = 0; i < str.length; i++) { + bytes[i] = str.charCodeAt(i) + } + + return bytes +} + +function convertArrayBufferViewtoString (buffer) { + var str = '' + for (var i = 0; i < buffer.byteLength; i++) { + str += String.fromCharCode(buffer[i]) + } + + return str +} + +function keyFromPassword (password) { + var passBuffer = convertStringToArrayBufferView(password) + return global.crypto.subtle.digest('SHA-256', passBuffer) + .then(function (passHash){ + return global.crypto.subtle.importKey('raw', passHash, {name: 'AES-GCM'}, false, ['encrypt', 'decrypt']) + }) +} + +function serializeBufferFromStorage (str) { + str = ethUtil.stripHexPrefix(str) + var buf = new Uint8Array(str.length / 2) + for (var i = 0; i < str.length; i += 2) { + var seg = str.substr(i, 2) + buf[i / 2] = parseInt(seg, 16) + } + return buf +} + +// Should return a string, ready for storage, in hex format. +function serializeBufferForStorage (buffer) { + var result = '0x' + var len = buffer.length || buffer.byteLength + for (var i = 0; i < len; i++) { + result += unprefixedHex(buffer[i]) + } + return result +} + +function unprefixedHex (num) { + var hex = num.toString(16) + while (hex.length < 2) { + hex = '0' + hex + } + return hex +} diff --git a/package.json b/package.json index ea67eee75..b654a6402 100644 --- a/package.json +++ b/package.json @@ -8,6 +8,7 @@ "lint": "gulp lint", "dev": "gulp dev", "dist": "gulp dist", + "buildCiUnits": "node test/integration/index.js", "test": "npm run fastTest && npm run ci && npm run lint", "fastTest": "mocha --require test/helper.js --compilers js:babel-register --recursive \"test/unit/**/*.js\"", "watch": "mocha watch --compilers js:babel-register --recursive \"test/unit/**/*.js\"", @@ -15,7 +16,7 @@ "mock": "beefy mock-dev.js:bundle.js --live --open --index=./development/index.html --cwd ./", "buildMock": "browserify ./mock-dev.js -o ./development/bundle.js", "testem": "npm run buildMock && testem", - "ci": "npm run buildMock && testem ci -P 2", + "ci": "npm run buildMock && npm run buildCiUnits && testem ci -P 2", "announce": "node development/announcer.js" }, "browserify": { diff --git a/test/integration/index.html b/test/integration/index.html index 6de40b046..ad4b4eb14 100644 --- a/test/integration/index.html +++ b/test/integration/index.html @@ -12,7 +12,7 @@ <script src="https://code.jquery.com/qunit/qunit-2.0.0.js"></script> <script src="./jquery-3.1.0.min.js"></script> <script src="helpers.js"></script> - <script src="tests.js"></script> + <script src="bundle.js"></script> <script src="/testem.js"></script> <iframe src="/development/index.html" height="500px" width="360px"> diff --git a/test/integration/index.js b/test/integration/index.js new file mode 100644 index 000000000..ff6d1baf8 --- /dev/null +++ b/test/integration/index.js @@ -0,0 +1,21 @@ +var fs = require('fs') +var path = require('path') +var browserify = require('browserify'); +var tests = fs.readdirSync(path.join(__dirname, 'lib')) +var bundlePath = path.join(__dirname, 'bundle.js') + +var b = browserify(); + +// Remove old bundle +try { + fs.unlinkSync(bundlePath) +} catch (e) {} + +var writeStream = fs.createWriteStream(bundlePath) + +tests.forEach(function(fileName) { + b.add(path.join(__dirname, 'lib', fileName)) +}) + +b.bundle().pipe(writeStream); + diff --git a/test/integration/lib/encryptor-test.js b/test/integration/lib/encryptor-test.js new file mode 100644 index 000000000..88ebed51b --- /dev/null +++ b/test/integration/lib/encryptor-test.js @@ -0,0 +1,44 @@ +var encryptor = require('../../../app/scripts/lib/encryptor') + +QUnit.test('encryptor:serializeBufferForStorage', function (assert) { + assert.expect(1) + var buf = new Buffer(2) + buf[0] = 16 + buf[1] = 1 + + var output = encryptor.serializeBufferForStorage(buf) + + var expect = '0x1001' + assert.equal(expect, output) +}) + +QUnit.test('encryptor:serializeBufferFromStorage', function (assert) { + assert.expect(2) + var input = '0x1001' + var output = encryptor.serializeBufferFromStorage(input) + + assert.equal(output[0], 16) + assert.equal(output[1], 1) +}) + +QUnit.test('encryptor:encrypt & decrypt', function(assert) { + var done = assert.async(); + var password, data, encrypted + + password = 'a sample passw0rd' + data = { foo: 'data to encrypt' } + + encryptor.encrypt(password, data) + .then(function(encryptedStr) { + assert.equal(typeof encryptedStr, 'string', 'returns a string') + return encryptor.decrypt(password, encryptedStr) + }) + .then(function (decryptedObj) { + assert.deepEqual(decryptedObj, data, 'decrypted what was encrypted') + done() + }) + .catch(function(reason) { + assert.ifError(reason, 'threw an error') + }) + +}) diff --git a/test/integration/tests.js b/test/integration/lib/first-time.js index 92111b05b..af9b94e24 100644 --- a/test/integration/tests.js +++ b/test/integration/lib/first-time.js @@ -15,10 +15,11 @@ QUnit.test('agree to terms', function (assert) { assert.equal(title, 'MetaMask', 'title screen') var buttons = app.find('button') - assert.equal(buttons.length, 2, 'two buttons: create and restore') + assert.equal(buttons.length, 1, 'one button: create new vault') done() }) // Wait for view to transition: }) + diff --git a/testem.yml b/testem.yml index 7923a2929..2cf40f7f4 100644 --- a/testem.yml +++ b/testem.yml @@ -6,4 +6,5 @@ launch_in_ci: - Firefox framework: - qunit +before_tests: "npm run buildCiUnits" test_page: "test/integration/index.html" |