refactor test and storage code

This commit is contained in:
Tankred Hase 2013-09-26 13:26:57 +02:00
parent a3f11befdc
commit 38b0a8e8b1
20 changed files with 475 additions and 266 deletions

View File

@ -135,7 +135,8 @@ module.exports = function(grunt) {
'imap-client/node_modules/mimelib/node_modules/encoding/node_modules/iconv-lite/src/*.js',
'imap-client/node_modules/mimelib/node_modules/encoding/node_modules/mime/src/*.js',
'imap-client/node_modules/mailparser/src/*.js',
'imap-client/node_modules/mailparser/node_modules/mime/src/mime.js'
'imap-client/node_modules/mailparser/node_modules/mime/src/mime.js',
'smtp-client/src-gen/*.js'
],
dest: 'src/lib/'
},

View File

@ -8,7 +8,7 @@ cd ..
# build imap/smtp modules and copy
cd ./node_modules/smtp-client/
node build.js && cp ./src-gen/*.js ../../src/lib/
node build.js
cd ../../
echo "\n--> finished building dependencies.\n"

BIN
src/img/icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

View File

@ -10,6 +10,8 @@ define(function(require) {
EmailDAO = require('js/dao/email-dao'),
KeychainDAO = require('js/dao/keychain-dao'),
cloudstorage = require('js/dao/cloudstorage-dao'),
DeviceStorageDAO = require('js/dao/devicestorage-dao'),
Crypto = require('js/crypto/crypto'),
config = require('js/app-config').config;
require('cordova');
@ -66,7 +68,7 @@ define(function(require) {
self.login = function(userId, password, token, callback) {
var auth, imapOptions, smtpOptions,
keychain, imapClient, smtpClient;
keychain, imapClient, smtpClient, crypto, deviceStorage;
// create mail credentials objects for imap/smtp
auth = {
@ -93,7 +95,9 @@ define(function(require) {
keychain = new KeychainDAO(cloudstorage);
imapClient = new ImapClient(imapOptions);
smtpClient = new SmtpClient(smtpOptions);
self._emailDao = new EmailDAO(keychain, imapClient, smtpClient);
crypto = new Crypto();
deviceStorage = new DeviceStorageDAO();
self._emailDao = new EmailDAO(keychain, imapClient, smtpClient, crypto, deviceStorage);
// init email dao
var account = {

View File

@ -3,10 +3,13 @@ define(function(require) {
var _ = require('underscore'),
appController = require('js/app-controller'),
moment = require('moment'),
emailDao;
var MailListCtrl = function($scope) {
var offset = -6,
num = 0;
// show inbox at the beginning
$scope.folder = 'INBOX';
emailDao = appController._emailDao;
@ -17,72 +20,83 @@ define(function(require) {
$scope.$parent.selected = $scope.selected;
};
// production... in chrome packaged app
if (window.chrome && chrome.identity) {
fetchList($scope.folder, function(emails) {
$scope.emails = emails;
$scope.select($scope.emails[0]);
$scope.$apply();
});
initList();
return;
}
// development
createDummyMails(function(emails) {
$scope.emails = emails;
$scope.select($scope.emails[0]);
});
};
function fetchList(folder, callback) {
// fetch imap folder's message list
emailDao.imapListMessages({
folder: folder,
offset: -6,
num: 0
}, function(err, emails) {
if (err) {
console.log(err);
return;
}
// fetch message bodies
fetchBodies(emails, folder, function(messages) {
addDisplayDate(messages);
callback(messages);
function initList() {
// list messaged from local db
listLocalMessages({
folder: $scope.folder,
offset: offset,
num: num
}, function() {
// sync from imap to local db
syncImapFolder({
folder: $scope.folder,
offset: offset,
num: num
}, function() {
// list again from local db after syncing
listLocalMessages({
folder: $scope.folder,
offset: offset,
num: num
}, function() {
console.log('syncing ' + $scope.folder + ' complete');
});
});
});
});
}
}
function fetchBodies(messageList, folder, callback) {
var emails = [];
var after = _.after(messageList.length, function() {
callback(emails);
});
_.each(messageList, function(messageItem) {
emailDao.imapGetMessage({
folder: folder,
uid: messageItem.uid
}, function(err, message) {
function syncImapFolder(options, callback) {
// sync if emails are empty
emailDao.imapSync(options, function(err) {
if (err) {
console.log(err);
return;
}
emails.push(message);
after();
callback();
});
});
}
}
function addDisplayDate(emails) {
emails.forEach(function(email) {
// set display date
email.displayDate = moment(email.sentDate).format('DD.MM.YY');
});
function listLocalMessages(options, callback) {
emailDao.listMessages(options, function(err, emails) {
if (err) {
console.log(err);
return;
}
// add display dates
displayEmails(emails);
return emails;
}
callback(emails);
});
}
function displayEmails(emails) {
if (!emails || emails.length < 1) {
return;
}
// sort by uid
emails = _.sortBy(emails, function(e) {
return -e.uid;
});
$scope.emails = emails;
$scope.select($scope.emails[0]);
$scope.$apply();
}
};
function createDummyMails(callback) {
var Email = function(unread, attachments, replied) {
@ -97,8 +111,7 @@ define(function(require) {
this.attachments = (attachments) ? [true] : undefined;
this.unread = unread;
this.replied = replied;
this.displayDate = '23.08.13';
this.longDisplayDate = 'Wednesday, 23.08.2013 19:23';
this.sentDate = new Date('Thu Sep 19 2013 20:41:23 GMT+0200 (CEST)');
this.subject = "Welcome Max"; // Subject line
this.body = "Hi Max,\n\n" +
"Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.\n\n" +

View File

@ -12,16 +12,21 @@ define(function(require) {
pbkdf2 = require('js/crypto/pbkdf2'),
config = require('js/app-config').config;
var self = {},
passBasedKey,
var passBasedKey,
BATCH_WORKER = '/crypto/crypto-batch-worker.js',
PBKDF2_WORKER = '/crypto/pbkdf2-worker.js';
var Crypto = function() {
};
/**
* Initializes the crypto modules by fetching the user's
* encrypted secret key from storage and storing it in memory.
*/
self.init = function(args, callback) {
Crypto.prototype.init = function(args, callback) {
var self = this;
// valdiate input
if (!args.emailAddress || !args.keySize || !args.rsaKeySize) {
callback({
@ -119,7 +124,7 @@ define(function(require) {
/**
* Do PBKDF2 key derivation in a WebWorker thread
*/
self.deriveKey = function(password, keySize, callback) {
Crypto.prototype.deriveKey = function(password, keySize, callback) {
startWorker({
script: PBKDF2_WORKER,
args: {
@ -137,8 +142,9 @@ define(function(require) {
// En/Decrypt a list of items with AES in a WebWorker thread
//
self.symEncryptList = function(list, callback) {
var key, envelope, envelopes = [];
Crypto.prototype.symEncryptList = function(list, callback) {
var self = this,
key, envelope, envelopes = [];
// generate single secret key shared for all list items
key = util.random(self.keySize);
@ -173,7 +179,7 @@ define(function(require) {
});
};
self.symDecryptList = function(list, keys, callback) {
Crypto.prototype.symDecryptList = function(list, keys, callback) {
startWorker({
script: BATCH_WORKER,
args: {
@ -192,8 +198,9 @@ define(function(require) {
// En/Decrypt something speficially using the user's secret key
//
self.encryptListForUser = function(list, receiverPubkeys, callback) {
var envelope, envelopes = [];
Crypto.prototype.encryptListForUser = function(list, receiverPubkeys, callback) {
var self = this,
envelope, envelopes = [];
if (!receiverPubkeys || receiverPubkeys.length !== 1) {
callback({
@ -235,7 +242,7 @@ define(function(require) {
});
};
self.decryptListForUser = function(list, senderPubkeys, callback) {
Crypto.prototype.decryptListForUser = function(list, senderPubkeys, callback) {
if (!senderPubkeys || senderPubkeys < 1) {
callback({
errMsg: 'Sender public keys must be set!'
@ -268,7 +275,7 @@ define(function(require) {
// Re-encrypt keys item and items seperately
//
self.reencryptListKeysForUser = function(list, senderPubkeys, callback) {
Crypto.prototype.reencryptListKeysForUser = function(list, senderPubkeys, callback) {
var keypair = rsa.exportKeys();
var receiverPrivkey = {
_id: keypair._id,
@ -291,7 +298,7 @@ define(function(require) {
});
};
self.decryptKeysAndList = function(list, callback) {
Crypto.prototype.decryptKeysAndList = function(list, callback) {
startWorker({
script: BATCH_WORKER,
args: {
@ -351,5 +358,5 @@ define(function(require) {
options.callback(null, result);
}
return self;
return Crypto;
});

View File

@ -4,17 +4,26 @@
* through transparent encryption. If not, the crypto API is
* used to encrypt data on the fly before persisting via a JSON store.
*/
define(['cryptoLib/util', 'js/crypto/crypto', 'js/dao/lawnchair-dao'], function(util, crypto, jsonDao) {
define(function(require) {
'use strict';
var self = {};
var util = require('cryptoLib/util'),
jsonDao = require('js/dao/lawnchair-dao');
var DeviceStorageDAO = function() {
};
DeviceStorageDAO.prototype.init = function(emailAddress, callback) {
jsonDao.init(emailAddress, callback);
};
/**
* Stores a list of encrypted items in the object store
* @param list [Array] The list of items to be persisted
* @param type [String] The type of item to be persisted e.g. 'email'
*/
self.storeEcryptedList = function(list, type, callback) {
DeviceStorageDAO.prototype.storeEcryptedList = function(list, type, callback) {
var date, key, items = [];
// nothing to store
@ -25,9 +34,10 @@ define(['cryptoLib/util', 'js/crypto/crypto', 'js/dao/lawnchair-dao'], function(
// format items for batch storing in dao
list.forEach(function(i) {
// put date in key if available... for easy querying
if (i.sentDate) {
// put uid in key if available... for easy querying
if (i.uid) {
key = type + '_' + i.uid;
} else if (i.sentDate) {
date = util.parseDate(i.sentDate);
key = type + '_' + i.sentDate + '_' + i.id;
} else {
@ -38,7 +48,6 @@ define(['cryptoLib/util', 'js/crypto/crypto', 'js/dao/lawnchair-dao'], function(
key: key,
object: i
});
});
jsonDao.batch(items, function() {
@ -52,7 +61,7 @@ define(['cryptoLib/util', 'js/crypto/crypto', 'js/dao/lawnchair-dao'], function(
* @param offset [Number] The offset of items to fetch (0 is the last stored item)
* @param num [Number] The number of items to fetch (null means fetch all)
*/
self.listEncryptedItems = function(type, offset, num, callback) {
DeviceStorageDAO.prototype.listEncryptedItems = function(type, offset, num, callback) {
// fetch all items of a certain type from the data-store
jsonDao.list(type, offset, num, function(encryptedList) {
@ -63,9 +72,9 @@ define(['cryptoLib/util', 'js/crypto/crypto', 'js/dao/lawnchair-dao'], function(
/**
* Clear the whole device data-store
*/
self.clear = function(callback) {
DeviceStorageDAO.prototype.clear = function(callback) {
jsonDao.clear(callback);
};
return self;
return DeviceStorageDAO;
});

View File

@ -3,20 +3,20 @@ define(function(require) {
var _ = require('underscore'),
util = require('cryptoLib/util'),
crypto = require('js/crypto/crypto'),
jsonDB = require('js/dao/lawnchair-dao'),
str = require('js/app-config').string;
/**
* A high-level Data-Access Api for handling Email synchronization
* between the cloud service and the device's local storage
*/
var EmailDAO = function(keychain, imapClient, smtpClient) {
var EmailDAO = function(keychain, imapClient, smtpClient, crypto, devicestorage) {
var self = this;
self._keychain = keychain;
self._imapClient = imapClient;
self._smtpClient = smtpClient;
self._crypto = crypto;
self._devicestorage = devicestorage;
};
/**
@ -51,21 +51,21 @@ define(function(require) {
function initKeychain() {
// init user's local database
jsonDB.init(emailAddress);
// call getUserKeyPair to read/sync keypair with devicestorage/cloud
self._keychain.getUserKeyPair(emailAddress, function(err, storedKeypair) {
if (err) {
callback(err);
return;
}
// init crypto
initCrypto(storedKeypair);
self._devicestorage.init(emailAddress, function() {
// call getUserKeyPair to read/sync keypair with devicestorage/cloud
self._keychain.getUserKeyPair(emailAddress, function(err, storedKeypair) {
if (err) {
callback(err);
return;
}
// init crypto
initCrypto(storedKeypair);
});
});
}
function initCrypto(storedKeypair) {
crypto.init({
self._crypto.init({
emailAddress: emailAddress,
password: password,
keySize: self._account.symKeySize,
@ -144,8 +144,11 @@ define(function(require) {
// validate public key
if (!receiverPubkey) {
callback({
errMsg: 'User has no public key yet!'
});
// user hasn't registered a public key yet... invite
self.encryptForNewUser(email, callback);
//self.encryptForNewUser(email, callback);
return;
}
@ -163,7 +166,7 @@ define(function(require) {
receiverPubkeys = [receiverPubkey];
// encrypt the email
crypto.encryptListForUser(ptItems, receiverPubkeys, function(err, encryptedList) {
self._crypto.encryptListForUser(ptItems, receiverPubkeys, function(err, encryptedList) {
if (err) {
callback(err);
return;
@ -183,7 +186,7 @@ define(function(require) {
var self = this,
ptItems = bundleForEncryption(email);
crypto.symEncryptList(ptItems, function(err, result) {
self._crypto.symEncryptList(ptItems, function(err, result) {
if (err) {
callback(err);
return;
@ -295,6 +298,168 @@ define(function(require) {
self._imapClient.unreadMessages(path, callback);
};
/**
* Fetch a list of emails from the device's local storage
*/
EmailDAO.prototype.listMessages = function(options, callback) {
var self = this,
encryptedList = [];
// validate options
if (!options.folder || typeof options.offset === 'undefined' || typeof options.num === 'undefined') {
callback({
errMsg: 'Invalid options!'
});
return;
}
// fetch items from device storage
self._devicestorage.listEncryptedItems('email_' + options.folder, options.offset, options.num, function(err, emails) {
if (err) {
callback(err);
return;
}
if (emails.length === 0) {
callback(null, []);
return;
}
// find encrypted items
emails.forEach(function(i) {
if (i.body.indexOf(str.cryptPrefix) !== -1 && i.body.indexOf(str.cryptSuffix) !== -1) {
// parse ct object from ascii armored message block
encryptedList.push(parseMessageBlock(i));
}
});
// decrypt items
decryptList(encryptedList, function(err, decryptedList) {
// return only decrypted items
callback(null, decryptedList);
});
});
function parseMessageBlock(email) {
var ctMessageBase64, ctMessageJson, ctMessage;
// parse email body for encrypted message block
try {
// get base64 encoded message block
ctMessageBase64 = email.body.split(str.cryptPrefix)[1].split(str.cryptSuffix)[0].trim();
// decode bae64
ctMessageJson = atob(ctMessageBase64);
// parse json string to get ciphertext object
ctMessage = JSON.parse(ctMessageJson);
} catch (e) {
callback({
errMsg: 'Error parsing encrypted message block!'
});
return;
}
return ctMessage;
}
function decryptList(encryptedList, callback) {
var already, pubkeyIds = [];
// 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
self._keychain.getPublicKeys(pubkeyIds, function(err, senderPubkeys) {
if (err) {
callback(err);
return;
}
// verfiy signatures and re-encrypt item keys
self._crypto.decryptListForUser(encryptedList, senderPubkeys, function(err, decryptedList) {
if (err) {
callback(err);
return;
}
callback(null, decryptedList);
});
});
}
};
/**
* High level sync operation for the delta from the user's IMAP inbox
*/
EmailDAO.prototype.imapSync = function(options, callback) {
var self = this;
// validate options
if (!options.folder || typeof options.offset === 'undefined' || typeof options.num === 'undefined') {
callback({
errMsg: 'Invalid options!'
});
return;
}
fetchList(options, function(emails) {
// persist encrypted list in device storage
self._devicestorage.storeEcryptedList(emails, 'email_' + options.folder, function() {
callback();
});
});
function fetchList(folder, callback) {
// fetch imap folder's message list
self.imapListMessages({
folder: options.folder,
offset: options.offset,
num: options.num
}, function(err, emails) {
if (err) {
console.log(err);
return;
}
// fetch message bodies
fetchBodies(emails, folder, function(messages) {
callback(messages);
});
});
}
function fetchBodies(messageList, folder, callback) {
var emails = [];
var after = _.after(messageList.length, function() {
callback(emails);
});
_.each(messageList, function(messageItem) {
self.imapGetMessage({
folder: folder,
uid: messageItem.uid
}, function(err, message) {
if (err) {
console.log(err);
return;
}
emails.push(message);
after();
});
});
}
};
/**
* List messages from an imap folder. This will not yet fetch the email body.
* @param {String} options.folderName The name of the imap folder.
@ -337,16 +502,6 @@ define(function(require) {
return;
}
// try fetching from cache before doing a roundtrip
message = self.readCache(options.folder, options.uid);
if (message) {
// message was fetched from cache successfully
callback(null, message);
return;
}
/* message was not found in cache... fetch from imap server */
function messageReady(err, gottenMessage) {
message = gottenMessage;
itemCounter++;
@ -358,68 +513,12 @@ define(function(require) {
return;
}
// decrypt Message body
if (message.body.indexOf(str.cryptPrefix) !== -1 && message.body.indexOf(str.cryptSuffix) !== -1) {
decryptBody(message, function(err, ptMessage) {
message = ptMessage;
// return decrypted message
callback(err, message);
});
return;
}
// return unencrypted message
// return message
callback(null, message);
//check();
}
function decryptBody(email, callback) {
var ctMessageBase64, ctMessageJson, ctMessage, pubkeyIds;
// parse email body for encrypted message block
try {
// get base64 encoded message block
ctMessageBase64 = email.body.split(str.cryptPrefix)[1].split(str.cryptSuffix)[0].trim();
// decode bae64
ctMessageJson = atob(ctMessageBase64);
// parse json string to get ciphertext object
ctMessage = JSON.parse(ctMessageJson);
} catch (e) {
callback({
errMsg: 'Error parsing encrypted message block!'
});
return;
}
// gather public key ids required to verify signatures
pubkeyIds = [{
_id: ctMessage.senderPk
}];
// fetch public keys from keychain
self._keychain.getPublicKeys(pubkeyIds, function(err, senderPubkeys) {
if (err) {
callback(err);
return;
}
// verfiy signatures and re-encrypt item keys
crypto.decryptListForUser([ctMessage], senderPubkeys, function(err, decryptedList) {
if (err) {
callback(err);
return;
}
var ptEmail = decryptedList[0];
email.body = ptEmail.body;
email.subject = ptEmail.subject;
callback(null, email);
});
});
}
// function attachmentReady(err, gottenAttachment) {
// attachments.push(gottenAttachment);
// itemCounter++;

View File

@ -7,17 +7,22 @@ define(['lawnchair', 'lawnchairSQL', 'lawnchairIDB'], function(Lawnchair) {
var self = {},
db;
self.init = function(dbName) {
self.init = function(dbName, callback) {
var temp;
if (!dbName) {
throw new Error('Lawnchair DB name must be specified!');
}
db = new Lawnchair({
temp = new Lawnchair({
name: dbName
}, function(lc) {
if (!lc) {
throw new Error('Lawnchair init failed!');
}
db = lc;
callback();
});
};

File diff suppressed because one or more lines are too long

View File

@ -5,7 +5,7 @@
"manifest_version": 2,
"offline_enabled": true,
"icons": {
"128": "img/mail-128.png"
"128": "img/icon.png"
},
"permissions": [
"https://storage.whiteout.io/",

View File

@ -19,7 +19,6 @@
angular: 'angular/angular.min',
angularRoute: 'angular/angular-route.min',
angularTouch: 'angular/angular-touch.min',
moment: 'moment/moment.min',
uuid: 'uuid/uuid'
},
shim: {

View File

@ -8,7 +8,7 @@
<h3>{{email.from[0].name || email.from[0].address}}</h3>
<div class="head">
<p class="subject">{{email.subject}}</p>
<time>{{email.displayDate}}</time>
<time>{{email.sentDate | date:'mediumDate'}}</time>
</div>
<p class="body">{{email.body}}</p>
</li>

View File

@ -7,7 +7,7 @@
<div class="view-read">
<div class="headers">
<p class="subject">{{selected.subject}}</p>
<p class="date">{{selected.longDisplayDate}}</p>
<p class="date">{{selected.sentDate | date:'EEEE, MMM d, yyyy h:mm a'}}</p>
<p class="address">From: <span class="label">{{selected.from[0].name || selected.from[0].address}}</span></p>
<p class="address">To: <span class="label" ng-repeat="t in selected.to">{{t.address}} </span></p>
<div ng-switch="selected.cc !== undefined">

View File

@ -3,8 +3,10 @@ define(function(require) {
var KeychainDAO = require('js/dao/keychain-dao'),
EmailDAO = require('js/dao/email-dao'),
DeviceStorageDAO = require('js/dao/devicestorage-dao'),
SmtpClient = require('smtp-client'),
ImapClient = require('imap-client'),
Crypto = require('js/crypto/crypto'),
app = require('js/app-config'),
expect = chai.expect;
@ -19,12 +21,12 @@ define(function(require) {
var publicKey = "-----BEGIN PUBLIC KEY-----\r\n" + "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCxy+Te5dyeWd7g0P+8LNO7fZDQ\r\n" + "g96xTb1J6pYE/pPTMlqhB6BRItIYjZ1US5q2vk5Zk/5KasBHAc9RbCqvh9v4XFEY\r\n" + "JVmTXC4p8ft1LYuNWIaDk+R3dyYXmRNct/JC4tks2+8fD3aOvpt0WNn3R75/FGBt\r\n" + "h4BgojAXDE+PRQtcVQIDAQAB\r\n" + "-----END PUBLIC KEY-----";
describe('Email DAO unit tests', function() {
this.timeout(20000);
var emailDao, account,
keychainStub, imapClientStub, smtpClientStub;
keychainStub, imapClientStub, smtpClientStub, cryptoStub, devicestorageStub;
beforeEach(function() {
// init dummy object
dummyMail = {
from: [{
name: 'Whiteout Test',
@ -47,8 +49,10 @@ define(function(require) {
keychainStub = sinon.createStubInstance(KeychainDAO);
imapClientStub = sinon.createStubInstance(ImapClient);
smtpClientStub = sinon.createStubInstance(SmtpClient);
cryptoStub = sinon.createStubInstance(Crypto);
devicestorageStub = sinon.createStubInstance(DeviceStorageDAO);
emailDao = new EmailDAO(keychainStub, imapClientStub, smtpClientStub);
emailDao = new EmailDAO(keychainStub, imapClientStub, smtpClientStub, cryptoStub, devicestorageStub);
});
afterEach(function() {});
@ -65,9 +69,11 @@ define(function(require) {
it('should fail due to error in getUserKeyPair', function(done) {
imapClientStub.login.yields();
devicestorageStub.init.yields();
keychainStub.getUserKeyPair.yields(42);
emailDao.init(account, emaildaoTest.passphrase, function(err) {
expect(devicestorageStub.init.calledOnce).to.be.true;
expect(imapClientStub.login.calledOnce).to.be.true;
expect(err).to.equal(42);
done();
@ -76,12 +82,16 @@ define(function(require) {
it('should init with new keygen', function(done) {
imapClientStub.login.yields();
devicestorageStub.init.yields();
keychainStub.getUserKeyPair.yields();
cryptoStub.init.yields(null, {});
keychainStub.putUserKeyPair.yields();
emailDao.init(account, emaildaoTest.passphrase, function(err) {
expect(imapClientStub.login.calledOnce).to.be.true;
expect(devicestorageStub.init.calledOnce).to.be.true;
expect(keychainStub.getUserKeyPair.calledOnce).to.be.true;
expect(cryptoStub.init.calledOnce).to.be.true;
expect(keychainStub.putUserKeyPair.calledOnce).to.be.true;
expect(err).to.not.exist;
done();
@ -92,12 +102,16 @@ define(function(require) {
describe('IMAP/SMTP tests', function() {
beforeEach(function(done) {
imapClientStub.login.yields();
devicestorageStub.init.yields();
keychainStub.getUserKeyPair.yields();
cryptoStub.init.yields(null, {});
keychainStub.putUserKeyPair.yields();
emailDao.init(account, emaildaoTest.passphrase, function(err) {
expect(imapClientStub.login.calledOnce).to.be.true;
expect(devicestorageStub.init.calledOnce).to.be.true;
expect(keychainStub.getUserKeyPair.calledOnce).to.be.true;
expect(cryptoStub.init.calledOnce).to.be.true;
expect(keychainStub.putUserKeyPair.calledOnce).to.be.true;
expect(err).to.not.exist;
done();
@ -141,11 +155,11 @@ define(function(require) {
emailDao.smtpSend(dummyMail, function(err) {
expect(keychainStub.getReveiverPublicKey.calledOnce).to.be.true;
expect(smtpClientStub.send.calledOnce).to.be.true;
smtpClientStub.send.calledWith(sinon.match(function(o) {
return typeof o.attachments === 'undefined';
}));
expect(err).to.not.exist;
// expect(smtpClientStub.send.called).to.be.true;
// smtpClientStub.send.calledWith(sinon.match(function(o) {
// return typeof o.attachments === 'undefined';
// }));
expect(err).to.exist;
done();
});
});
@ -157,6 +171,7 @@ define(function(require) {
publicKey: publicKey
});
smtpClientStub.send.yields();
cryptoStub.encryptListForUser.yields(null, []);
emailDao.smtpSend(dummyMail, function(err) {
expect(keychainStub.getReveiverPublicKey.calledOnce).to.be.true;
@ -181,6 +196,7 @@ define(function(require) {
publicKey: publicKey
});
smtpClientStub.send.yields();
cryptoStub.encryptListForUser.yields(null, [{}, {}]);
emailDao.smtpSend(dummyMail, function(err) {
expect(keychainStub.getReveiverPublicKey.calledOnce).to.be.true;
@ -344,6 +360,61 @@ define(function(require) {
// });
// });
});
describe('IMAP: sync messages to local storage', function() {
it('should work', function(done) {
imapClientStub.listMessages.yields(null, [{
uid: 413,
}, {
uid: 414,
}]);
imapClientStub.getMessage.yields(null, {
body: 'asdf'
});
devicestorageStub.storeEcryptedList.yields();
emailDao.imapSync({
folder: 'INBOX',
offset: 0,
num: 2
}, function(err) {
expect(err).to.not.exist;
expect(imapClientStub.listMessages.calledOnce).to.be.true;
expect(imapClientStub.getMessage.calledTwice).to.be.true;
expect(devicestorageStub.storeEcryptedList.calledOnce).to.be.true;
done();
});
});
});
describe('IMAP: list messages from local storage', function() {
it('should work', function(done) {
devicestorageStub.listEncryptedItems.yields(null, [{
body: ''
}]);
keychainStub.getPublicKeys.yields(null, [{
_id: "fcf8b4aa-5d09-4089-8b4f-e3bc5091daf3",
userId: "safewithme.testuser@gmail.com",
publicKey: publicKey
}]);
cryptoStub.decryptListForUser.yields(null, []);
emailDao.listMessages({
folder: 'INBOX',
offset: 0,
num: 2
}, function(err, emails) {
expect(devicestorageStub.listEncryptedItems.calledOnce).to.be.true;
expect(keychainStub.getPublicKeys.calledOnce).to.be.true;
expect(cryptoStub.decryptListForUser.calledOnce).to.be.true;
expect(err).to.not.exist;
expect(emails.length).to.equal(0);
done();
});
});
});
});
});

View File

@ -1,4 +1,4 @@
define(['js/crypto/crypto', 'cryptoLib/util', 'test/test-data'], function(crypto, util, testData) {
define(['js/crypto/crypto', 'cryptoLib/util', 'test/test-data'], function(Crypto, util, testData) {
'use strict';
module("Crypto Api");
@ -11,7 +11,10 @@ define(['js/crypto/crypto', 'cryptoLib/util', 'test/test-data'], function(crypto
rsaKeySize: 1024
};
var crypto;
asyncTest("Init without keypair", 4, function() {
crypto = new Crypto();
// init dependencies
ok(crypto, 'Crypto');

View File

@ -1,94 +1,97 @@
define(['underscore', 'cryptoLib/util', 'js/crypto/crypto', 'js/dao/lawnchair-dao',
'js/dao/devicestorage-dao', 'test/test-data'
], function(_, util, crypto, jsonDao, storage, testData) {
'use strict';
define(['underscore', 'cryptoLib/util', 'js/crypto/crypto', 'js/dao/devicestorage-dao', 'test/test-data'], function(_, util, Crypto, DeviceStorageDAO, testData) {
'use strict';
module("DeviceStorage");
module("DeviceStorage");
var devicestorageTest = {
user: 'devicestorage_test@example.com',
password: 'Password',
keySize: 128,
ivSize: 128,
rsaKeySize: 1024
};
var devicestorageTest = {
user: 'devicestorage_test@example.com',
password: 'Password',
keySize: 128,
ivSize: 128,
rsaKeySize: 1024
};
asyncTest("Init", 3, function() {
// init dependencies
jsonDao.init(devicestorageTest.user);
ok(storage, 'DeviceStorageDAO');
var crypto, storage;
// generate test data
devicestorageTest.list = testData.getEmailCollection(100);
asyncTest("Init", 3, function() {
// init dependencies
storage = new DeviceStorageDAO();
storage.init(devicestorageTest.user, function() {
ok(storage, 'DeviceStorageDAO');
// init crypto
crypto.init({
emailAddress: devicestorageTest.user,
password: devicestorageTest.password,
keySize: devicestorageTest.keySize,
rsaKeySize: devicestorageTest.rsaKeySize
}, function(err, generatedKeypair) {
ok(!err && generatedKeypair, 'Init crypto');
devicestorageTest.generatedKeypair = generatedKeypair;
// generate test data
devicestorageTest.list = testData.getEmailCollection(100);
// clear db before tests
jsonDao.clear(function(err) {
ok(!err, 'DB cleared. Error status: ' + err);
// init crypto
crypto = new Crypto();
crypto.init({
emailAddress: devicestorageTest.user,
password: devicestorageTest.password,
keySize: devicestorageTest.keySize,
rsaKeySize: devicestorageTest.rsaKeySize
}, function(err, generatedKeypair) {
ok(!err && generatedKeypair, 'Init crypto');
devicestorageTest.generatedKeypair = generatedKeypair;
start();
});
// clear db before tests
storage.clear(function(err) {
ok(!err, 'DB cleared. Error status: ' + err);
});
});
start();
});
asyncTest("Encrypt list for user", 2, function() {
var receiverPubkeys = [devicestorageTest.generatedKeypair.publicKey];
});
});
});
crypto.encryptListForUser(devicestorageTest.list, receiverPubkeys, function(err, encryptedList) {
ok(!err);
equal(encryptedList.length, devicestorageTest.list.length, 'Encrypt list');
asyncTest("Encrypt list for user", 2, function() {
var receiverPubkeys = [devicestorageTest.generatedKeypair.publicKey];
encryptedList.forEach(function(i) {
i.sentDate = _.findWhere(devicestorageTest.list, {
id: i.id
}).sentDate;
});
crypto.encryptListForUser(devicestorageTest.list, receiverPubkeys, function(err, encryptedList) {
ok(!err);
equal(encryptedList.length, devicestorageTest.list.length, 'Encrypt list');
devicestorageTest.encryptedList = encryptedList;
start();
});
});
encryptedList.forEach(function(i) {
i.sentDate = _.findWhere(devicestorageTest.list, {
id: i.id
}).sentDate;
});
asyncTest("Store encrypted list", 1, function() {
storage.storeEcryptedList(devicestorageTest.encryptedList, 'email_inbox', function() {
ok(true, 'Store encrypted list');
devicestorageTest.encryptedList = encryptedList;
start();
});
});
start();
});
});
asyncTest("Store encrypted list", 1, function() {
storage.storeEcryptedList(devicestorageTest.encryptedList, 'email_inbox', function() {
ok(true, 'Store encrypted list');
asyncTest("List items", 4, function() {
start();
});
});
var senderPubkeys = [devicestorageTest.generatedKeypair.publicKey];
asyncTest("List items", 4, function() {
var offset = 2,
num = 6;
var senderPubkeys = [devicestorageTest.generatedKeypair.publicKey];
// list encrypted items from storage
storage.listEncryptedItems('email_inbox', offset, num, function(err, encryptedList) {
ok(!err);
var offset = 2,
num = 6;
// decrypt list
crypto.decryptListForUser(encryptedList, senderPubkeys, function(err, decryptedList) {
ok(!err);
equal(decryptedList.length, num, 'Found ' + decryptedList.length + ' items in store (and decrypted)');
// list encrypted items from storage
storage.listEncryptedItems('email_inbox', offset, num, function(err, encryptedList) {
ok(!err);
var origSet = devicestorageTest.list.splice(92, num);
deepEqual(decryptedList, origSet, 'Messages decrypted correctly');
// decrypt list
crypto.decryptListForUser(encryptedList, senderPubkeys, function(err, decryptedList) {
ok(!err);
equal(decryptedList.length, num, 'Found ' + decryptedList.length + ' items in store (and decrypted)');
start();
});
});
});
var origSet = devicestorageTest.list.splice(92, num);
deepEqual(decryptedList, origSet, 'Messages decrypted correctly');
start();
});
});
});
});

View File

@ -28,11 +28,12 @@ define(['js/dao/keychain-dao', 'js/dao/lawnchair-dao'], function(KeychainDAO, js
ok(keychaindaoTest.keychainDao);
// init and clear db before test
jsonDao.init(keychaindaoTest.user);
jsonDao.clear(function() {
ok(true, 'cleared db');
jsonDao.init(keychaindaoTest.user, function() {
jsonDao.clear(function() {
ok(true, 'cleared db');
start();
start();
});
});
});
@ -70,9 +71,8 @@ define(['js/dao/keychain-dao', 'js/dao/lawnchair-dao'], function(KeychainDAO, js
asyncTest("Get Public Keys", 2, function() {
var pubkeyIds = [{
_id: keychaindaoTest.keypair.publicKey._id
}
];
_id: keychaindaoTest.keypair.publicKey._id
}];
keychaindaoTest.keychainDao.getPublicKeys(pubkeyIds, function(err, pubkeys) {
ok(!err);
deepEqual(pubkeys[0], keychaindaoTest.keypair.publicKey, "Fetch public key");

View File

@ -9,14 +9,15 @@ define(['js/dao/lawnchair-dao'], function(jsonDao) {
asyncTest("Init", 2, function() {
// init dependencies
jsonDao.init(lawnchairdaoTest.user);
ok(jsonDao, 'LanwchairDAO');
jsonDao.init(lawnchairdaoTest.user, function() {
ok(true, 'init db');
// clear db before test
jsonDao.clear(function() {
ok(true, 'cleared db');
// clear db before test
jsonDao.clear(function() {
ok(true, 'cleared db');
start();
start();
});
});
});