mirror of
https://github.com/kremalicious/metamask-extension.git
synced 2024-12-23 01:39:44 +01:00
Merge in crypto.
This commit is contained in:
parent
1481a3ef8e
commit
17506fe14f
3
.gitignore
vendored
3
.gitignore
vendored
@ -1,5 +1,4 @@
|
|||||||
dist
|
dist
|
||||||
|
|
||||||
node_modules
|
node_modules
|
||||||
temp
|
temp
|
||||||
.tmp
|
.tmp
|
||||||
@ -7,10 +6,10 @@ temp
|
|||||||
app/bower_components
|
app/bower_components
|
||||||
test/bower_components
|
test/bower_components
|
||||||
package
|
package
|
||||||
|
|
||||||
.DS_Store
|
.DS_Store
|
||||||
builds/
|
builds/
|
||||||
notes.txt
|
notes.txt
|
||||||
app/.DS_Store
|
app/.DS_Store
|
||||||
development/bundle.js
|
development/bundle.js
|
||||||
builds.zip
|
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`.
|
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
|
### Deploying the UI
|
||||||
|
|
||||||
You must be authorized already on the MetaMask plugin.
|
You must be authorized already on the MetaMask plugin.
|
||||||
|
119
app/scripts/lib/encryptor.js
Normal file
119
app/scripts/lib/encryptor.js
Normal 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
|
||||||
|
}
|
@ -8,6 +8,7 @@
|
|||||||
"lint": "gulp lint",
|
"lint": "gulp lint",
|
||||||
"dev": "gulp dev",
|
"dev": "gulp dev",
|
||||||
"dist": "gulp dist",
|
"dist": "gulp dist",
|
||||||
|
"buildCiUnits": "node test/integration/index.js",
|
||||||
"test": "npm run fastTest && npm run ci && npm run lint",
|
"test": "npm run fastTest && npm run ci && npm run lint",
|
||||||
"fastTest": "mocha --require test/helper.js --compilers js:babel-register --recursive \"test/unit/**/*.js\"",
|
"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\"",
|
"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 ./",
|
"mock": "beefy mock-dev.js:bundle.js --live --open --index=./development/index.html --cwd ./",
|
||||||
"buildMock": "browserify ./mock-dev.js -o ./development/bundle.js",
|
"buildMock": "browserify ./mock-dev.js -o ./development/bundle.js",
|
||||||
"testem": "npm run buildMock && testem",
|
"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"
|
"announce": "node development/announcer.js"
|
||||||
},
|
},
|
||||||
"browserify": {
|
"browserify": {
|
||||||
|
@ -12,7 +12,7 @@
|
|||||||
<script src="https://code.jquery.com/qunit/qunit-2.0.0.js"></script>
|
<script src="https://code.jquery.com/qunit/qunit-2.0.0.js"></script>
|
||||||
<script src="./jquery-3.1.0.min.js"></script>
|
<script src="./jquery-3.1.0.min.js"></script>
|
||||||
<script src="helpers.js"></script>
|
<script src="helpers.js"></script>
|
||||||
<script src="tests.js"></script>
|
<script src="bundle.js"></script>
|
||||||
<script src="/testem.js"></script>
|
<script src="/testem.js"></script>
|
||||||
|
|
||||||
<iframe src="/development/index.html" height="500px" width="360px">
|
<iframe src="/development/index.html" height="500px" width="360px">
|
||||||
|
21
test/integration/index.js
Normal file
21
test/integration/index.js
Normal 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);
|
||||||
|
|
44
test/integration/lib/encryptor-test.js
Normal file
44
test/integration/lib/encryptor-test.js
Normal 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')
|
||||||
|
})
|
||||||
|
|
||||||
|
})
|
@ -15,10 +15,11 @@ QUnit.test('agree to terms', function (assert) {
|
|||||||
assert.equal(title, 'MetaMask', 'title screen')
|
assert.equal(title, 'MetaMask', 'title screen')
|
||||||
|
|
||||||
var buttons = app.find('button')
|
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()
|
done()
|
||||||
})
|
})
|
||||||
|
|
||||||
// Wait for view to transition:
|
// Wait for view to transition:
|
||||||
})
|
})
|
||||||
|
|
@ -6,4 +6,5 @@ launch_in_ci:
|
|||||||
- Firefox
|
- Firefox
|
||||||
framework:
|
framework:
|
||||||
- qunit
|
- qunit
|
||||||
|
before_tests: "npm run buildCiUnits"
|
||||||
test_page: "test/integration/index.html"
|
test_page: "test/integration/index.html"
|
||||||
|
Loading…
Reference in New Issue
Block a user