mirror of
https://github.com/moparisthebest/mail
synced 2024-11-22 08:52:15 -05:00
optimized email sync from cloud to do RSA validation and decryption only once
This commit is contained in:
parent
09a104ce80
commit
6097000f9f
@ -16,6 +16,11 @@
|
||||
* @return [String] The base64 encoded ciphertext
|
||||
*/
|
||||
this.encrypt = function(plaintext, key, iv) {
|
||||
// validate args
|
||||
if (!plaintext || !key || !iv) {
|
||||
throw new Error("Missing args for encryption!");
|
||||
}
|
||||
|
||||
// decode args to utf8 and encrypt
|
||||
var cipher = forge.aes.createEncryptionCipher(utl.decode64(key));
|
||||
cipher.start(utl.decode64(iv));
|
||||
@ -34,6 +39,11 @@
|
||||
* @return [String] The decrypted plaintext in UTF-16
|
||||
*/
|
||||
this.decrypt = function(ciphertext, key, iv) {
|
||||
// validate args
|
||||
if (!ciphertext || !key || !iv) {
|
||||
throw new Error("Missing args for decryption!");
|
||||
}
|
||||
|
||||
// decode args input to utf8 decrypt
|
||||
var cipher = forge.aes.createDecryptionCipher(utl.decode64(key));
|
||||
cipher.start(utl.decode64(iv));
|
||||
|
@ -28,6 +28,14 @@
|
||||
// start decryption
|
||||
output = batch.decryptListForUser(i.list, i.senderPubkeys, i.receiverPrivkey);
|
||||
|
||||
} else if (i.type === 'reencrypt' && i.senderPubkeys && i.receiverPrivkey && i.list && i.symKey) {
|
||||
// start validation and re-encryption
|
||||
output = batch.reencryptListKeysForUser(i.list, i.senderPubkeys, i.receiverPrivkey, i.symKey);
|
||||
|
||||
} else if (i.type === 'decryptItems' && i.symKey && i.list) {
|
||||
// start decryption
|
||||
output = batch.decryptKeysAndList(i.list, i.symKey);
|
||||
|
||||
} else {
|
||||
throw 'Not all arguments for web worker crypto are defined!';
|
||||
}
|
||||
|
@ -97,7 +97,7 @@
|
||||
* Decrypt and verify a list of item keys using RSA
|
||||
* @param list [Array] The list of items to decrypt
|
||||
* @param senderPubkeys [Array] A list of public keys used to verify
|
||||
* @param receiverPrivkey [Array] The receiver's private key used to decrypt
|
||||
* @param receiverPrivkey [String] The receiver's private key used to decrypt
|
||||
*/
|
||||
this.decryptListKeysForUser = function(list, senderPubkeys, receiverPrivkey) {
|
||||
var senderPk,
|
||||
@ -120,6 +120,27 @@
|
||||
return list;
|
||||
};
|
||||
|
||||
/**
|
||||
* Decrypt a list of item keys using RSA and the encrypt them again using AES
|
||||
* @param list [Array] The list of items to decrypt
|
||||
* @param senderPubkeys [Array] A list of public keys used to verify
|
||||
* @param receiverPrivkey [String] The receiver's private key used to decrypt
|
||||
* @param symKey [String] The symmetric key used to re-encrypt the item key
|
||||
*/
|
||||
this.reencryptListKeysForUser = function(list, senderPubkeys, receiverPrivkey, symKey) {
|
||||
// verify and decrypt item keys using RSA
|
||||
this.decryptListKeysForUser(list, senderPubkeys, receiverPrivkey);
|
||||
|
||||
list.forEach(function(i) {
|
||||
// re-encrypt item key using aes
|
||||
i.encryptedKey = aes.encrypt(i.key, symKey, i.iv);
|
||||
|
||||
delete i.key;
|
||||
});
|
||||
|
||||
return list;
|
||||
};
|
||||
|
||||
/**
|
||||
* Decrypt an item using AES
|
||||
* @param i [Object] The item to decrypt
|
||||
@ -149,6 +170,32 @@
|
||||
return list;
|
||||
};
|
||||
|
||||
/**
|
||||
* Decrypt keys and items using AES
|
||||
* @param list [Array] The list of items to decrypt
|
||||
* @param symKey [String] The symmetric key used to re-encrypt the item key
|
||||
*/
|
||||
this.decryptKeysAndList = function(list, symKey) {
|
||||
var self = this,
|
||||
j;
|
||||
|
||||
list.forEach(function(i) {
|
||||
// decrypt item key
|
||||
i.key = aes.decrypt(i.encryptedKey, symKey, i.iv);
|
||||
// decrypt item for user
|
||||
self.decryptItem(i);
|
||||
|
||||
delete i.encryptedKey;
|
||||
});
|
||||
|
||||
// set plaintext as list item
|
||||
for (j = 0; j < list.length; j++) {
|
||||
list[j] = list[j].plaintext;
|
||||
}
|
||||
|
||||
return list;
|
||||
};
|
||||
|
||||
/**
|
||||
* Decrypt and verify an item using AES and RSA
|
||||
* @param i [Object] The item to decrypt
|
||||
|
@ -9,6 +9,8 @@ define(['cryptoLib/util', 'cryptoLib/aes-cbc', 'cryptoLib/rsa', 'cryptoLib/crypt
|
||||
|
||||
var self = {};
|
||||
|
||||
var passBasedKey;
|
||||
|
||||
/**
|
||||
* Initializes the crypto modules by fetching the user's
|
||||
* encrypted secret key from storage and storing it in memory.
|
||||
@ -34,6 +36,9 @@ define(['cryptoLib/util', 'cryptoLib/aes-cbc', 'cryptoLib/rsa', 'cryptoLib/crypt
|
||||
return;
|
||||
}
|
||||
|
||||
// remember pbkdf2 for later use
|
||||
passBasedKey = derivedKey;
|
||||
|
||||
// check if key exists
|
||||
if (!args.storedKeypair) {
|
||||
// generate keys, encrypt and persist if none exists
|
||||
@ -239,6 +244,42 @@ define(['cryptoLib/util', 'cryptoLib/aes-cbc', 'cryptoLib/rsa', 'cryptoLib/crypt
|
||||
});
|
||||
};
|
||||
|
||||
//
|
||||
// Re-encrypt keys item and items seperately
|
||||
//
|
||||
|
||||
self.reencryptListKeysForUser = function(list, senderPubkeys, callback) {
|
||||
var keypair = rsa.exportKeys();
|
||||
var receiverPrivkey = {
|
||||
_id: keypair._id,
|
||||
privateKey: keypair.privkeyPem
|
||||
};
|
||||
|
||||
startWorker('/crypto/crypto-batch-worker.js', {
|
||||
type: 'reencrypt',
|
||||
list: list,
|
||||
receiverPrivkey: receiverPrivkey,
|
||||
senderPubkeys: senderPubkeys,
|
||||
symKey: passBasedKey
|
||||
}, callback, function() {
|
||||
return cryptoBatch.reencryptListKeysForUser(list, senderPubkeys, receiverPrivkey, passBasedKey);
|
||||
});
|
||||
};
|
||||
|
||||
self.decryptKeysAndList = function(list, callback) {
|
||||
startWorker('/crypto/crypto-batch-worker.js', {
|
||||
type: 'decryptItems',
|
||||
list: list,
|
||||
symKey: passBasedKey
|
||||
}, callback, function() {
|
||||
return cryptoBatch.decryptKeysAndList(list, passBasedKey);
|
||||
});
|
||||
};
|
||||
|
||||
//
|
||||
// helper functions
|
||||
//
|
||||
|
||||
function startWorker(script, args, callback, noWorker) {
|
||||
// check for WebWorker support
|
||||
if (window.Worker) {
|
||||
|
@ -80,7 +80,7 @@ define(['underscore', 'cryptoLib/util', 'js/crypto/crypto', 'js/dao/lawnchair-da
|
||||
* @param num [Number] The number of items to fetch (null means fetch all)
|
||||
*/
|
||||
self.listItems = function(folderName, offset, num, callback) {
|
||||
var collection, folder, already, pubkeyIds = [];
|
||||
var collection, folder;
|
||||
|
||||
// check if items are in memory already (account.folders model)
|
||||
folder = self.account.get('folders').where({
|
||||
@ -99,45 +99,23 @@ define(['underscore', 'cryptoLib/util', 'js/crypto/crypto', 'js/dao/lawnchair-da
|
||||
return;
|
||||
}
|
||||
|
||||
// gather public key ids required to verify signatures
|
||||
encryptedList.forEach(function(i) {
|
||||
already = null;
|
||||
already = _.findWhere(pubkeyIds, {
|
||||
_id: i.senderPk
|
||||
});
|
||||
if (!already) {
|
||||
pubkeyIds.push({
|
||||
_id: i.senderPk
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// fetch public keys from keychain
|
||||
keychain.getPublicKeys(pubkeyIds, function(err, senderPubkeys) {
|
||||
// decrypt list
|
||||
crypto.decryptKeysAndList(encryptedList, function(err, decryptedList) {
|
||||
if (err) {
|
||||
callback(err);
|
||||
return;
|
||||
}
|
||||
|
||||
// decrypt list
|
||||
crypto.decryptListForUser(encryptedList, senderPubkeys, function(err, decryptedList) {
|
||||
if (err) {
|
||||
callback(err);
|
||||
return;
|
||||
}
|
||||
|
||||
// cache collection in folder memory
|
||||
if (decryptedList.length > 0) {
|
||||
folder = new app.model.Folder({
|
||||
name: folderName
|
||||
});
|
||||
folder.set('items', decryptedList);
|
||||
self.account.get('folders').add(folder);
|
||||
}
|
||||
|
||||
callback(null, decryptedList);
|
||||
});
|
||||
// cache collection in folder memory
|
||||
if (decryptedList.length > 0) {
|
||||
folder = new app.model.Folder({
|
||||
name: folderName
|
||||
});
|
||||
folder.set('items', decryptedList);
|
||||
self.account.get('folders').add(folder);
|
||||
}
|
||||
|
||||
callback(null, decryptedList);
|
||||
});
|
||||
});
|
||||
|
||||
@ -153,9 +131,9 @@ define(['underscore', 'cryptoLib/util', 'js/crypto/crypto', 'js/dao/lawnchair-da
|
||||
* @param folderName [String] The name of the folder e.g. 'inbox'
|
||||
*/
|
||||
self.syncFromCloud = function(folderName, callback) {
|
||||
var folder;
|
||||
var folder, already, pubkeyIds = [];
|
||||
|
||||
cloudstorage.listEncryptedItems('email', self.account.get('emailAddress'), folderName, function(err, data) {
|
||||
cloudstorage.listEncryptedItems('email', self.account.get('emailAddress'), folderName, function(err, encryptedList) {
|
||||
// return if an error occured
|
||||
if (err) {
|
||||
callback({
|
||||
@ -164,19 +142,53 @@ define(['underscore', 'cryptoLib/util', 'js/crypto/crypto', 'js/dao/lawnchair-da
|
||||
}); // error
|
||||
return;
|
||||
}
|
||||
if (encryptedList.length === 0) {
|
||||
callback();
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: remove old folder items from devicestorage
|
||||
|
||||
// persist encrypted list in device storage
|
||||
devicestorage.storeEcryptedList(data, 'email_' + folderName, function() {
|
||||
// remove cached folder in account model
|
||||
folder = self.account.get('folders').where({
|
||||
name: folderName
|
||||
})[0];
|
||||
if (folder) {
|
||||
self.account.get('folders').remove(folder);
|
||||
// gather public key ids required to verify signatures
|
||||
encryptedList.forEach(function(i) {
|
||||
already = null;
|
||||
already = _.findWhere(pubkeyIds, {
|
||||
_id: i.senderPk
|
||||
});
|
||||
if (!already) {
|
||||
pubkeyIds.push({
|
||||
_id: i.senderPk
|
||||
});
|
||||
}
|
||||
callback();
|
||||
});
|
||||
|
||||
// fetch public keys from keychain
|
||||
keychain.getPublicKeys(pubkeyIds, function(err, senderPubkeys) {
|
||||
if (err) {
|
||||
callback(err);
|
||||
return;
|
||||
}
|
||||
|
||||
// verfiy signatures and re-encrypt item keys
|
||||
crypto.reencryptListKeysForUser(encryptedList, senderPubkeys, function(err, encryptedKeyList) {
|
||||
if (err) {
|
||||
callback(err);
|
||||
return;
|
||||
}
|
||||
|
||||
// persist encrypted list in device storage
|
||||
devicestorage.storeEcryptedList(encryptedKeyList, 'email_' + folderName, function() {
|
||||
// remove cached folder in account model
|
||||
folder = self.account.get('folders').where({
|
||||
name: folderName
|
||||
})[0];
|
||||
if (folder) {
|
||||
self.account.get('folders').remove(folder);
|
||||
}
|
||||
callback();
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
};
|
||||
|
@ -17,7 +17,7 @@ define(['js/dao/email-dao', 'js/dao/keychain-dao', 'js/dao/lawnchair-dao',
|
||||
// init dependencies
|
||||
jsonDao.init(emaildaoTest.user);
|
||||
// cloud storage stub
|
||||
var cloudstorageStub = {
|
||||
emaildaoTest.cloudstorageStub = {
|
||||
putPublicKey: function(pk, callback) {
|
||||
callback();
|
||||
},
|
||||
@ -28,8 +28,8 @@ define(['js/dao/email-dao', 'js/dao/keychain-dao', 'js/dao/lawnchair-dao',
|
||||
callback();
|
||||
}
|
||||
};
|
||||
emaildaoTest.keychain = new KeychainDAO(cloudstorageStub);
|
||||
emaildaoTest.emailDao = new EmailDAO(cloudstorageStub, emaildaoTest.keychain);
|
||||
emaildaoTest.keychain = new KeychainDAO(emaildaoTest.cloudstorageStub);
|
||||
emaildaoTest.emailDao = new EmailDAO(emaildaoTest.cloudstorageStub, emaildaoTest.keychain);
|
||||
|
||||
// generate test data
|
||||
emaildaoTest.list = testData.getEmailCollection(100);
|
||||
@ -54,7 +54,7 @@ define(['js/dao/email-dao', 'js/dao/keychain-dao', 'js/dao/lawnchair-dao',
|
||||
});
|
||||
});
|
||||
|
||||
asyncTest("Persist test emails", 4, function() {
|
||||
asyncTest("Persist test emails (stubbed sync from cloud)", 4, function() {
|
||||
emaildaoTest.keychain.getUserKeyPair(emaildaoTest.user, function(err, keypair) {
|
||||
ok(!err && keypair, 'Fetch keypair from keychain');
|
||||
|
||||
@ -69,8 +69,13 @@ define(['js/dao/email-dao', 'js/dao/keychain-dao', 'js/dao/lawnchair-dao',
|
||||
encryptedList[i].sentDate = emaildaoTest.list.at(i).get('sentDate');
|
||||
}
|
||||
|
||||
storage.storeEcryptedList(encryptedList, 'email_inbox', function() {
|
||||
ok(true, 'Store encrypted list');
|
||||
// set encrypted test list as return value for cloud storage stub
|
||||
emaildaoTest.cloudstorageStub.listEncryptedItems = function(type, emailAddress, folderName, callback) {
|
||||
callback(null, encryptedList);
|
||||
};
|
||||
|
||||
emaildaoTest.emailDao.syncFromCloud('inbox', function() {
|
||||
ok(true, 'Stored encrypted list');
|
||||
|
||||
start();
|
||||
});
|
||||
|
Loading…
Reference in New Issue
Block a user