From f2a14ad65b76779a36a4f56fdb1c2b995db39348 Mon Sep 17 00:00:00 2001 From: Tankred Hase Date: Wed, 5 Jun 2013 01:47:28 +0200 Subject: [PATCH] refactoring of crypto worker code and lots of cleanup --- .jshintrc | 3 +- src/js/crypto/crypto-batch.js | 2 +- src/js/crypto/crypto.js | 198 ++++++++------------ src/js/crypto/pgp.js | 341 ---------------------------------- src/js/crypto/util.js | 10 +- test/pgp-test.js | 70 ------- test/unit/crypto-test.js | 17 +- test/unit/index.html | 1 + 8 files changed, 99 insertions(+), 543 deletions(-) delete mode 100644 src/js/crypto/pgp.js delete mode 100644 test/pgp-test.js diff --git a/.jshintrc b/.jshintrc index 51d130c..dfd5509 100644 --- a/.jshintrc +++ b/.jshintrc @@ -4,7 +4,6 @@ "jquery": true, "node": true, "browser": true, - "camelcase": true, "nonew": true, "curly": true, "eqeqeq": true, @@ -32,10 +31,12 @@ "describe", "it", "chai", + "asyncTest", "ok", "equal", "deepEqual", "start", + "TestData", "chrome" ], diff --git a/src/js/crypto/crypto-batch.js b/src/js/crypto/crypto-batch.js index c01cf97..c09e077 100644 --- a/src/js/crypto/crypto-batch.js +++ b/src/js/crypto/crypto-batch.js @@ -7,7 +7,7 @@ var CryptoBatch = function(aes, rsa, util, _) { /** - * Encrypt and sign a an item using AES and RSA + * Encrypt and sign an item using AES and RSA * @param i [Object] The item to encrypt * @param receiverPubkey [String] The public key used to encrypt * @param senderKeyId [String] The sender's private key ID used to sign diff --git a/src/js/crypto/crypto.js b/src/js/crypto/crypto.js index ffc0114..97fa0b6 100644 --- a/src/js/crypto/crypto.js +++ b/src/js/crypto/crypto.js @@ -29,7 +29,11 @@ app.crypto.Crypto = function(window, util) { self.rsaKeySize = args.rsaKeySize; // derive PBKDF2 from password in web worker thread - self.deriveKey(args.password, self.keySize, function(pbkdf2) { + self.deriveKey(args.password, self.keySize, function(err, pbkdf2) { + if (err) { + callback(err); + return; + } // check if key exists if (!args.storedKeypair) { @@ -106,29 +110,13 @@ app.crypto.Crypto = function(window, util) { * Do PBKDF2 key derivation in a WebWorker thread */ this.deriveKey = function(password, keySize, callback) { - // check for WebWorker support - if (window.Worker) { - - // init webworker thread - var worker = new Worker(app.config.workerPath + '/crypto/pbkdf2-worker.js'); - - worker.onmessage = function(e) { - // return derived key from the worker - callback(e.data); - }; - - // send plaintext data to the worker - worker.postMessage({ - password: password, - keySize: keySize - }); - - } else { - // no WebWorker support... do synchronous call + startWorker('/crypto/pbkdf2-worker.js', { + password: password, + keySize: keySize + }, callback, function() { var pbkdf2 = new app.crypto.PBKDF2(); - var key = pbkdf2.getKey(password, keySize); - callback(key); - } + return pbkdf2.getKey(password, keySize); + }); }; // @@ -136,43 +124,29 @@ app.crypto.Crypto = function(window, util) { // this.aesEncrypt = function(plaintext, key, iv, callback) { - if (window.Worker) { + var self = this; - var worker = new Worker(app.config.workerPath + '/crypto/aes-worker.js'); - worker.onmessage = function(e) { - callback(e.data); - }; - worker.postMessage({ - type: 'encrypt', - plaintext: plaintext, - key: key, - iv: iv - }); - - } else { - var ct = this.aesEncryptSync(plaintext, key, iv); - callback(ct); - } + startWorker('/crypto/aes-worker.js', { + type: 'encrypt', + plaintext: plaintext, + key: key, + iv: iv + }, callback, function() { + return self.aesEncryptSync(plaintext, key, iv); + }); }; this.aesDecrypt = function(ciphertext, key, iv, callback) { - if (window.Worker) { + var self = this; - var worker = new Worker(app.config.workerPath + '/crypto/aes-worker.js'); - worker.onmessage = function(e) { - callback(e.data); - }; - worker.postMessage({ - type: 'decrypt', - ciphertext: ciphertext, - key: key, - iv: iv - }); - - } else { - var pt = this.aesDecryptSync(ciphertext, key, iv); - callback(pt); - } + startWorker('/crypto/aes-worker.js', { + type: 'decrypt', + ciphertext: ciphertext, + key: key, + iv: iv + }, callback, function() { + return self.aesDecryptSync(ciphertext, key, iv); + }); }; this.aesEncryptSync = function(plaintext, key, iv) { @@ -188,41 +162,23 @@ app.crypto.Crypto = function(window, util) { // this.aesEncryptList = function(list, callback) { - if (window.Worker) { - - var worker = new Worker(app.config.workerPath + '/crypto/aes-batch-worker.js'); - worker.onmessage = function(e) { - callback(e.data); - }; - worker.postMessage({ - type: 'encrypt', - list: list - }); - - } else { + startWorker('/crypto/aes-batch-worker.js', { + type: 'encrypt', + list: list + }, callback, function() { var batch = new cryptoLib.CryptoBatch(aes); - var encryptedList = batch.encryptList(list); - callback(encryptedList); - } + return batch.encryptList(list); + }); }; this.aesDecryptList = function(list, callback) { - if (window.Worker) { - - var worker = new Worker(app.config.workerPath + '/crypto/aes-batch-worker.js'); - worker.onmessage = function(e) { - callback(e.data); - }; - worker.postMessage({ - type: 'decrypt', - list: list - }); - - } else { + startWorker('/crypto/aes-batch-worker.js', { + type: 'decrypt', + list: list + }, callback, function() { var batch = new cryptoLib.CryptoBatch(aes); - var decryptedList = batch.decryptList(list); - callback(decryptedList); - } + return batch.decryptList(list); + }); }; // @@ -258,24 +214,15 @@ app.crypto.Crypto = function(window, util) { envelopes.push(envelope); }); - if (window.Worker) { - - var worker = new Worker(app.config.workerPath + '/crypto/crypto-batch-worker.js'); - worker.onmessage = function(e) { - callback(null, e.data); - }; - worker.postMessage({ - type: 'encrypt', - list: envelopes, - senderPrivkey: senderPrivkey, - receiverPubkeys: receiverPubkeys - }); - - } else { + startWorker('/crypto/crypto-batch-worker.js', { + type: 'encrypt', + list: envelopes, + senderPrivkey: senderPrivkey, + receiverPubkeys: receiverPubkeys + }, callback, function() { var batch = new cryptoLib.CryptoBatch(aes, rsa, util, _); - var encryptedList = batch.encryptListForUser(envelopes, receiverPubkeys, senderPrivkey); - callback(null, encryptedList); - } + return batch.encryptListForUser(envelopes, receiverPubkeys, senderPrivkey); + }); }; this.decryptListForUser = function(list, senderPubkeys, callback) { @@ -292,24 +239,37 @@ app.crypto.Crypto = function(window, util) { privateKey: keypair.privkeyPem }; - if (window.Worker) { - - var worker = new Worker(app.config.workerPath + '/crypto/crypto-batch-worker.js'); - worker.onmessage = function(e) { - callback(null, e.data); - }; - worker.postMessage({ - type: 'decrypt', - list: list, - receiverPrivkey: receiverPrivkey, - senderPubkeys: senderPubkeys - }); - - } else { + startWorker('/crypto/crypto-batch-worker.js', { + type: 'decrypt', + list: list, + receiverPrivkey: receiverPrivkey, + senderPubkeys: senderPubkeys + }, callback, function() { var batch = new cryptoLib.CryptoBatch(aes, rsa, util, _); - var decryptedList = batch.decryptListForUser(list, senderPubkeys, receiverPrivkey); - callback(null, decryptedList); - } + return batch.decryptListForUser(list, senderPubkeys, receiverPrivkey); + }); }; + function startWorker(script, args, callback, noWorker) { + // check for WebWorker support + if (window.Worker) { + + // 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); + + } else { + // no WebWorker support... do synchronous call + var result = noWorker(); + callback(null, result); + } + } + }; \ No newline at end of file diff --git a/src/js/crypto/pgp.js b/src/js/crypto/pgp.js deleted file mode 100644 index 89afa28..0000000 --- a/src/js/crypto/pgp.js +++ /dev/null @@ -1,341 +0,0 @@ -/** - * A wrapper for asymmetric OpenPGP encryption logic - */ -app.crypto.PGP = function(window, openpgp, util, server) { - 'use strict'; - - var self = this, - privateKey, // user's private key - publicKey, // user's public key - passphrase; // user's passphrase used for decryption - - openpgp.init(); // initialize OpenPGP.js - - // - // Key management - // - - /** - * Check if user already has a public key on the server and if not, - * generate a new keypait for the user - */ - self.initKeyPair = function(loginInfo, callback, displayCallback, finishCallback) { - // check if user already has a keypair in local storage - if (loginInfo.publicKeyId) { - // decode base 64 key ID - var keyId = window.atob(loginInfo.publicKeyId); - // read the user's keys from local storage - callback(keyId); - - } else { - // user has no key pair yet - displayCallback(function() { - // generate new key pair with 2048 bit RSA keys - var keys = self.generateKeys(2048); - var keyId = keys.privateKey.getKeyId(); - - // display finish - finishCallback(keyId); - // read the user's keys from local storage - callback(keyId); - }); - } - }; - - /** - * Generate a key pair for the user - * @param numBits [int] number of bits for the key creation. (should be 1024+, generally) - * @email [string] user's email address - * @pass [string] a passphrase used to protect the private key - */ - self.generateKeys = function(numBits) { - // check passphrase - if (!passphrase && passphrase !== '') { - throw 'No passphrase set!'; - } - - var userId = 'SafeWith.me User '; - var keys = openpgp.generate_key_pair(1, numBits, userId, passphrase); // keytype 1=RSA - - self.importKeys(keys.publicKeyArmored, keys.privateKeyArmored); - - return keys; - }; - - /** - * Import the users key into the HTML5 local storage - */ - self.importKeys = function(publicKeyArmored, privateKeyArmored) { - // check passphrase - if (!passphrase && passphrase !== '') { - throw 'No passphrase set!'; - } - - // store keys in html5 local storage - openpgp.keyring.importPrivateKey(privateKeyArmored, passphrase); - openpgp.keyring.importPublicKey(publicKeyArmored); - openpgp.keyring.store(); - }; - - /** - * Export the keys by using the HTML5 FileWriter - */ - self.exportKeys = function(callback) { - // build blob - var buf = util.binStr2ArrBuf(publicKey.armored + privateKey.armored); - var blob = util.arrBuf2Blob(buf, 'text/plain'); - // create url - util.createUrl(undefined, blob, callback); - }; - - /** - * Read the users keys from the browser's HTML5 local storage - * @email [string] user's email address - * @keyId [string] the public key ID in unicode (not base 64) - */ - self.readKeys = function(keyId, callback, errorCallback) { - // read keys from keyring (local storage) - var privKeyQuery = openpgp.keyring.getPrivateKeyForKeyId(keyId)[0]; - if (privKeyQuery) { - privateKey = privKeyQuery.key; - } - publicKey = openpgp.keyring.getPublicKeysForKeyId(keyId)[0]; - - // check keys - if (!publicKey || !privateKey || (publicKey.keyId !== privateKey.keyId)) { - // no amtching keys found in the key store - return false; - } - - // read passphrase from local storage if no passphrase is specified - if (!passphrase && passphrase !== '') { - passphrase = window.sessionStorage.getItem(window.btoa(keyId) + 'Passphrase'); - } - - // check passphrase - if (!passphrase && passphrase !== '') { - return false; - } - // do test encrypt/decrypt to verify passphrase - try { - var testCt = self.asymmetricEncrypt('test'); - self.asymmetricDecrypt(testCt); - } catch (e) { - return false; - } - - return true; - }; - - /** - * Generate a new key pair for the user and persist the public key on the server - */ - self.syncKeysToServer = function(email, callback) { - // base64 encode key ID - var keyId = publicKey.keyId; - var encodedKeyId = window.btoa(keyId); - var pubKey = { - keyId: encodedKeyId, - ownerEmail: email, - asciiArmored: publicKey.armored - }; - var privKey = { - keyId: encodedKeyId, - ownerEmail: email, - asciiArmored: privateKey.armored - }; - - var jsonPublicKey = JSON.stringify(pubKey); - var jsonPrivateKey = JSON.stringify(privKey); - - // first upload public key - server.xhr({ - type: 'POST', - uri: '/ws/publicKeys', - contentType: 'application/json', - expected: 201, - body: jsonPublicKey, - success: function(resp) { - uploadPrivateKeys(); - }, - error: function(e) { - // if server is not available, just continue - // and read the user's keys from local storage - console.log('Server unavailable: keys were not synced to server!'); - callback(keyId); - } - }); - - // then upload private key - - function uploadPrivateKeys() { - server.xhr({ - type: 'POST', - uri: '/ws/privateKeys', - contentType: 'application/json', - expected: 201, - body: jsonPrivateKey, - success: function(resp) { - // read the user's keys from local storage - callback(keyId); - } - }); - } - }; - - /** - * Get the keypair from the server and import them into localstorage - */ - self.fetchKeys = function(email, keyId, callback, errCallback) { - var base64Key = window.btoa(keyId); - var encodedKeyId = encodeURIComponent(base64Key); - - // get public key - server.xhr({ - type: 'GET', - uri: '/ws/publicKeys?keyId=' + encodedKeyId, - expected: 200, - success: function(pubKey) { - getPrivateKey(pubKey); - }, - error: function(e) { - // if server is not available, just continue - console.log('Server unavailable: keys could not be fetched from server!'); - errCallback(e); - } - }); - - // get private key - - function getPrivateKey(pubKey) { - server.xhr({ - type: 'GET', - uri: '/ws/privateKeys?keyId=' + encodedKeyId, - expected: 200, - success: function(privKey) { - // import keys - self.importKeys(pubKey.asciiArmored, privKey.asciiArmored, email); - callback({ - privateKey: privKey, - publicKey: pubKey - }); - } - }); - } - }; - - /** - * Get the current user's private key - */ - self.getPrivateKey = function() { - if (!privateKey) { - return undefined; - } - return privateKey.armored; - }; - - /** - * Get the current user's public key - */ - self.getPublicKey = function() { - if (!publicKey) { - return undefined; - } - return publicKey.armored; - }; - - /** - * Get the current user's base64 encoded public key ID - */ - self.getPublicKeyIdBase64 = function() { - return window.btoa(publicKey.keyId); - }; - - /** - * Get the user's passphrase for decrypting their private key - */ - self.setPassphrase = function(pass) { - passphrase = pass; - }; - - /** - * Store the passphrase for the current session - */ - self.rememberPassphrase = function(keyId) { - var base64KeyId = window.btoa(keyId); - window.sessionStorage.setItem(base64KeyId + 'Passphrase', passphrase); - }; - - // - // Asymmetric crypto - // - - /** - * Encrypt a string - * @param customPubKey [PublicKey] (optional) another user's public key for sharing - */ - self.asymmetricEncrypt = function(plaintext, customPubKey) { - var pub_key = null; - if (customPubKey) { - // use a custom set public for e.g. or sharing - pub_key = openpgp.read_publicKey(customPubKey); - } else { - // use the user's local public key - pub_key = openpgp.read_publicKey(publicKey.armored); - } - - var ciphertext = openpgp.write_encrypted_message(pub_key, window.btoa(plaintext)); - return ciphertext; - }; - - /** - * Decrypt a string - */ - self.asymmetricDecrypt = function(ciphertext) { - var priv_key = openpgp.read_privateKey(privateKey.armored); - - var msg = openpgp.read_message(ciphertext); - var keymat = null; - var sesskey = null; - - // Find the private (sub)key for the session key of the message - for (var i = 0; i < msg[0].sessionKeys.length; i++) { - if (priv_key[0].privateKeyPacket.publicKey.getKeyId() == msg[0].sessionKeys[i].keyId.bytes) { - keymat = { - key: priv_key[0], - keymaterial: priv_key[0].privateKeyPacket - }; - sesskey = msg[0].sessionKeys[i]; - break; - } - for (var j = 0; j < priv_key[0].subKeys.length; j++) { - if (priv_key[0].subKeys[j].publicKey.getKeyId() == msg[0].sessionKeys[i].keyId.bytes) { - keymat = { - key: priv_key[0], - keymaterial: priv_key[0].subKeys[j] - }; - sesskey = msg[0].sessionKeys[i]; - break; - } - } - } - if (keymat !== null) { - if (!keymat.keymaterial.decryptSecretMPIs(passphrase)) { - throw "Passphrase for secrect key was incorrect!"; - } - - var decrypted = msg[0].decrypt(keymat, sesskey); - return window.atob(decrypted); - - } else { - throw "No private key found!"; - } - }; - -}; - -/** - * This function needs to be implemented, since it is used by the openpgp utils - */ - -function showMessages(str) {} \ No newline at end of file diff --git a/src/js/crypto/util.js b/src/js/crypto/util.js index f9d454c..1ae0d6c 100644 --- a/src/js/crypto/util.js +++ b/src/js/crypto/util.js @@ -54,23 +54,23 @@ this.formatDate = function(date) { var year = "" + date.getFullYear(); var month = "" + (date.getMonth() + 1); - if (month.length == 1) { + if (month.length === 1) { month = "0" + month; } var day = "" + date.getDate(); - if (day.length == 1) { + if (day.length === 1) { day = "0" + day; } var hour = "" + date.getHours(); - if (hour.length == 1) { + if (hour.length === 1) { hour = "0" + hour; } var minute = "" + date.getMinutes(); - if (minute.length == 1) { + if (minute.length === 1) { minute = "0" + minute; } var second = "" + date.getSeconds(); - if (second.length == 1) { + if (second.length === 1) { second = "0" + second; } return year + "-" + month + "-" + day + " " + hour + ":" + minute + ":" + second; diff --git a/test/pgp-test.js b/test/pgp-test.js deleted file mode 100644 index 91c37a2..0000000 --- a/test/pgp-test.js +++ /dev/null @@ -1,70 +0,0 @@ -module("PGP Crypto"); - -var pgp_test = { - keyID: null, - keySize: 1024 -}; - -asyncTest("Init", 1, function() { - // init dependencies - pgp_test.util = new app.crypto.Util(window); - pgp_test.crypto = new app.crypto.PGP(window, openpgp, util, null); - pgp_test.crypto.setPassphrase('asdf'); - ok(pgp_test.crypto, 'PGP crypto'); - - pgp_test.helperEncrDecr = function(crypto, keyId, plaintext) { - if (!crypto.getPublicKey()) { - crypto.readKeys(keyId); - } - - console.log('plaintext size [bytes]: ' + plaintext.length); - - var startTime = (new Date).getTime(); - var ct = crypto.asymmetricEncrypt(plaintext); - var diff = (new Date).getTime() - startTime; - - console.log('Time taken for encryption [ms]: ' + diff); - ok(ct, "ciphertext: see console output for benchmark"); - console.log('ciphertext size [bytes]: ' + ct.length); - - var decrStart = (new Date).getTime(); - var pt = crypto.asymmetricDecrypt(ct); - var decrDiff = (new Date).getTime() - decrStart; - - console.log('Time taken for decryption [ms]: ' + decrDiff); - ok(pt, "decrypted: see console output for benchmark"); - equal(pt, plaintext, "Decrypted should be the same as the plaintext"); - }; - - start(); -}); - -asyncTest("Generate keypair, De/Encrypt", 7, function() { - var startTime = (new Date).getTime(); - var keys = pgp_test.crypto.generateKeys(pgp_test.keySize); - var diff = (new Date).getTime() - startTime; - - pgp_test.keyID = keys.privateKey.getKeyId(); - pgp_test.crypto.readKeys(pgp_test.keyID); - - console.log('Time taken for key generation [ms]: ' + diff + ' (' + pgp_test.keySize + ' bit RSA keypair)'); - ok(pgp_test.crypto.getPrivateKey()); - ok(pgp_test.crypto.getPrivateKey().indexOf('-----BEGIN PGP PRIVATE KEY BLOCK-----') === 0); - ok(pgp_test.crypto.getPublicKey()); - ok(pgp_test.crypto.getPublicKey().indexOf('-----BEGIN PGP PUBLIC KEY BLOCK-----') === 0); - - pgp_test.helperEncrDecr(pgp_test.crypto, pgp_test.keyID, '06a9214036b8a15b512e03d534120006'); - - start(); - - // pgp_test.crypto.exportKeys(function(url) { - // ok(url, 'export url'); - // - // $.get(url, function(data) { - // ok(data.indexOf('-----BEGIN PGP PUBLIC KEY BLOCK-----') !== -1, 'exportd public key'); - // ok(data.indexOf('-----END PGP PRIVATE KEY BLOCK-----') !== -1, 'export private key'); - // - // start(); - // }); - // }); -}); \ No newline at end of file diff --git a/test/unit/crypto-test.js b/test/unit/crypto-test.js index 7ef20b4..11b17b8 100644 --- a/test/unit/crypto-test.js +++ b/test/unit/crypto-test.js @@ -1,3 +1,5 @@ +'use strict'; + module("Crypto Api"); var crypto_test = { @@ -40,30 +42,33 @@ asyncTest("Init with keypair", 1, function() { rsaKeySize: crypto_test.rsaKeySize, storedKeypair: crypto_test.generatedKeypair }, function(err, generatedKeypair) { - ok(!err, 'Init crypto with keypair input'); + ok(!err && !generatedKeypair, 'Init crypto with keypair input'); start(); }); }); -asyncTest("PBKDF2 (Async/Worker)", 1, function() { - crypto_test.crypto.deriveKey(crypto_test.password, crypto_test.keySize, function(key) { +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)", 2, function() { +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(ciphertext) { + crypto_test.crypto.aesEncrypt(secret, key, iv, function(err, ciphertext) { + ok(!err); ok(ciphertext, 'Encrypt item'); - crypto_test.crypto.aesDecrypt(ciphertext, key, iv, function(decrypted) { + crypto_test.crypto.aesDecrypt(ciphertext, key, iv, function(err, decrypted) { + ok(!err); equal(decrypted, secret, 'Decrypt item'); start(); diff --git a/test/unit/index.html b/test/unit/index.html index 1067282..e00046e 100644 --- a/test/unit/index.html +++ b/test/unit/index.html @@ -12,6 +12,7 @@