diff --git a/src/js/crypto/crypto.js b/src/js/crypto/crypto.js index b418734..7cab0ec 100644 --- a/src/js/crypto/crypto.js +++ b/src/js/crypto/crypto.js @@ -5,8 +5,11 @@ app.crypto.Crypto = function(window, util) { 'use strict'; - var aes = new app.crypto.AesCBC(forge), // use AES-CBC mode by default - rsa = new app.crypto.RSA(forge, util); // use RSA for asym. crypto + var aes = new app.crypto.AesCBC(forge); // use AES-CBC mode by default + var rsa = new app.crypto.RSA(forge, util); // use RSA for asym. crypto + var keyStore = new app.dao.LocalStorageDAO(window); + + var storageId; // storage id for the encrypted keypair in local storage /** * Initializes the crypto modules by fetching the user's @@ -28,18 +31,18 @@ app.crypto.Crypto = function(window, util) { this.ivSize = args.keySize; this.rsaKeySize = args.rsaKeySize; + storageId = self.emailAddress + '_encryptedKeypair'; + // derive PBKDF2 from password in web worker thread - this.deriveKey(args.password, args.keySize, function(pbkdf2) { + this.deriveKey(args.password, self.keySize, function(pbkdf2) { // fetch user's encrypted secret key from keychain/storage - var keyStore = new app.dao.LocalStorageDAO(window); - var storageId = args.emailAddress + '_encryptedKeypair'; var storedKeypair = keyStore.read(storageId); // check if key exists if (!storedKeypair) { // generate keys, encrypt and persist if none exists - generateKeypair(keyStore, storageId, pbkdf2); + generateKeypair(pbkdf2); } else { // decrypt key decryptKeypair(storedKeypair, pbkdf2); @@ -47,7 +50,7 @@ app.crypto.Crypto = function(window, util) { }); - function generateKeypair(keyStore, storageId, pbkdf2) { + function generateKeypair(pbkdf2) { // generate RSA keypair in web worker rsa.generateKeypair(self.rsaKeySize, function(err) { if (err) { @@ -64,7 +67,7 @@ app.crypto.Crypto = function(window, util) { // store encrypted keypair var newStoredKeypair = { _id: keypair._id, - userId: args.emailAddress, + userId: self.emailAddress, encryptedKey: encryptedKeys, iv: iv }; @@ -106,6 +109,31 @@ app.crypto.Crypto = function(window, util) { }; }; + /** + * Return a Private Key object containing the encrypted private key + */ + this.getEncryptedPrivateKey = function(emailAddress) { + if (!emailAddress && !storageId) { + throw new Error('Emailaddress needs to be set or crypto needs to be initiated!'); + } + + var strgId = (storageId) ? storageId : emailAddress + '_encryptedKeypair'; + var storedKeypair = keyStore.read(strgId); + + return storedKeypair; + }; + + this.putEncryptedPrivateKey = function(privkey) { + var strgId = (storageId) ? storageId : privkey.userId + '_encryptedKeypair'; + + // validate private key object + if (!strgId || !privkey || !privkey._id || !privkey.userId || !privkey.encryptedKey || !privkey.iv) { + throw new Error('Invalid encrypted private key object... will not store!'); + } + + return keyStore.persist(strgId, privkey); + }; + /** * Do PBKDF2 key derivation in a WebWorker thread */ diff --git a/src/js/dao/cloudstorage-dao.js b/src/js/dao/cloudstorage-dao.js index ba8aedd..9a1613a 100644 --- a/src/js/dao/cloudstorage-dao.js +++ b/src/js/dao/cloudstorage-dao.js @@ -143,32 +143,21 @@ app.dao.CloudStorage = function(window, $) { }; // - // Secret Key + // Ecrypted Private Key // /** - * Persist encrypted user key to cloud service + * Persist encrypted private key to cloud service */ - this.putUserSecretKey = function(emailAddress, callback) { + this.putPrivateKey = function(privkey, callback) { // fetch user's encrypted secret key from keychain/storage - var keyStore = new app.dao.LocalStorageDAO(window), - storageId = emailAddress + '_encryptedSymmetricKey', - storedKey = keyStore.read(storageId), - uri; + var uri; - if (!storedKey) { - callback({ - error: 'err', - status: 'No key found in storage!' - }); - return; - } - - uri = app.config.cloudUrl + '/secretkey/user/' + emailAddress + '/key/' + storedKey._id; + uri = app.config.cloudUrl + '/privatekey/user/' + privkey.userId + '/key/' + privkey._id; $.ajax({ url: uri, type: 'PUT', - data: JSON.stringify(storedKey), + data: JSON.stringify(privkey), contentType: 'application/json', success: function() { callback(); @@ -183,17 +172,14 @@ app.dao.CloudStorage = function(window, $) { }; /** - * Get encrypted user key from cloud service + * Sync encrypted private key from cloud service */ - this.getUserSecretKey = function(emailAddress, callback, replaceCallback) { + this.syncPrivateKey = function(emailAddress, storedkey, callback, replaceCallback) { // fetch user's encrypted secret key from keychain/storage var self = this, - keyStore = new app.dao.LocalStorageDAO(window), - storageId = emailAddress + '_encryptedSymmetricKey', - storedKey = keyStore.read(storageId), gottenKey, uri; - uri = app.config.cloudUrl + '/secretkey/user/' + emailAddress; + uri = app.config.cloudUrl + '/privatekey/user/' + emailAddress; $.ajax({ url: uri, type: 'GET', @@ -223,25 +209,23 @@ app.dao.CloudStorage = function(window, $) { }); function handleKey(fetchedKey, callback) { - if ((!storedKey || !storedKey.encryptedKey) && fetchedKey && fetchedKey.encryptedKey && fetchedKey.keyIV) { + if ((!storedkey || !storedkey.encryptedKey) && fetchedKey && fetchedKey.encryptedKey) { // no local key... persist fetched key - keyStore.persist(storageId, fetchedKey); - replaceCallback(); + replaceCallback(fetchedKey); - } else if (!fetchedKey && storedKey && storedKey.encryptedKey && storedKey.keyIV) { + } else if (!fetchedKey && storedkey && storedkey.encryptedKey) { // no key in cloud... push local key to cloud - self.putUserSecretKey(emailAddress, callback); + self.putPrivateKey(storedkey, callback); - } else if (storedKey && fetchedKey && (storedKey.encryptedKey !== fetchedKey.encryptedKey || storedKey.keyIV !== fetchedKey.keyIV)) { + } else if (storedkey && fetchedKey && (storedkey.encryptedKey !== fetchedKey.encryptedKey || storedkey.iv !== fetchedKey.iv)) { // local and fetched keys are not equal if (window.confirm('Swap local key?')) { // replace local key with fetched key - keyStore.persist(storageId, fetchedKey); - replaceCallback(); + replaceCallback(fetchedKey); } else { if (window.confirm('Swap cloud key?')) { // upload local key to cloud - self.putUserSecretKey(emailAddress, callback); + self.putPrivateKey(emailAddress, callback); } else { callback({ error: 'err', diff --git a/src/js/dao/email-dao.js b/src/js/dao/email-dao.js index 17e277b..e326561 100644 --- a/src/js/dao/email-dao.js +++ b/src/js/dao/email-dao.js @@ -12,15 +12,18 @@ app.dao.EmailDAO = function(_, crypto, devicestorage, cloudstorage, util) { this.account = account; // sync user's cloud key with local storage - cloudstorage.getUserSecretKey(account.get('emailAddress'), function(err) { + var storedKey = crypto.getEncryptedPrivateKey(account.get('emailAddress')); + cloudstorage.syncPrivateKey(account.get('emailAddress'), storedKey, function(err) { if (err) { console.log('Error syncing secret key to cloud: ' + err); } // init crypto initCrypto(); - }, function() { - // replaced local key with cloud key... whipe local storage + }, function(fetchedKey) { + // replace local key with cloud key + crypto.putEncryptedPrivateKey(fetchedKey); + // whipe local storage devicestorage.clear(function() { initCrypto(); }); diff --git a/test/integration/cloudstorage-dao-test.js b/test/integration/cloudstorage-dao-test.js index ea584fa..fac0045 100644 --- a/test/integration/cloudstorage-dao-test.js +++ b/test/integration/cloudstorage-dao-test.js @@ -4,18 +4,18 @@ var cloudstoragedao_test = { user: 'email.dao.it.test@mail.whiteout.io', password: 'hellosafe', keySize: 128, - ivSize: 128 + ivSize: 128, + rsaKeySize: 1024 }; asyncTest("Init", 1, function() { // init dependencies cloudstoragedao_test.util = new app.crypto.Util(window, uuid); var jsonDao = new app.dao.LawnchairDAO(window); - var crypto = new app.crypto.Crypto(window, cloudstoragedao_test.util); - var naclCrypto = new app.crypto.NaclCrypto(nacl, cloudstoragedao_test.util); - cloudstoragedao_test.storage = new app.dao.DeviceStorage(cloudstoragedao_test.util, crypto, jsonDao, null); + cloudstoragedao_test.crypto = new app.crypto.Crypto(window, cloudstoragedao_test.util); + cloudstoragedao_test.storage = new app.dao.DeviceStorage(cloudstoragedao_test.util, cloudstoragedao_test.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.util); + cloudstoragedao_test.emailDao = new app.dao.EmailDAO(_, cloudstoragedao_test.crypto, cloudstoragedao_test.storage, cloudstoragedao_test.cloudstorage, cloudstoragedao_test.util); // clear db before tests jsonDao.clear(function(err) { @@ -28,11 +28,33 @@ asyncTest("Init", 1, function() { asyncTest("Persist public key to cloud", 1, function() { // testdata - cloudstoragedao_test.privateKey = "Bv51afjeuH8CatKo75HOHQRT1B3amvF+DEwijka79nA="; + cloudstoragedao_test.privateKey = "-----BEGIN RSA PRIVATE KEY-----\r\n" + + "MIICXAIBAAKBgQDK6H7BiPcwiRWnWDuqndw+t+3vIhSmwEEn38kPLenbd+iWb2dX\r\n" + + "M5y5aBFIgqqHBrcZLwzhMQ10BUTcOgB6Kr3AK7lONKxZ+HD5hX6koj9X5uHtFYF1\r\n" + + "NYkQv+5WKzHGHRFqoKityZ6AqTxgPss29s6EIOqF/dvvKMiFhgp+4JPsJQIDAQAB\r\n" + + "AoGAQxIM7C44/zshBDrfJiueJMEpjhUm3GPKZcLMNA9KMPh20lsqvqFZ2dNzexNu\r\n" + + "CMoIdfOef0V2m/Yt59noVHmSVL7itN4nvbTcD39UQacFiyzT7GRQjeaVAs8ZyeO5\r\n" + + "2AXtJTNipEyvJ3TbJZCOCML/wOEvCimyHLNCMcoDvkjAbMECQQD81xbRonOZt/7E\r\n" + + "fBHZQonaTQU/x88l8bXDHvcPfMfg4QkPO+pZ8dBQ4+IpuG60kl4TSmmme4frcJoj\r\n" + + "jSqd54VVAkEAzXGon2gP+9ZjhbOWESpw+JXiRBytAgailnblFnCJt+o+UoXU8hwH\r\n" + + "1D5rG2yOIO1vOiqGDQq/Bs61DsfeotvLkQJBAKo6tmZWFba9Jo5raij4n4+Wo54Z\r\n" + + "jOJjJplEU9rdjEVfvZXAJTyBjlun0jF8tyxkD2q1gwRPz2c43M5q0PKXWjECQCl4\r\n" + + "UO5khh1yyEIb3yX16Dn1n2faVf37suQmedXOv631RcFIrJR2ngn005AEmKgC5Znb\r\n" + + "LZYCXk8UeK3UIJfFQFECQGkP1NPyd10Z76LR0lXeL15iP22M/OCaQUIsSi/S+idL\r\n" + + "YCVcgDpdgVXef0NeNk6w821rlqUjseZyGGKpJ4VNywU=\r\n" + + "-----END RSA PRIVATE KEY-----"; + + var pk = "-----BEGIN PUBLIC KEY-----\r\n" + + "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDK6H7BiPcwiRWnWDuqndw+t+3v\r\n" + + "IhSmwEEn38kPLenbd+iWb2dXM5y5aBFIgqqHBrcZLwzhMQ10BUTcOgB6Kr3AK7lO\r\n" + + "NKxZ+HD5hX6koj9X5uHtFYF1NYkQv+5WKzHGHRFqoKityZ6AqTxgPss29s6EIOqF\r\n" + + "/dvvKMiFhgp+4JPsJQIDAQAB\r\n" + + "-----END PUBLIC KEY-----"; + cloudstoragedao_test.publicKey = new app.model.PublicKey({ - _id: "da4bfa93-ba87-490e-877c-e4020a6f6729", + _id: "e91f04a2-a634-42df-a1a4-6a7f1448dbf6", userId: 'integration@atlasdev.onmicrosoft.com', - publicKey: "yHUhh3Pcbjmh2k367pSXfE8hDwPsAxQs0QETm9mfmz0=" + publicKey: pk }); cloudstoragedao_test.cloudstorage.putPublicKey(cloudstoragedao_test.publicKey.toJSON(), function(err) { @@ -51,12 +73,15 @@ asyncTest("Get Public key from cloud", 2, function() { }); }); -asyncTest("Get user secret key from cloud", 1, function() { - cloudstoragedao_test.cloudstorage.getUserSecretKey(cloudstoragedao_test.user, function(err) { +asyncTest("Sync private key from cloud", 1, function() { + cloudstoragedao_test.cloudstorage.syncPrivateKey(cloudstoragedao_test.user, null, function(err) { ok(!err, 'Get/Sync key from cloud'); start(); - }, function() { + }, function(fetchedKey) { + // replace local key with cloud key + cloudstoragedao_test.crypto.putEncryptedPrivateKey(fetchedKey); + // whipe local storage cloudstoragedao_test.storage.clear(function(err) { ok(!err, 'DB cleared. Error status: ' + err); @@ -65,8 +90,10 @@ asyncTest("Get user secret key from cloud", 1, function() { }); }); -asyncTest("Persist user secret key to cloud", 1, function() { - cloudstoragedao_test.cloudstorage.putUserSecretKey(cloudstoragedao_test.user, function(err) { +asyncTest("Persist private key to cloud", 1, function() { + var storedKey = cloudstoragedao_test.crypto.getEncryptedPrivateKey(cloudstoragedao_test.user); + + cloudstoragedao_test.cloudstorage.putPrivateKey(storedKey, function(err) { ok(!err, 'Persist key to cloud'); start(); @@ -81,7 +108,8 @@ asyncTest("Init", 1, function() { var account = new app.model.Account({ emailAddress: cloudstoragedao_test.user, symKeySize: cloudstoragedao_test.keySize, - symIvSize: cloudstoragedao_test.ivSize + symIvSize: cloudstoragedao_test.ivSize, + asymKeySize: cloudstoragedao_test.rsaKeySize }); cloudstoragedao_test.emailDao.init(account, cloudstoragedao_test.password, function(err) { @@ -91,39 +119,39 @@ asyncTest("Init", 1, function() { }); }); -asyncTest("Send Plaintext Email item", 1, function() { +// asyncTest("Send Plaintext Email item", 1, function() { - var email = new app.model.Email({ - id: cloudstoragedao_test.util.UUID(), - from: cloudstoragedao_test.user, // sender address - to: [cloudstoragedao_test.user], // list of receivers - subject: 'Client Email DAO Test', // Subject line - body: 'Hello world' // plaintext body - }); +// var email = new app.model.Email({ +// id: cloudstoragedao_test.util.UUID(), +// from: cloudstoragedao_test.user, // sender address +// to: [cloudstoragedao_test.user], // list of receivers +// subject: 'Client Email DAO Test', // Subject line +// body: 'Hello world' // plaintext body +// }); - cloudstoragedao_test.emailDao.sendEmail(email, function(err) { - ok(!err, 'Email sent'); +// cloudstoragedao_test.emailDao.sendEmail(email, function(err) { +// ok(!err, 'Email sent'); - start(); - }); -}); +// start(); +// }); +// }); -asyncTest("Check virtual inbox, re-encrypt and push to cloud", 1, function() { - cloudstoragedao_test.emailDao.checkVInbox(function(err) { - ok(!err, 'Synced items'); +// asyncTest("Check virtual inbox, re-encrypt and push to cloud", 1, function() { +// cloudstoragedao_test.emailDao.checkVInbox(function(err) { +// ok(!err, 'Synced items'); - start(); - }); -}); +// start(); +// }); +// }); -asyncTest("Sync emails from cloud", 2, function() { - cloudstoragedao_test.emailDao.syncFromCloud('inbox', function(err) { - ok(!err, 'Synced items'); +// asyncTest("Sync emails from cloud", 2, function() { +// cloudstoragedao_test.emailDao.syncFromCloud('inbox', function(err) { +// ok(!err, 'Synced items'); - cloudstoragedao_test.emailDao.listItems('inbox', 0, null, function(collection) { - ok(collection.length > 0, 'Read synced items'); +// cloudstoragedao_test.emailDao.listItems('inbox', 0, null, function(collection) { +// ok(collection.length > 0, 'Read synced items'); - start(); - }); - }); -}); \ No newline at end of file +// start(); +// }); +// }); +// }); \ No newline at end of file diff --git a/test/unit/crypto-test.js b/test/unit/crypto-test.js index 855ae53..8f83026 100644 --- a/test/unit/crypto-test.js +++ b/test/unit/crypto-test.js @@ -32,6 +32,19 @@ test("Get Public Key PEM", 2, function() { ok(pk.publicKey.indexOf('-----BEGIN PUBLIC KEY-----') === 0, pk.publicKey); }); +test("Get Encrypted Private Key", 2, function() { + var prk = crypto_test.crypto.getEncryptedPrivateKey(); + ok(prk._id && prk.userId, 'Key ID: ' + prk._id); + ok(prk.encryptedKey, prk.encryptedKey); + + crypto_test.prk = prk; +}); + +test("Put Encrypted Private Key", 1, function() { + crypto_test.crypto.putEncryptedPrivateKey(crypto_test.prk); + ok(true); +}); + asyncTest("PBKDF2 (Async/Worker)", 1, function() { crypto_test.crypto.deriveKey(crypto_test.password, crypto_test.keySize, function(key) { equal(crypto_test.util.base642Str(key).length * 8, crypto_test.keySize, 'Keysize ' + crypto_test.keySize); diff --git a/test/unit/devicestorage-test.js b/test/unit/devicestorage-test.js index 46dd392..ef7496c 100644 --- a/test/unit/devicestorage-test.js +++ b/test/unit/devicestorage-test.js @@ -4,7 +4,8 @@ var devicestorage_test = { user: 'devicestorage_test@example.com', password: 'Password', keySize: 128, - ivSize: 128 + ivSize: 128, + rsaKeySize: 1024 }; asyncTest("Init", 3, function() { @@ -22,7 +23,8 @@ asyncTest("Init", 3, function() { devicestorage_test.crypto.init({ emailAddress: devicestorage_test.user, password: devicestorage_test.password, - keySize: devicestorage_test.keySize + keySize: devicestorage_test.keySize, + rsaKeySize: devicestorage_test.rsaKeySize }, function(err) { ok(!err, 'Init crypto'); diff --git a/test/unit/email-dao-test.js b/test/unit/email-dao-test.js index 23f3cd6..dfdb06d 100644 --- a/test/unit/email-dao-test.js +++ b/test/unit/email-dao-test.js @@ -16,7 +16,7 @@ asyncTest("Init", 3, function() { emaildao_test.storage = new app.dao.DeviceStorage(util, emaildao_test.crypto, jsonDao, null); // cloud storage stub var cloudstorageStub = { - getUserSecretKey: function(emailAdress, callback) { + syncPrivateKey: function(emailAdress, storedKey, callback) { callback(); }, putPublicKey: function(pk, callback) {