From 9dec074b0ce54b484010b7cba3bc76337a4fc22d Mon Sep 17 00:00:00 2001 From: Tankred Hase Date: Fri, 19 Apr 2013 19:13:27 +0200 Subject: [PATCH] pull from vinbox, reencrypt, push to inbox works --- src/js/dao/cloudstorage-dao.js | 35 +++++++-- src/js/dao/email-dao.js | 94 ++++++++++++++++++++++- test/integration/cloudstorage-dao-test.js | 13 +++- 3 files changed, 130 insertions(+), 12 deletions(-) diff --git a/src/js/dao/cloudstorage-dao.js b/src/js/dao/cloudstorage-dao.js index 19d5f19..1735292 100644 --- a/src/js/dao/cloudstorage-dao.js +++ b/src/js/dao/cloudstorage-dao.js @@ -12,23 +12,23 @@ app.dao.CloudStorage = function(window, $) { /** * Find the user's corresponding public key */ - this.getPublicKey = function(emailAddress, callback) { + this.getPublicKey = function(keyId, callback) { var uri; - uri = app.config.cloudUrl + '/publickey/user/' + emailAddress; + uri = app.config.cloudUrl + '/publickey/key/' + keyId; $.ajax({ url: uri, type: 'GET', dataType: 'json', - success: function(list) { - if (!list || list.length === 0) { + success: function(key) { + if (!key || !key._id) { callback({ error: 'No public key for that user!' }); return; } - callback(null, list[0]); + callback(null, key); }, error: function(xhr, textStatus, err) { callback({ @@ -67,6 +67,31 @@ app.dao.CloudStorage = function(window, $) { // Encrypted Mails // + /** + * Pushes an encrypted item to the user's cloud storage + * @param type [String] The type of item e.g. 'email' + */ + this.putEncryptedItem = function(item, type, emailAddress, folderName, callback) { + var uri; + + uri = app.config.cloudUrl + '/' + type + '/user/' + emailAddress + '/folder/' + folderName + '/' + item.id; + $.ajax({ + url: uri, + type: 'PUT', + data: JSON.stringify(item), + contentType: 'application/json', + success: function() { + callback(); + }, + error: function(xhr, textStatus, err) { + callback({ + error: err, + status: textStatus + }); + } + }); + }; + /** * Lists the encrypted items * @param type [String] The type of item e.g. 'email' diff --git a/src/js/dao/email-dao.js b/src/js/dao/email-dao.js index bf0d5ba..cc9e839 100644 --- a/src/js/dao/email-dao.js +++ b/src/js/dao/email-dao.js @@ -2,7 +2,7 @@ * A high-level Data-Access Api for handling Email synchronization * between the cloud service and the device's local storage */ -app.dao.EmailDAO = function(_, crypto, devicestorage, cloudstorage, naclCrypto) { +app.dao.EmailDAO = function(_, crypto, devicestorage, cloudstorage, naclCrypto, util) { 'use strict'; var keypair; // the user's keypair @@ -101,6 +101,92 @@ app.dao.EmailDAO = function(_, crypto, devicestorage, cloudstorage, naclCrypto) } }; + /** + * Checks the user virtual inbox containing end-2-end encrypted mail items + */ + this.checkVInbox = function(callback) { + var self = this; + + cloudstorage.listEncryptedItems('email', this.account.get('emailAddress'), 'vinbox', function(err, data) { + // if virtual inbox is emtpy just callback + if (err || !data || data.status || data.length === 0) { + callback(); // error + return; + } + + // asynchronously iterate over the encrypted items + var after = _.after(data.length, function() { + callback(); + }); + + _.each(data, function(asymCt) { + // asymmetric decrypt + asymDecryptMail(asymCt, function(err, pt) { + if (err) { + callback(err); + return; + } + + // symmetric encrypt and push to cloud + symEncryptAndUpload(pt, function(err) { + if (err) { + callback(err); + return; + } + + // TODO: delete items from virtual inbox + + after(); // asynchronously iterate through objects + }); + }); + }); + }); + + function asymDecryptMail(m, callback) { + var pubKeyId = m.senderPk.split(';')[1]; + // pull the sender's public key + cloudstorage.getPublicKey(pubKeyId, function(err, senderPk) { + if (err) { + callback(err); + return; + } + + // do authenticated decryption + naclCrypto.asymDecrypt(m.ciphertext, m.itemIV, senderPk.publicKey, keypair.boxSk, function(plaintext) { + callback(null, JSON.parse(plaintext)); + }); + }); + } + + function symEncryptAndUpload(email, callback) { + var itemKey = util.random(self.account.get('symKeySize')), + itemIV = util.random(self.account.get('symIvSize')), + keyIV = util.random(self.account.get('symIvSize')), + json = JSON.stringify(email), + envelope, encryptedKey; + + // symmetrically encrypt item + crypto.aesEncrypt(json, itemKey, itemIV, function(ct) { + + // encrypt item key for user + encryptedKey = crypto.aesEncryptForUserSync(itemKey, keyIV); + envelope = { + id: email.id, + crypto: 'aes-128-ccm', + ciphertext: ct, + encryptedKey: encryptedKey, + keyIV: keyIV, + itemIV: itemIV + }; + + // push encrypted item to cloud + cloudstorage.putEncryptedItem(envelope, 'email', self.account.get('emailAddress'), 'inbox', function(err) { + callback(err); + }); + }); + } + }; + /** * Synchronize a folder's items from the cloud to the device-storage * @param folderName [String] The name of the folder e.g. 'inbox' @@ -108,9 +194,9 @@ app.dao.EmailDAO = function(_, crypto, devicestorage, cloudstorage, naclCrypto) this.syncFromCloud = function(folderName, callback) { var folder, self = this; - cloudstorage.listEncryptedItems('email', this.account.get('emailAddress'), folderName, function(err, res) { + cloudstorage.listEncryptedItems('email', this.account.get('emailAddress'), folderName, function(err, data) { // return if an error occured or if fetched list from cloud storage is empty - if (err || !res || res.status || res.length === 0) { + if (err || !data || data.status || data.length === 0) { callback({ error: err }); // error @@ -120,7 +206,7 @@ app.dao.EmailDAO = function(_, crypto, devicestorage, cloudstorage, naclCrypto) // TODO: remove old folder items from devicestorage // persist encrypted list in device storage - devicestorage.storeEcryptedList(res, 'email_' + folderName, function() { + devicestorage.storeEcryptedList(data, 'email_' + folderName, function() { // remove cached folder in account model folder = self.account.get('folders').where({ name: folderName diff --git a/test/integration/cloudstorage-dao-test.js b/test/integration/cloudstorage-dao-test.js index 30444a4..0d07db6 100644 --- a/test/integration/cloudstorage-dao-test.js +++ b/test/integration/cloudstorage-dao-test.js @@ -15,7 +15,7 @@ asyncTest("Init", 1, function() { var naclCrypto = new app.crypto.NaclCrypto(nacl, util); cloudstoragedao_test.storage = new app.dao.DeviceStorage(util, crypto, jsonDao, null); cloudstoragedao_test.cloudstorage = new app.dao.CloudStorage(window, $); - cloudstoragedao_test.emailDao = new app.dao.EmailDAO(_, crypto, cloudstoragedao_test.storage, cloudstoragedao_test.cloudstorage, naclCrypto); + cloudstoragedao_test.emailDao = new app.dao.EmailDAO(_, crypto, cloudstoragedao_test.storage, cloudstoragedao_test.cloudstorage, naclCrypto, util); // clear db before tests jsonDao.clear(function(err) { @@ -43,7 +43,7 @@ asyncTest("Persist public key to cloud", 1, function() { }); asyncTest("Get Public key from cloud", 2, function() { - cloudstoragedao_test.cloudstorage.getPublicKey(cloudstoragedao_test.publicKey.get('userId'), function(err, data) { + cloudstoragedao_test.cloudstorage.getPublicKey(cloudstoragedao_test.publicKey.get('_id'), function(err, data) { ok(!err && data && data.publicKey, 'Get public key from cloud'); deepEqual(data, cloudstoragedao_test.publicKey.toJSON(), 'Public key is equal'); @@ -91,8 +91,15 @@ asyncTest("Init", 1, function() { }); }); -asyncTest("Sync emails from cloud", 3, function() { +asyncTest("Check virtual inbox, re-encrypt and push to cloud", 1, function() { + cloudstoragedao_test.emailDao.checkVInbox(function(err) { + ok(!err, 'Synced items'); + start(); + }); +}); + +asyncTest("Sync emails from cloud", 2, function() { cloudstoragedao_test.emailDao.syncFromCloud('inbox', function(err) { ok(!err, 'Synced items');