mail/src/js/crypto/crypto.js

275 lines
6.6 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)
*/
app.crypto.Crypto = function(window, util) {
'use strict';
var aes = new cryptoLib.AesCBC(forge); // use AES-CBC mode by default
var rsa = new cryptoLib.RSA(forge, util); // use RSA for asym. crypto
2013-04-02 09:02:57 -04:00
2013-03-13 11:58:46 -04:00
/**
2013-04-02 09:02:57 -04:00
* Initializes the crypto modules by fetching the user's
* encrypted secret key from storage and storing it in memory.
2013-03-13 11:58:46 -04:00
*/
2013-05-14 14:28:12 -04:00
this.init = function(args, callback) {
var self = this;
2013-05-18 19:33:59 -04:00
// valdiate input
if (!args.emailAddress || !args.keySize || !args.rsaKeySize) {
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;
2013-05-18 22:00:53 -04:00
2013-04-02 09:02:57 -04:00
// derive PBKDF2 from password in web worker thread
self.deriveKey(args.password, self.keySize, function(err, pbkdf2) {
if (err) {
callback(err);
return;
}
2013-04-02 09:02:57 -04:00
// check if key exists
if (!args.storedKeypair) {
2013-05-14 14:28:12 -04:00
// generate keys, encrypt and persist if none exists
2013-05-18 22:00:53 -04:00
generateKeypair(pbkdf2);
} else {
2013-04-02 09:02:57 -04:00
// decrypt key
decryptKeypair(args.storedKeypair, pbkdf2);
2013-05-14 14:28:12 -04:00
}
});
2013-05-18 22:00:53 -04:00
function generateKeypair(pbkdf2) {
2013-05-14 14:28:12 -04:00
// generate RSA keypair in web worker
rsa.generateKeypair(self.rsaKeySize, function(err, generatedKeypair) {
2013-05-14 14:28:12 -04:00
if (err) {
callback(err);
return;
}
2013-05-14 14:28:12 -04:00
// encrypt keypair
var iv = util.random(self.ivSize);
var encryptedPrivateKey = aes.encrypt(generatedKeypair.privkeyPem, pbkdf2, 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
}
2013-05-14 14:28:12 -04:00
};
// return generated keypair for storage in keychain dao
callback(null, newKeypair);
2013-05-14 14:28:12 -04:00
});
}
function decryptKeypair(storedKeypair, pbkdf2) {
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 pbkdf2
2013-05-14 14:28:12 -04:00
try {
var prK = storedKeypair.privateKey;
decryptedPrivateKey = aes.decrypt(prK.encryptedKey, pbkdf2, prK.iv);
2013-05-14 14:28:12 -04:00
} catch (ex) {
callback({
errMsg: 'Wrong password!'
});
return;
2013-03-13 11:58:46 -04:00
}
// set rsa keys
rsa.init(storedKeypair.publicKey.publicKey, decryptedPrivateKey, storedKeypair.publicKey._id);
2013-04-02 09:02:57 -04:00
callback();
2013-05-14 14:28:12 -04:00
}
2013-04-02 09:02:57 -04:00
};
2013-04-02 09:02:57 -04:00
/**
* Do PBKDF2 key derivation in a WebWorker thread
*/
this.deriveKey = function(password, keySize, callback) {
startWorker('/crypto/pbkdf2-worker.js', {
password: password,
keySize: keySize
}, callback, function() {
2013-04-02 09:02:57 -04:00
var pbkdf2 = new app.crypto.PBKDF2();
return pbkdf2.getKey(password, keySize);
});
2013-04-02 09:02:57 -04:00
};
2013-04-02 09:02:57 -04:00
//
// En/Decrypts single item
//
this.aesEncrypt = function(plaintext, key, iv, callback) {
var self = this;
startWorker('/crypto/aes-worker.js', {
type: 'encrypt',
plaintext: plaintext,
key: key,
iv: iv
}, callback, function() {
return self.aesEncryptSync(plaintext, key, iv);
});
2013-04-02 09:02:57 -04:00
};
this.aesDecrypt = function(ciphertext, key, iv, callback) {
var self = this;
startWorker('/crypto/aes-worker.js', {
type: 'decrypt',
ciphertext: ciphertext,
key: key,
iv: iv
}, callback, function() {
return self.aesDecryptSync(ciphertext, key, iv);
});
2013-04-02 09:02:57 -04:00
};
this.aesEncryptSync = function(plaintext, key, iv) {
return aes.encrypt(plaintext, key, iv);
};
this.aesDecryptSync = function(ciphertext, key, iv) {
return aes.decrypt(ciphertext, key, iv);
};
//
// En/Decrypt a list of items with AES in a WebWorker thread
//
this.aesEncryptList = function(list, callback) {
startWorker('/crypto/aes-batch-worker.js', {
type: 'encrypt',
list: list
}, callback, function() {
var batch = new cryptoLib.CryptoBatch(aes);
return batch.encryptList(list);
});
2013-04-02 09:02:57 -04:00
};
this.aesDecryptList = function(list, callback) {
startWorker('/crypto/aes-batch-worker.js', {
type: 'decrypt',
list: list
}, callback, function() {
var batch = new cryptoLib.CryptoBatch(aes);
return batch.decryptList(list);
});
2013-04-02 09:02:57 -04:00
};
//
// En/Decrypt something speficially using the user's secret key
//
this.encryptListForUser = function(list, receiverPubkeys, callback) {
var envelope, envelopes = [],
2013-04-02 09:02:57 -04:00
self = this;
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
};
2013-04-02 09:02:57 -04:00
// package objects into batchable envelope format
list.forEach(function(i) {
2013-04-02 09:02:57 -04:00
envelope = {
id: i.id,
plaintext: i,
2013-04-02 09:02:57 -04:00
key: util.random(self.keySize),
iv: util.random(self.ivSize),
receiverPk: receiverPubkeys[0]._id
2013-04-02 09:02:57 -04:00
};
envelopes.push(envelope);
});
2013-04-02 09:02:57 -04:00
startWorker('/crypto/crypto-batch-worker.js', {
type: 'encrypt',
list: envelopes,
senderPrivkey: senderPrivkey,
receiverPubkeys: receiverPubkeys
}, callback, function() {
var batch = new cryptoLib.CryptoBatch(aes, rsa, util, _);
return batch.encryptListForUser(envelopes, receiverPubkeys, senderPrivkey);
});
2013-04-02 09:02:57 -04:00
};
this.decryptListForUser = function(list, senderPubkeys, callback) {
if (!senderPubkeys || senderPubkeys < 1) {
callback({
errMsg: 'Sender public keys must be set!'
});
return;
}
2013-04-02 09:02:57 -04:00
var keypair = rsa.exportKeys();
var receiverPrivkey = {
_id: keypair._id,
privateKey: keypair.privkeyPem
};
startWorker('/crypto/crypto-batch-worker.js', {
type: 'decrypt',
list: list,
receiverPrivkey: receiverPrivkey,
senderPubkeys: senderPubkeys
}, callback, function() {
var batch = new cryptoLib.CryptoBatch(aes, rsa, util, _);
return batch.decryptListForUser(list, senderPubkeys, receiverPrivkey);
});
};
function startWorker(script, args, callback, noWorker) {
// check for WebWorker support
if (window.Worker) {
2013-04-02 09:02:57 -04:00
// init webworker thread
var worker = new Worker(app.config.workerPath + script);
worker.onmessage = function(e) {
// return derived key from the worker
callback(null, e.data);
};
// send data to the worker
worker.postMessage(args);
2013-03-13 11:58:46 -04:00
} else {
// no WebWorker support... do synchronous call
var result = noWorker();
callback(null, result);
}
}
2013-04-02 09:02:57 -04:00
};