refactored crypto

This commit is contained in:
Tankred Hase 2013-06-10 23:07:29 +02:00
parent 5d409933e5
commit ccebe011cb
6 changed files with 114 additions and 110 deletions

View File

@ -2,7 +2,7 @@
* High level crypto api that invokes native crypto (if available) and
* gracefully degrades to JS crypto (if unavailable)
*/
define(['cryptoLib/util', 'cryptoLib/aes-cbc', 'cryptoLib/rsa', 'cryptoLib/crypto-batch'], function(util, aes, rsa, cryptoBatch) {
define(['cryptoLib/util', 'cryptoLib/aes-cbc', 'cryptoLib/rsa', 'cryptoLib/crypto-batch', 'js/crypto/pbkdf2'], function(util, aes, rsa, cryptoBatch, pbkdf2) {
'use strict';
var self = {};
@ -26,7 +26,7 @@ define(['cryptoLib/util', 'cryptoLib/aes-cbc', 'cryptoLib/rsa', 'cryptoLib/crypt
self.rsaKeySize = args.rsaKeySize;
// derive PBKDF2 from password in web worker thread
self.deriveKey(args.password, self.keySize, function(err, pbkdf2) {
self.deriveKey(args.password, self.keySize, function(err, derivedKey) {
if (err) {
callback(err);
return;
@ -35,15 +35,15 @@ define(['cryptoLib/util', 'cryptoLib/aes-cbc', 'cryptoLib/rsa', 'cryptoLib/crypt
// check if key exists
if (!args.storedKeypair) {
// generate keys, encrypt and persist if none exists
generateKeypair(pbkdf2);
generateKeypair(derivedKey);
} else {
// decrypt key
decryptKeypair(args.storedKeypair, pbkdf2);
decryptKeypair(args.storedKeypair, derivedKey);
}
});
function generateKeypair(pbkdf2) {
function generateKeypair(derivedKey) {
// generate RSA keypair in web worker
rsa.generateKeypair(self.rsaKeySize, function(err, generatedKeypair) {
if (err) {
@ -53,7 +53,7 @@ define(['cryptoLib/util', 'cryptoLib/aes-cbc', 'cryptoLib/rsa', 'cryptoLib/crypt
// encrypt keypair
var iv = util.random(self.ivSize);
var encryptedPrivateKey = aes.encrypt(generatedKeypair.privkeyPem, pbkdf2, iv);
var encryptedPrivateKey = aes.encrypt(generatedKeypair.privkeyPem, derivedKey, iv);
// new encrypted keypair object
var newKeypair = {
@ -75,7 +75,7 @@ define(['cryptoLib/util', 'cryptoLib/aes-cbc', 'cryptoLib/rsa', 'cryptoLib/crypt
});
}
function decryptKeypair(storedKeypair, pbkdf2) {
function decryptKeypair(storedKeypair, derivedKey) {
var decryptedPrivateKey;
// validate input
@ -86,10 +86,10 @@ define(['cryptoLib/util', 'cryptoLib/aes-cbc', 'cryptoLib/rsa', 'cryptoLib/crypt
return;
}
// try to decrypt with pbkdf2
// try to decrypt with derivedKey
try {
var prK = storedKeypair.privateKey;
decryptedPrivateKey = aes.decrypt(prK.encryptedKey, pbkdf2, prK.iv);
decryptedPrivateKey = aes.decrypt(prK.encryptedKey, derivedKey, prK.iv);
} catch (ex) {
callback({
errMsg: 'Wrong password!'
@ -111,7 +111,6 @@ define(['cryptoLib/util', 'cryptoLib/aes-cbc', 'cryptoLib/rsa', 'cryptoLib/crypt
password: password,
keySize: keySize
}, callback, function() {
var pbkdf2 = new app.crypto.PBKDF2();
return pbkdf2.getKey(password, keySize);
});
};

View File

@ -1,16 +1,18 @@
/**
* A Wrapper for Forge's PBKDF2 function
*/
app.crypto.PBKDF2 = function() {
define(['forge'], function(forge) {
'use strict';
var self = {};
/**
* PBKDF2-HMAC-SHA1 key derivation with a constant salt and 1000 iterations
* @param password [String] The password in UTF8
* @param keySize [Number] The key size in bits
* @return [String] The base64 encoded key
*/
this.getKey = function(password, keySize) {
self.getKey = function(password, keySize) {
var salt = forge.util.decode64("vbhmLjC+Ub6MSbhS6/CkOwxB25wvwRkSLP2DzDtYb+4=");
var key = forge.pkcs5.pbkdf2(password, salt, 1000, keySize / 8);
var keyBase64 = forge.util.encode64(key);
@ -18,4 +20,5 @@ app.crypto.PBKDF2 = function() {
return keyBase64;
};
};
return self;
});

View File

@ -1,4 +1,4 @@
(function() {
define(['backbone'], function(Backbone) {
'use strict';
app.model.Email = Backbone.Model.extend({
@ -24,4 +24,4 @@
});
}());
});

View File

@ -1,4 +1,4 @@
define(['cryptoLib/util'], function(util) {
define(['cryptoLib/util', 'js/model/email-model'], function(util) {
'use strict';
var self = {};

View File

@ -1,108 +1,109 @@
'use strict';
define(['js/crypto/crypto', 'cryptoLib/util', 'test/test-data'], function(crypto, util, testData) {
'use strict';
module("Crypto Api");
module("Crypto Api");
var crypto_test = {
user: 'crypto_test@example.com',
password: 'Password',
keySize: 128,
ivSize: 128,
rsaKeySize: 1024
};
var cryptoTest = {
user: 'crypto_test@example.com',
password: 'Password',
keySize: 128,
ivSize: 128,
rsaKeySize: 1024
};
asyncTest("Init without keypair", 4, function() {
// init dependencies
crypto_test.util = new cryptoLib.Util(window, uuid);
crypto_test.crypto = new app.crypto.Crypto(window, crypto_test.util);
ok(crypto_test.crypto, 'Crypto');
asyncTest("Init without keypair", 4, function() {
// init dependencies
ok(crypto, 'Crypto');
// test without passing keys
crypto_test.crypto.init({
emailAddress: crypto_test.user,
password: crypto_test.password,
keySize: crypto_test.keySize,
rsaKeySize: crypto_test.rsaKeySize
}, function(err, generatedKeypair) {
ok(!err && generatedKeypair, 'Init crypto without keypair input');
var pk = generatedKeypair.publicKey;
ok(pk._id && pk.userId, 'Key ID: ' + pk._id);
ok(pk.publicKey.indexOf('-----BEGIN PUBLIC KEY-----') === 0, pk.publicKey);
crypto_test.generatedKeypair = generatedKeypair;
start();
});
});
asyncTest("Init with keypair", 1, function() {
// test with passing keypair
crypto_test.crypto.init({
emailAddress: crypto_test.user,
password: crypto_test.password,
keySize: crypto_test.keySize,
rsaKeySize: crypto_test.rsaKeySize,
storedKeypair: crypto_test.generatedKeypair
}, function(err, generatedKeypair) {
ok(!err && !generatedKeypair, 'Init crypto with keypair input');
start();
});
});
asyncTest("PBKDF2 (Async/Worker)", 2, function() {
crypto_test.crypto.deriveKey(crypto_test.password, crypto_test.keySize, function(err, key) {
ok(!err);
equal(crypto_test.util.base642Str(key).length * 8, crypto_test.keySize, 'Keysize ' + crypto_test.keySize);
start();
});
});
asyncTest("AES en/decrypt (Async/Worker)", 4, function() {
var secret = 'Big secret';
var key = crypto_test.util.random(crypto_test.keySize);
var iv = crypto_test.util.random(crypto_test.ivSize);
crypto_test.crypto.aesEncrypt(secret, key, iv, function(err, ciphertext) {
ok(!err);
ok(ciphertext, 'Encrypt item');
crypto_test.crypto.aesDecrypt(ciphertext, key, iv, function(err, decrypted) {
ok(!err);
equal(decrypted, secret, 'Decrypt item');
// test without passing keys
crypto.init({
emailAddress: cryptoTest.user,
password: cryptoTest.password,
keySize: cryptoTest.keySize,
rsaKeySize: cryptoTest.rsaKeySize
}, function(err, generatedKeypair) {
ok(!err && generatedKeypair, 'Init crypto without keypair input');
var pk = generatedKeypair.publicKey;
ok(pk._id && pk.userId, 'Key ID: ' + pk._id);
ok(pk.publicKey.indexOf('-----BEGIN PUBLIC KEY-----') === 0, pk.publicKey);
cryptoTest.generatedKeypair = generatedKeypair;
start();
});
});
});
asyncTest("AES/RSA encrypt batch for User (Async/Worker)", 2, function() {
// generate test data
var collection, td = new TestData();
asyncTest("Init with keypair", 1, function() {
// test with passing keypair
crypto.init({
emailAddress: cryptoTest.user,
password: cryptoTest.password,
keySize: cryptoTest.keySize,
rsaKeySize: cryptoTest.rsaKeySize,
storedKeypair: cryptoTest.generatedKeypair
}, function(err, generatedKeypair) {
ok(!err && !generatedKeypair, 'Init crypto with keypair input');
collection = td.getEmailCollection(10);
crypto_test.list = collection.toJSON();
var receiverPubkeys = [crypto_test.generatedKeypair.publicKey];
crypto_test.crypto.encryptListForUser(crypto_test.list, receiverPubkeys, function(err, encryptedList) {
ok(!err && encryptedList, 'Encrypt list for user');
equal(encryptedList.length, crypto_test.list.length, 'Length of list');
crypto_test.encryptedList = encryptedList;
start();
start();
});
});
});
asyncTest("AES/RSA decrypt batch for User (Async/Worker)", 3, function() {
asyncTest("PBKDF2 (Async/Worker)", 2, function() {
crypto.deriveKey(cryptoTest.password, cryptoTest.keySize, function(err, key) {
ok(!err);
equal(util.base642Str(key).length * 8, cryptoTest.keySize, 'Keysize ' + cryptoTest.keySize);
var senderPubkeys = [crypto_test.generatedKeypair.publicKey];
crypto_test.crypto.decryptListForUser(crypto_test.encryptedList, senderPubkeys, function(err, decryptedList) {
ok(!err && decryptedList, 'Decrypt list');
equal(decryptedList.length, crypto_test.list.length, 'Length of list');
deepEqual(decryptedList, crypto_test.list, 'Decrypted list is correct');
start();
start();
});
});
asyncTest("AES en/decrypt (Async/Worker)", 4, function() {
var secret = 'Big secret';
var key = util.random(cryptoTest.keySize);
var iv = util.random(cryptoTest.ivSize);
crypto.aesEncrypt(secret, key, iv, function(err, ciphertext) {
ok(!err);
ok(ciphertext, 'Encrypt item');
crypto.aesDecrypt(ciphertext, key, iv, function(err, decrypted) {
ok(!err);
equal(decrypted, secret, 'Decrypt item');
start();
});
});
});
asyncTest("AES/RSA encrypt batch for User (Async/Worker)", 2, function() {
// generate test data
var collection;
collection = testData.getEmailCollection(10);
cryptoTest.list = collection.toJSON();
var receiverPubkeys = [cryptoTest.generatedKeypair.publicKey];
crypto.encryptListForUser(cryptoTest.list, receiverPubkeys, function(err, encryptedList) {
ok(!err && encryptedList, 'Encrypt list for user');
equal(encryptedList.length, cryptoTest.list.length, 'Length of list');
cryptoTest.encryptedList = encryptedList;
start();
});
});
asyncTest("AES/RSA decrypt batch for User (Async/Worker)", 3, function() {
var senderPubkeys = [cryptoTest.generatedKeypair.publicKey];
crypto.decryptListForUser(cryptoTest.encryptedList, senderPubkeys, function(err, decryptedList) {
ok(!err && decryptedList, 'Decrypt list');
equal(decryptedList.length, cryptoTest.list.length, 'Length of list');
deepEqual(decryptedList, cryptoTest.list, 'Decrypted list is correct');
start();
});
});
});

View File

@ -25,7 +25,8 @@ function startTests() {
'test/unit/aes-test',
'test/unit/rsa-test',
'test/unit/lawnchair-dao-test',
'test/unit/keychain-dao-test'
'test/unit/keychain-dao-test',
'test/unit/crypto-test'
], function() {
//Tests loaded, run tests
QUnit.start();