1
0
mirror of https://github.com/moparisthebest/mail synced 2024-11-14 21:25:03 -05:00
mail/src/js/crypto/crypto.js

363 lines
11 KiB
JavaScript
Raw Normal View History

2013-04-02 09:02:57 -04:00
/**
* High level crypto api that invokes native crypto (if available) and
* gracefully degrades to JS crypto (if unavailable)
*/
2013-08-30 10:05:33 -04:00
define(function(require) {
'use strict';
var util = require('cryptoLib/util'),
aes = require('cryptoLib/aes-cbc'),
rsa = require('cryptoLib/rsa'),
cryptoBatch = require('cryptoLib/crypto-batch'),
pbkdf2 = require('js/crypto/pbkdf2'),
2013-09-15 09:17:28 -04:00
config = require('js/app-config').config;
2013-08-30 10:05:33 -04:00
2013-09-26 07:26:57 -04:00
var passBasedKey,
BATCH_WORKER = '/crypto/crypto-batch-worker.js',
PBKDF2_WORKER = '/crypto/pbkdf2-worker.js';
2013-08-30 10:05:33 -04:00
2013-09-26 07:26:57 -04:00
var Crypto = function() {
};
2013-08-30 10:05:33 -04:00
/**
* Initializes the crypto modules by fetching the user's
* encrypted secret key from storage and storing it in memory.
*/
2013-09-26 07:26:57 -04:00
Crypto.prototype.init = function(args, callback) {
var self = this;
2013-08-30 10:05:33 -04:00
// valdiate input
if (!args.emailAddress || !args.keySize || !args.rsaKeySize || typeof args.password !== 'string' || !args.salt) {
2013-08-30 10:05:33 -04:00
callback({
errMsg: 'Crypto init failed. Not all args set!'
});
return;
}
self.emailAddress = args.emailAddress;
self.keySize = args.keySize;
self.ivSize = args.keySize;
self.rsaKeySize = args.rsaKeySize;
// derive PBKDF2 from password in web worker thread
self.deriveKey(args.password, args.salt, self.keySize, function(err, derivedKey) {
2013-08-30 10:05:33 -04:00
if (err) {
callback(err);
return;
}
// remember pbkdf2 for later use
passBasedKey = derivedKey;
// check if key exists
if (!args.storedKeypair) {
// generate keys, encrypt and persist if none exists
generateKeypair(derivedKey);
} else {
// decrypt key
decryptKeypair(args.storedKeypair, derivedKey);
}
});
function generateKeypair(derivedKey) {
// generate RSA keypair in web worker
rsa.generateKeypair(self.rsaKeySize, function(err, generatedKeypair) {
if (err) {
callback(err);
return;
}
// encrypt keypair
var iv = util.random(self.ivSize);
var encryptedPrivateKey = aes.encrypt(generatedKeypair.privkeyPem, derivedKey, iv);
// new encrypted keypair object
var newKeypair = {
publicKey: {
_id: generatedKeypair._id,
userId: self.emailAddress,
publicKey: generatedKeypair.pubkeyPem
},
privateKey: {
_id: generatedKeypair._id,
userId: self.emailAddress,
encryptedKey: encryptedPrivateKey,
iv: iv
}
};
// return generated keypair for storage in keychain dao
callback(null, newKeypair);
});
}
function decryptKeypair(storedKeypair, derivedKey) {
var decryptedPrivateKey;
// validate input
if (!storedKeypair || !storedKeypair.privateKey || !storedKeypair.privateKey.encryptedKey || !storedKeypair.privateKey.iv) {
callback({
errMsg: 'Incomplete arguments for private key decryption!'
});
return;
}
// try to decrypt with derivedKey
try {
var prK = storedKeypair.privateKey;
decryptedPrivateKey = aes.decrypt(prK.encryptedKey, derivedKey, prK.iv);
} catch (ex) {
callback({
errMsg: 'Wrong password!'
});
return;
}
// set rsa keys
rsa.init(storedKeypair.publicKey.publicKey, decryptedPrivateKey, storedKeypair.publicKey._id);
callback();
}
};
/**
* Do PBKDF2 key derivation in a WebWorker thread
*/
Crypto.prototype.deriveKey = function(password, salt, keySize, callback) {
startWorker({
script: PBKDF2_WORKER,
args: {
password: password,
salt: salt,
keySize: keySize
},
callback: callback,
noWorker: function() {
return pbkdf2.getKey(password, salt, keySize);
}
2013-08-30 10:05:33 -04:00
});
};
//
// En/Decrypt a list of items with AES in a WebWorker thread
2013-08-30 10:05:33 -04:00
//
2013-09-26 07:26:57 -04:00
Crypto.prototype.symEncryptList = function(list, callback) {
var self = this,
key, envelope, envelopes = [];
2013-08-30 10:05:33 -04:00
// generate single secret key shared for all list items
key = util.random(self.keySize);
2013-08-30 10:05:33 -04:00
// package objects into batchable envelope format
list.forEach(function(i) {
envelope = {
id: i.id,
plaintext: i,
key: key,
iv: util.random(self.ivSize)
};
envelopes.push(envelope);
});
2013-08-30 10:05:33 -04:00
startWorker({
script: BATCH_WORKER,
args: {
type: 'symEncrypt',
list: envelopes
},
callback: function(err, encryptedList) {
// return generated secret key
callback(err, {
key: key,
list: encryptedList
});
},
noWorker: function() {
return cryptoBatch.authEncryptList(envelopes);
}
2013-08-30 10:05:33 -04:00
});
};
2013-09-26 07:26:57 -04:00
Crypto.prototype.symDecryptList = function(list, keys, callback) {
startWorker({
script: BATCH_WORKER,
args: {
type: 'symDecrypt',
list: list,
keys: keys
},
callback: callback,
noWorker: function() {
return cryptoBatch.authDecryptList(list, keys);
}
2013-08-30 10:05:33 -04:00
});
};
//
// En/Decrypt something speficially using the user's secret key
//
2013-09-26 07:26:57 -04:00
Crypto.prototype.encryptListForUser = function(list, receiverPubkeys, callback) {
var self = this,
envelope, envelopes = [];
2013-08-30 10:05:33 -04:00
if (!receiverPubkeys || receiverPubkeys.length !== 1) {
callback({
errMsg: 'Encryption is currently implemented for only one receiver!'
});
return;
}
var keypair = rsa.exportKeys();
var senderPrivkey = {
_id: keypair._id,
privateKey: keypair.privkeyPem
};
// package objects into batchable envelope format
list.forEach(function(i) {
envelope = {
id: i.id,
plaintext: i,
key: util.random(self.keySize),
iv: util.random(self.ivSize),
receiverPk: receiverPubkeys[0]._id
};
envelopes.push(envelope);
});
startWorker({
script: BATCH_WORKER,
args: {
type: 'asymEncrypt',
list: envelopes,
senderPrivkey: senderPrivkey,
receiverPubkeys: receiverPubkeys
},
callback: callback,
noWorker: function() {
return cryptoBatch.encryptListForUser(envelopes, receiverPubkeys, senderPrivkey);
}
2013-08-30 10:05:33 -04:00
});
};
2013-09-26 07:26:57 -04:00
Crypto.prototype.decryptListForUser = function(list, senderPubkeys, callback) {
2013-08-30 10:05:33 -04:00
if (!senderPubkeys || senderPubkeys < 1) {
callback({
errMsg: 'Sender public keys must be set!'
});
return;
}
var keypair = rsa.exportKeys();
var receiverPrivkey = {
_id: keypair._id,
privateKey: keypair.privkeyPem
};
startWorker({
script: BATCH_WORKER,
args: {
type: 'asymDecrypt',
list: list,
receiverPrivkey: receiverPrivkey,
senderPubkeys: senderPubkeys
},
callback: callback,
noWorker: function() {
return cryptoBatch.decryptListForUser(list, senderPubkeys, receiverPrivkey);
}
2013-08-30 10:05:33 -04:00
});
};
//
// Re-encrypt keys item and items seperately
//
2013-09-26 07:26:57 -04:00
Crypto.prototype.reencryptListKeysForUser = function(list, senderPubkeys, callback) {
2013-08-30 10:05:33 -04:00
var keypair = rsa.exportKeys();
var receiverPrivkey = {
_id: keypair._id,
privateKey: keypair.privkeyPem
};
startWorker({
script: BATCH_WORKER,
args: {
type: 'reencrypt',
list: list,
receiverPrivkey: receiverPrivkey,
senderPubkeys: senderPubkeys,
symKey: passBasedKey
},
callback: callback,
noWorker: function() {
return cryptoBatch.reencryptListKeysForUser(list, senderPubkeys, receiverPrivkey, passBasedKey);
}
2013-08-30 10:05:33 -04:00
});
};
2013-09-26 07:26:57 -04:00
Crypto.prototype.decryptKeysAndList = function(list, callback) {
startWorker({
script: BATCH_WORKER,
args: {
type: 'decryptItems',
list: list,
symKey: passBasedKey
},
callback: callback,
noWorker: function() {
return cryptoBatch.decryptKeysAndList(list, passBasedKey);
}
2013-08-30 10:05:33 -04:00
});
};
//
// helper functions
//
function startWorker(options) {
2013-08-30 10:05:33 -04:00
// check for WebWorker support
if (window.Worker) {
// init webworker thread
2013-09-15 09:17:28 -04:00
var worker = new Worker(config.workerPath + options.script);
2013-08-30 10:05:33 -04:00
worker.onmessage = function(e) {
if (e.data.err) {
options.callback(e.data.err);
2013-08-30 10:05:33 -04:00
return;
}
// return result from the worker
options.callback(null, e.data);
2013-08-30 10:05:33 -04:00
};
worker.onerror = function(e) {
// show error message in console
console.error('Error handling web worker: Line ' + e.lineno + ' in ' + e.filename + ': ' + e.message);
// return error
options.callback({
errMsg: (e.message) ? e.message : e
});
return;
};
2013-08-30 10:05:33 -04:00
// send data to the worker
worker.postMessage(options.args);
return;
}
2013-08-30 10:05:33 -04:00
// no WebWorker support... do synchronous call
var result;
try {
result = options.noWorker();
} catch (e) {
// return error
options.callback({
errMsg: (e.message) ? e.message : e
});
return;
2013-08-30 10:05:33 -04:00
}
options.callback(null, result);
2013-08-30 10:05:33 -04:00
}
2013-09-26 07:26:57 -04:00
return Crypto;
2013-06-10 11:57:33 -04:00
});