From 699871276c716426a97f4bc13d396bddb531f9b0 Mon Sep 17 00:00:00 2001 From: Tankred Hase Date: Mon, 1 Jul 2013 22:42:39 +0200 Subject: [PATCH] sending end-2-end encrypted emails internally work --- src/js/dao/cloudstorage-dao.js | 8 +++ src/js/dao/email-dao.js | 64 ++++++++++++++++++++--- src/js/dao/keychain-dao.js | 45 ++++++++++++++++ src/js/view/compose-view.js | 14 ++++- test/integration/cloudstorage-dao-test.js | 33 ++++++++++-- test/unit/keychain-dao-test.js | 9 ++++ 6 files changed, 160 insertions(+), 13 deletions(-) diff --git a/src/js/dao/cloudstorage-dao.js b/src/js/dao/cloudstorage-dao.js index bc03914..54dae8f 100644 --- a/src/js/dao/cloudstorage-dao.js +++ b/src/js/dao/cloudstorage-dao.js @@ -84,6 +84,14 @@ define(['jquery', 'js/app-config'], function($, app) { self.put(item, uri, callback); }; + /** + * Deliver an email to the user's outbox + */ + self.deliverEmail = function(email, from, to, callback) { + var uri = app.config.cloudUrl + '/email/user/' + from + '/folder/outbox/' + email.id + '?to=' + to; + self.put(email, uri, callback); + }; + /** * Delete an encrypted item from the cloud * @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 ee60ccc..14f00e6 100644 --- a/src/js/dao/email-dao.js +++ b/src/js/dao/email-dao.js @@ -143,7 +143,7 @@ define(['underscore', 'cryptoLib/util', 'js/crypto/crypto', 'js/dao/lawnchair-da var filter = ''; if (localItems && localItems.length > 0) { // sync delta of last item sent date - filter = '?date=' + localItems[localItems.length - 1].sentDate; + //filter = '?date=' + localItems[localItems.length - 1].sentDate; startSync(filter); } else { // do a full sync of all items on the cloud @@ -225,9 +225,9 @@ define(['underscore', 'cryptoLib/util', 'js/crypto/crypto', 'js/dao/lawnchair-da // validate email addresses var invalidRecipient; - _.each(email.to, function(address) { - if (!validateEmail(address)) { - invalidRecipient = address; + _.each(email.to, function(i) { + if (!validateEmail(i.address)) { + invalidRecipient = i.address; } }); if (invalidRecipient) { @@ -236,7 +236,7 @@ define(['underscore', 'cryptoLib/util', 'js/crypto/crypto', 'js/dao/lawnchair-da }); return; } - if (!validateEmail(email.from)) { + if (!validateEmail(email.from[0].address)) { callback({ errMsg: 'Invalid sender: ' + email.from }); @@ -245,11 +245,59 @@ define(['underscore', 'cryptoLib/util', 'js/crypto/crypto', 'js/dao/lawnchair-da // generate a new UUID for the new email email.id = util.UUID(); + // set sent date + email.sentDate = util.formatDate(new Date()); - // send email to cloud service - cloudstorage.putEncryptedItem(email, 'email', userId, 'outbox', function(err) { - callback(err); + // only support single recipient for e-2-e encryption + var recipient = email.to[0].address; + + // check if receiver has a public key + keychain.getReveiverPublicKey(recipient, function(err, receiverPubkey) { + if (err) { + callback(err); + return; + } + + if (receiverPubkey) { + // public key found... encrypt and send + encrypt(email, receiverPubkey); + } else { + // no public key found... send plaintext mail via SMTP + send(email); + } }); + + function encrypt(email, receiverPubkey) { + // encrypt the email + crypto.encryptListForUser([email], [receiverPubkey], function(err, encryptedList) { + if (err) { + callback(err); + return; + } + + var ct = encryptedList[0]; + + var envelope = { + id: email.id, + crypto: 'rsa-1024-sha-256-aes-128-cbc', + sentDate: email.sentDate, + ciphertext: ct.ciphertext, + encryptedKey: ct.encryptedKey, + iv: ct.iv, + signature: ct.signature, + senderPk: ct.senderPk + }; + + send(envelope); + }); + } + + function send(email) { + // send email to cloud service + cloudstorage.deliverEmail(email, userId, recipient, function(err) { + callback(err); + }); + } }; }; diff --git a/src/js/dao/keychain-dao.js b/src/js/dao/keychain-dao.js index 10b05f8..8549003 100644 --- a/src/js/dao/keychain-dao.js +++ b/src/js/dao/keychain-dao.js @@ -46,6 +46,51 @@ define(['underscore', 'js/dao/lawnchair-dao'], function(_, jsonDao) { }); }; + /** + * Look up a reveiver's public key by user id + * @param userId [String] the receiver's email address + */ + self.getReveiverPublicKey = function(userId, callback) { + // search local keyring for public key + jsonDao.list('publickey', 0, null, function(allPubkeys) { + var pubkey = _.findWhere(allPubkeys, { + userId: userId + }); + + if (!pubkey || !pubkey._id) { + // no public key by that user id in storage + // find from cloud by email address + cloudstorage.getPublicKeyByUserId(userId, function(err, cloudPubkey) { + if (err || !cloudPubkey) { + callback(); + return; + } + + if (cloudPubkey && cloudPubkey._id) { + // there is a public key for that user already in the cloud... + // save to local storage + saveLocalPublicKey(cloudPubkey, function(err) { + if (err) { + callback(err); + return; + } + + callback(null, cloudPubkey); + }); + } else { + // no public key for that user + callback(); + return; + } + }); + + } else { + // that user's public key is already in local storage + callback(null, pubkey); + } + }); + }; + /** * Gets the local user's key either from local storage * or fetches it from the cloud. The private key is encrypted. diff --git a/src/js/view/compose-view.js b/src/js/view/compose-view.js index b78fb99..138e7ab 100644 --- a/src/js/view/compose-view.js +++ b/src/js/view/compose-view.js @@ -105,11 +105,21 @@ define(['jquery', 'underscore', 'backbone', 'js/app-config'], function($, _, Bac var signature = '\n\nSent with whiteout mail - get your free mailbox for end-2-end encrypted messaging!\nhttps://mail.whiteout.io'; var email = { - from: self.account, - to: to, + to: [], subject: page.find('#subjectInput').val(), body: page.find('#bodyTextarea').val() + signature }; + email.from = [{ + name: '', + address: self.account + } + ]; + to.forEach(function(address) { + email.to.push({ + name: '', + address: address + }); + }); // post message to main window app.util.postMessage('sendEmail', { diff --git a/test/integration/cloudstorage-dao-test.js b/test/integration/cloudstorage-dao-test.js index e26f4cf..67730a2 100644 --- a/test/integration/cloudstorage-dao-test.js +++ b/test/integration/cloudstorage-dao-test.js @@ -161,13 +161,40 @@ define(['js/dao/email-dao', 'js/dao/keychain-dao', 'js/dao/lawnchair-dao', }); }); - asyncTest("Send Plaintext Email item", 1, function() { + asyncTest("Send e-2-e Encrypted Email item", 1, function() { var email = { - from: cloudstoragedaoTest.user, // sender address - to: [cloudstoragedaoTest.user], // list of receivers subject: 'Client Email DAO Test', // Subject line body: 'Hello world' // plaintext body }; + email.from = [{ + address: cloudstoragedaoTest.user + } + ]; + email.to = [{ + address: cloudstoragedaoTest.user + } + ]; + + cloudstoragedaoTest.emailDao.sendEmail(email, function(err) { + ok(!err, 'Email sent'); + + start(); + }); + }); + + asyncTest("Send Plaintext Email item", 1, function() { + var email = { + subject: 'Client Email DAO Test', // Subject line + body: 'Hello world' // plaintext body + }; + email.from = [{ + address: cloudstoragedaoTest.user + } + ]; + email.to = [{ + address: 'safewithme.testuser@gmail.com' + } + ]; cloudstoragedaoTest.emailDao.sendEmail(email, function(err) { ok(!err, 'Email sent'); diff --git a/test/unit/keychain-dao-test.js b/test/unit/keychain-dao-test.js index 1b51ccc..8d4a4cc 100644 --- a/test/unit/keychain-dao-test.js +++ b/test/unit/keychain-dao-test.js @@ -81,4 +81,13 @@ define(['js/dao/keychain-dao', 'js/dao/lawnchair-dao'], function(KeychainDAO, js }); }); + asyncTest("Get User Keypair", 2, function() { + keychaindaoTest.keychainDao.getReveiverPublicKey(keychaindaoTest.user, function(err, pubkey) { + ok(!err); + ok(pubkey && pubkey.publicKey); + + start(); + }); + }); + }); \ No newline at end of file