Merge in crypto.

This commit is contained in:
Kevin Serrano 2016-10-19 11:17:29 -07:00
parent 1481a3ef8e
commit 17506fe14f
No known key found for this signature in database
GPG Key ID: 7CC862A58D2889B4
9 changed files with 195 additions and 5 deletions

3
.gitignore vendored
View File

@ -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

View File

@ -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.

View File

@ -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
}

View File

@ -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": {

View File

@ -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">

21
test/integration/index.js Normal file
View File

@ -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);

View File

@ -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')
})
})

View File

@ -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:
})

View File

@ -6,4 +6,5 @@ launch_in_ci:
- Firefox
framework:
- qunit
before_tests: "npm run buildCiUnits"
test_page: "test/integration/index.html"