mirror of
https://github.com/moparisthebest/mail
synced 2024-12-01 13:22:16 -05:00
refactored crypto
This commit is contained in:
parent
5d409933e5
commit
ccebe011cb
@ -2,7 +2,7 @@
|
|||||||
* High level crypto api that invokes native crypto (if available) and
|
* High level crypto api that invokes native crypto (if available) and
|
||||||
* gracefully degrades to JS crypto (if unavailable)
|
* gracefully degrades to JS crypto (if unavailable)
|
||||||
*/
|
*/
|
||||||
define(['cryptoLib/util', 'cryptoLib/aes-cbc', 'cryptoLib/rsa', 'cryptoLib/crypto-batch'], function(util, aes, rsa, cryptoBatch) {
|
define(['cryptoLib/util', 'cryptoLib/aes-cbc', 'cryptoLib/rsa', 'cryptoLib/crypto-batch', 'js/crypto/pbkdf2'], function(util, aes, rsa, cryptoBatch, pbkdf2) {
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
var self = {};
|
var self = {};
|
||||||
@ -26,7 +26,7 @@ define(['cryptoLib/util', 'cryptoLib/aes-cbc', 'cryptoLib/rsa', 'cryptoLib/crypt
|
|||||||
self.rsaKeySize = args.rsaKeySize;
|
self.rsaKeySize = args.rsaKeySize;
|
||||||
|
|
||||||
// derive PBKDF2 from password in web worker thread
|
// derive PBKDF2 from password in web worker thread
|
||||||
self.deriveKey(args.password, self.keySize, function(err, pbkdf2) {
|
self.deriveKey(args.password, self.keySize, function(err, derivedKey) {
|
||||||
if (err) {
|
if (err) {
|
||||||
callback(err);
|
callback(err);
|
||||||
return;
|
return;
|
||||||
@ -35,15 +35,15 @@ define(['cryptoLib/util', 'cryptoLib/aes-cbc', 'cryptoLib/rsa', 'cryptoLib/crypt
|
|||||||
// check if key exists
|
// check if key exists
|
||||||
if (!args.storedKeypair) {
|
if (!args.storedKeypair) {
|
||||||
// generate keys, encrypt and persist if none exists
|
// generate keys, encrypt and persist if none exists
|
||||||
generateKeypair(pbkdf2);
|
generateKeypair(derivedKey);
|
||||||
} else {
|
} else {
|
||||||
// decrypt key
|
// decrypt key
|
||||||
decryptKeypair(args.storedKeypair, pbkdf2);
|
decryptKeypair(args.storedKeypair, derivedKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
function generateKeypair(pbkdf2) {
|
function generateKeypair(derivedKey) {
|
||||||
// generate RSA keypair in web worker
|
// generate RSA keypair in web worker
|
||||||
rsa.generateKeypair(self.rsaKeySize, function(err, generatedKeypair) {
|
rsa.generateKeypair(self.rsaKeySize, function(err, generatedKeypair) {
|
||||||
if (err) {
|
if (err) {
|
||||||
@ -53,7 +53,7 @@ define(['cryptoLib/util', 'cryptoLib/aes-cbc', 'cryptoLib/rsa', 'cryptoLib/crypt
|
|||||||
|
|
||||||
// encrypt keypair
|
// encrypt keypair
|
||||||
var iv = util.random(self.ivSize);
|
var iv = util.random(self.ivSize);
|
||||||
var encryptedPrivateKey = aes.encrypt(generatedKeypair.privkeyPem, pbkdf2, iv);
|
var encryptedPrivateKey = aes.encrypt(generatedKeypair.privkeyPem, derivedKey, iv);
|
||||||
|
|
||||||
// new encrypted keypair object
|
// new encrypted keypair object
|
||||||
var newKeypair = {
|
var newKeypair = {
|
||||||
@ -75,7 +75,7 @@ define(['cryptoLib/util', 'cryptoLib/aes-cbc', 'cryptoLib/rsa', 'cryptoLib/crypt
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function decryptKeypair(storedKeypair, pbkdf2) {
|
function decryptKeypair(storedKeypair, derivedKey) {
|
||||||
var decryptedPrivateKey;
|
var decryptedPrivateKey;
|
||||||
|
|
||||||
// validate input
|
// validate input
|
||||||
@ -86,10 +86,10 @@ define(['cryptoLib/util', 'cryptoLib/aes-cbc', 'cryptoLib/rsa', 'cryptoLib/crypt
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// try to decrypt with pbkdf2
|
// try to decrypt with derivedKey
|
||||||
try {
|
try {
|
||||||
var prK = storedKeypair.privateKey;
|
var prK = storedKeypair.privateKey;
|
||||||
decryptedPrivateKey = aes.decrypt(prK.encryptedKey, pbkdf2, prK.iv);
|
decryptedPrivateKey = aes.decrypt(prK.encryptedKey, derivedKey, prK.iv);
|
||||||
} catch (ex) {
|
} catch (ex) {
|
||||||
callback({
|
callback({
|
||||||
errMsg: 'Wrong password!'
|
errMsg: 'Wrong password!'
|
||||||
@ -111,7 +111,6 @@ define(['cryptoLib/util', 'cryptoLib/aes-cbc', 'cryptoLib/rsa', 'cryptoLib/crypt
|
|||||||
password: password,
|
password: password,
|
||||||
keySize: keySize
|
keySize: keySize
|
||||||
}, callback, function() {
|
}, callback, function() {
|
||||||
var pbkdf2 = new app.crypto.PBKDF2();
|
|
||||||
return pbkdf2.getKey(password, keySize);
|
return pbkdf2.getKey(password, keySize);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
@ -1,16 +1,18 @@
|
|||||||
/**
|
/**
|
||||||
* A Wrapper for Forge's PBKDF2 function
|
* A Wrapper for Forge's PBKDF2 function
|
||||||
*/
|
*/
|
||||||
app.crypto.PBKDF2 = function() {
|
define(['forge'], function(forge) {
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
|
var self = {};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* PBKDF2-HMAC-SHA1 key derivation with a constant salt and 1000 iterations
|
* PBKDF2-HMAC-SHA1 key derivation with a constant salt and 1000 iterations
|
||||||
* @param password [String] The password in UTF8
|
* @param password [String] The password in UTF8
|
||||||
* @param keySize [Number] The key size in bits
|
* @param keySize [Number] The key size in bits
|
||||||
* @return [String] The base64 encoded key
|
* @return [String] The base64 encoded key
|
||||||
*/
|
*/
|
||||||
this.getKey = function(password, keySize) {
|
self.getKey = function(password, keySize) {
|
||||||
var salt = forge.util.decode64("vbhmLjC+Ub6MSbhS6/CkOwxB25wvwRkSLP2DzDtYb+4=");
|
var salt = forge.util.decode64("vbhmLjC+Ub6MSbhS6/CkOwxB25wvwRkSLP2DzDtYb+4=");
|
||||||
var key = forge.pkcs5.pbkdf2(password, salt, 1000, keySize / 8);
|
var key = forge.pkcs5.pbkdf2(password, salt, 1000, keySize / 8);
|
||||||
var keyBase64 = forge.util.encode64(key);
|
var keyBase64 = forge.util.encode64(key);
|
||||||
@ -18,4 +20,5 @@ app.crypto.PBKDF2 = function() {
|
|||||||
return keyBase64;
|
return keyBase64;
|
||||||
};
|
};
|
||||||
|
|
||||||
};
|
return self;
|
||||||
|
});
|
@ -1,4 +1,4 @@
|
|||||||
(function() {
|
define(['backbone'], function(Backbone) {
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
app.model.Email = Backbone.Model.extend({
|
app.model.Email = Backbone.Model.extend({
|
||||||
@ -24,4 +24,4 @@
|
|||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
}());
|
});
|
@ -1,4 +1,4 @@
|
|||||||
define(['cryptoLib/util'], function(util) {
|
define(['cryptoLib/util', 'js/model/email-model'], function(util) {
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
var self = {};
|
var self = {};
|
||||||
|
@ -1,108 +1,109 @@
|
|||||||
'use strict';
|
define(['js/crypto/crypto', 'cryptoLib/util', 'test/test-data'], function(crypto, util, testData) {
|
||||||
|
'use strict';
|
||||||
|
|
||||||
module("Crypto Api");
|
module("Crypto Api");
|
||||||
|
|
||||||
var crypto_test = {
|
var cryptoTest = {
|
||||||
user: 'crypto_test@example.com',
|
user: 'crypto_test@example.com',
|
||||||
password: 'Password',
|
password: 'Password',
|
||||||
keySize: 128,
|
keySize: 128,
|
||||||
ivSize: 128,
|
ivSize: 128,
|
||||||
rsaKeySize: 1024
|
rsaKeySize: 1024
|
||||||
};
|
};
|
||||||
|
|
||||||
asyncTest("Init without keypair", 4, function() {
|
asyncTest("Init without keypair", 4, function() {
|
||||||
// init dependencies
|
// init dependencies
|
||||||
crypto_test.util = new cryptoLib.Util(window, uuid);
|
ok(crypto, 'Crypto');
|
||||||
crypto_test.crypto = new app.crypto.Crypto(window, crypto_test.util);
|
|
||||||
ok(crypto_test.crypto, 'Crypto');
|
|
||||||
|
|
||||||
// test without passing keys
|
// test without passing keys
|
||||||
crypto_test.crypto.init({
|
crypto.init({
|
||||||
emailAddress: crypto_test.user,
|
emailAddress: cryptoTest.user,
|
||||||
password: crypto_test.password,
|
password: cryptoTest.password,
|
||||||
keySize: crypto_test.keySize,
|
keySize: cryptoTest.keySize,
|
||||||
rsaKeySize: crypto_test.rsaKeySize
|
rsaKeySize: cryptoTest.rsaKeySize
|
||||||
}, function(err, generatedKeypair) {
|
}, function(err, generatedKeypair) {
|
||||||
ok(!err && generatedKeypair, 'Init crypto without keypair input');
|
ok(!err && generatedKeypair, 'Init crypto without keypair input');
|
||||||
var pk = generatedKeypair.publicKey;
|
var pk = generatedKeypair.publicKey;
|
||||||
ok(pk._id && pk.userId, 'Key ID: ' + pk._id);
|
ok(pk._id && pk.userId, 'Key ID: ' + pk._id);
|
||||||
ok(pk.publicKey.indexOf('-----BEGIN PUBLIC KEY-----') === 0, pk.publicKey);
|
ok(pk.publicKey.indexOf('-----BEGIN PUBLIC KEY-----') === 0, pk.publicKey);
|
||||||
crypto_test.generatedKeypair = generatedKeypair;
|
cryptoTest.generatedKeypair = generatedKeypair;
|
||||||
|
|
||||||
start();
|
start();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
asyncTest("Init with keypair", 1, function() {
|
asyncTest("Init with keypair", 1, function() {
|
||||||
// test with passing keypair
|
// test with passing keypair
|
||||||
crypto_test.crypto.init({
|
crypto.init({
|
||||||
emailAddress: crypto_test.user,
|
emailAddress: cryptoTest.user,
|
||||||
password: crypto_test.password,
|
password: cryptoTest.password,
|
||||||
keySize: crypto_test.keySize,
|
keySize: cryptoTest.keySize,
|
||||||
rsaKeySize: crypto_test.rsaKeySize,
|
rsaKeySize: cryptoTest.rsaKeySize,
|
||||||
storedKeypair: crypto_test.generatedKeypair
|
storedKeypair: cryptoTest.generatedKeypair
|
||||||
}, function(err, generatedKeypair) {
|
}, function(err, generatedKeypair) {
|
||||||
ok(!err && !generatedKeypair, 'Init crypto with keypair input');
|
ok(!err && !generatedKeypair, 'Init crypto with keypair input');
|
||||||
|
|
||||||
start();
|
start();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
asyncTest("PBKDF2 (Async/Worker)", 2, function() {
|
asyncTest("PBKDF2 (Async/Worker)", 2, function() {
|
||||||
crypto_test.crypto.deriveKey(crypto_test.password, crypto_test.keySize, function(err, key) {
|
crypto.deriveKey(cryptoTest.password, cryptoTest.keySize, function(err, key) {
|
||||||
ok(!err);
|
ok(!err);
|
||||||
equal(crypto_test.util.base642Str(key).length * 8, crypto_test.keySize, 'Keysize ' + crypto_test.keySize);
|
equal(util.base642Str(key).length * 8, cryptoTest.keySize, 'Keysize ' + cryptoTest.keySize);
|
||||||
|
|
||||||
start();
|
start();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
asyncTest("AES en/decrypt (Async/Worker)", 4, function() {
|
asyncTest("AES en/decrypt (Async/Worker)", 4, function() {
|
||||||
var secret = 'Big secret';
|
var secret = 'Big secret';
|
||||||
|
|
||||||
var key = crypto_test.util.random(crypto_test.keySize);
|
var key = util.random(cryptoTest.keySize);
|
||||||
var iv = crypto_test.util.random(crypto_test.ivSize);
|
var iv = util.random(cryptoTest.ivSize);
|
||||||
|
|
||||||
crypto_test.crypto.aesEncrypt(secret, key, iv, function(err, ciphertext) {
|
crypto.aesEncrypt(secret, key, iv, function(err, ciphertext) {
|
||||||
ok(!err);
|
ok(!err);
|
||||||
ok(ciphertext, 'Encrypt item');
|
ok(ciphertext, 'Encrypt item');
|
||||||
|
|
||||||
crypto_test.crypto.aesDecrypt(ciphertext, key, iv, function(err, decrypted) {
|
crypto.aesDecrypt(ciphertext, key, iv, function(err, decrypted) {
|
||||||
ok(!err);
|
ok(!err);
|
||||||
equal(decrypted, secret, 'Decrypt item');
|
equal(decrypted, secret, 'Decrypt item');
|
||||||
|
|
||||||
start();
|
start();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
asyncTest("AES/RSA encrypt batch for User (Async/Worker)", 2, function() {
|
asyncTest("AES/RSA encrypt batch for User (Async/Worker)", 2, function() {
|
||||||
// generate test data
|
// generate test data
|
||||||
var collection, td = new TestData();
|
var collection;
|
||||||
|
|
||||||
collection = td.getEmailCollection(10);
|
collection = testData.getEmailCollection(10);
|
||||||
crypto_test.list = collection.toJSON();
|
cryptoTest.list = collection.toJSON();
|
||||||
|
|
||||||
var receiverPubkeys = [crypto_test.generatedKeypair.publicKey];
|
var receiverPubkeys = [cryptoTest.generatedKeypair.publicKey];
|
||||||
|
|
||||||
crypto_test.crypto.encryptListForUser(crypto_test.list, receiverPubkeys, function(err, encryptedList) {
|
crypto.encryptListForUser(cryptoTest.list, receiverPubkeys, function(err, encryptedList) {
|
||||||
ok(!err && encryptedList, 'Encrypt list for user');
|
ok(!err && encryptedList, 'Encrypt list for user');
|
||||||
equal(encryptedList.length, crypto_test.list.length, 'Length of list');
|
equal(encryptedList.length, cryptoTest.list.length, 'Length of list');
|
||||||
crypto_test.encryptedList = encryptedList;
|
cryptoTest.encryptedList = encryptedList;
|
||||||
|
|
||||||
start();
|
start();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
asyncTest("AES/RSA decrypt batch for User (Async/Worker)", 3, function() {
|
asyncTest("AES/RSA decrypt batch for User (Async/Worker)", 3, function() {
|
||||||
|
|
||||||
var senderPubkeys = [crypto_test.generatedKeypair.publicKey];
|
var senderPubkeys = [cryptoTest.generatedKeypair.publicKey];
|
||||||
|
|
||||||
crypto_test.crypto.decryptListForUser(crypto_test.encryptedList, senderPubkeys, function(err, decryptedList) {
|
crypto.decryptListForUser(cryptoTest.encryptedList, senderPubkeys, function(err, decryptedList) {
|
||||||
ok(!err && decryptedList, 'Decrypt list');
|
ok(!err && decryptedList, 'Decrypt list');
|
||||||
equal(decryptedList.length, crypto_test.list.length, 'Length of list');
|
equal(decryptedList.length, cryptoTest.list.length, 'Length of list');
|
||||||
deepEqual(decryptedList, crypto_test.list, 'Decrypted list is correct');
|
deepEqual(decryptedList, cryptoTest.list, 'Decrypted list is correct');
|
||||||
|
|
||||||
start();
|
start();
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
|
||||||
});
|
});
|
@ -25,7 +25,8 @@ function startTests() {
|
|||||||
'test/unit/aes-test',
|
'test/unit/aes-test',
|
||||||
'test/unit/rsa-test',
|
'test/unit/rsa-test',
|
||||||
'test/unit/lawnchair-dao-test',
|
'test/unit/lawnchair-dao-test',
|
||||||
'test/unit/keychain-dao-test'
|
'test/unit/keychain-dao-test',
|
||||||
|
'test/unit/crypto-test'
|
||||||
], function() {
|
], function() {
|
||||||
//Tests loaded, run tests
|
//Tests loaded, run tests
|
||||||
QUnit.start();
|
QUnit.start();
|
||||||
|
Loading…
Reference in New Issue
Block a user