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

View File

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

View File

@ -1,4 +1,4 @@
(function() { define(['backbone'], function(Backbone) {
'use strict'; 'use strict';
app.model.Email = Backbone.Model.extend({ 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'; 'use strict';
var self = {}; 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 = { var cryptoTest = {
user: 'crypto_test@example.com', user: 'crypto_test@example.com',
password: 'Password', password: 'Password',
keySize: 128, keySize: 128,
ivSize: 128, ivSize: 128,
rsaKeySize: 1024 rsaKeySize: 1024
}; };
asyncTest("Init without keypair", 4, function() { asyncTest("Init without keypair", 4, function() {
// init dependencies // init dependencies
crypto_test.util = new cryptoLib.Util(window, uuid); ok(crypto, 'Crypto');
crypto_test.crypto = new app.crypto.Crypto(window, crypto_test.util);
ok(crypto_test.crypto, 'Crypto');
// test without passing keys // test without passing keys
crypto_test.crypto.init({ crypto.init({
emailAddress: crypto_test.user, emailAddress: cryptoTest.user,
password: crypto_test.password, password: cryptoTest.password,
keySize: crypto_test.keySize, keySize: cryptoTest.keySize,
rsaKeySize: crypto_test.rsaKeySize rsaKeySize: cryptoTest.rsaKeySize
}, function(err, generatedKeypair) { }, function(err, generatedKeypair) {
ok(!err && generatedKeypair, 'Init crypto without keypair input'); ok(!err && generatedKeypair, 'Init crypto without keypair input');
var pk = generatedKeypair.publicKey; var pk = generatedKeypair.publicKey;
ok(pk._id && pk.userId, 'Key ID: ' + pk._id); ok(pk._id && pk.userId, 'Key ID: ' + pk._id);
ok(pk.publicKey.indexOf('-----BEGIN PUBLIC KEY-----') === 0, pk.publicKey); ok(pk.publicKey.indexOf('-----BEGIN PUBLIC KEY-----') === 0, pk.publicKey);
crypto_test.generatedKeypair = generatedKeypair; cryptoTest.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');
start(); start();
}); });
}); });
});
asyncTest("AES/RSA encrypt batch for User (Async/Worker)", 2, function() { asyncTest("Init with keypair", 1, function() {
// generate test data // test with passing keypair
var collection, td = new TestData(); 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); start();
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();
}); });
});
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]; start();
});
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();
}); });
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/aes-test',
'test/unit/rsa-test', 'test/unit/rsa-test',
'test/unit/lawnchair-dao-test', 'test/unit/lawnchair-dao-test',
'test/unit/keychain-dao-test' 'test/unit/keychain-dao-test',
'test/unit/crypto-test'
], function() { ], function() {
//Tests loaded, run tests //Tests loaded, run tests
QUnit.start(); QUnit.start();