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) {
|
2013-04-01 18:12:15 -04:00
|
|
|
'use strict';
|
|
|
|
|
2013-04-12 06:27:47 -04:00
|
|
|
var symmetricUserKey = null, // the user's secret key used to encrypt item-keys
|
|
|
|
aes = new app.crypto.AesCCM(sjcl); // use authenticated AES-CCM mode by default
|
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-04-02 09:02:57 -04:00
|
|
|
this.init = function(emailAddress, password, keySize, ivSize, callback) {
|
|
|
|
this.emailAddress = emailAddress;
|
|
|
|
this.keySize = keySize;
|
|
|
|
this.ivSize = ivSize;
|
|
|
|
|
|
|
|
// derive PBKDF2 from password in web worker thread
|
|
|
|
this.deriveKey(password, keySize, function(pbkdf2) {
|
|
|
|
|
|
|
|
// fetch user's encrypted secret key from keychain/storage
|
|
|
|
var keyStore = new app.dao.LocalStorageDAO(window);
|
|
|
|
var storageId = emailAddress + '_encryptedSymmetricKey';
|
2013-04-18 14:34:02 -04:00
|
|
|
var storedKey = keyStore.read(storageId);
|
2013-04-02 09:02:57 -04:00
|
|
|
|
|
|
|
// check if key exists
|
2013-04-18 14:34:02 -04:00
|
|
|
if (!storedKey) {
|
2013-04-02 09:02:57 -04:00
|
|
|
// generate key, encrypt and persist if none exists
|
|
|
|
symmetricUserKey = util.random(keySize);
|
|
|
|
var iv = util.random(ivSize);
|
|
|
|
var key = aes.encrypt(symmetricUserKey, pbkdf2, iv);
|
|
|
|
keyStore.persist(storageId, {
|
2013-04-18 14:34:02 -04:00
|
|
|
_id: util.UUID(),
|
|
|
|
userId: emailAddress,
|
|
|
|
encryptedKey: key,
|
|
|
|
keyIV: iv
|
2013-04-01 18:12:15 -04:00
|
|
|
});
|
|
|
|
} else {
|
2013-04-02 09:02:57 -04:00
|
|
|
// decrypt key
|
2013-04-18 14:34:02 -04:00
|
|
|
symmetricUserKey = aes.decrypt(storedKey.encryptedKey, pbkdf2, storedKey.keyIV);
|
2013-03-13 11:58:46 -04:00
|
|
|
}
|
2013-04-01 18:12:15 -04:00
|
|
|
|
2013-04-02 09:02:57 -04:00
|
|
|
callback();
|
|
|
|
});
|
|
|
|
};
|
2013-04-01 18:12:15 -04:00
|
|
|
|
2013-04-02 09:02:57 -04:00
|
|
|
/**
|
|
|
|
* 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');
|
|
|
|
|
2013-04-10 11:14:19 -04:00
|
|
|
worker.onmessage = function(e) {
|
2013-04-02 09:02:57 -04:00
|
|
|
// return derived key from the worker
|
|
|
|
callback(e.data);
|
2013-04-10 11:14:19 -04:00
|
|
|
};
|
2013-04-02 09:02:57 -04:00
|
|
|
|
|
|
|
// send plaintext data to the worker
|
|
|
|
worker.postMessage({
|
|
|
|
password: password,
|
|
|
|
keySize: keySize
|
|
|
|
});
|
2013-04-01 18:12:15 -04:00
|
|
|
|
2013-04-02 09:02:57 -04:00
|
|
|
} else {
|
|
|
|
// no WebWorker support... do synchronous call
|
|
|
|
var pbkdf2 = new app.crypto.PBKDF2();
|
|
|
|
var key = pbkdf2.getKey(password, keySize);
|
|
|
|
callback(key);
|
|
|
|
}
|
|
|
|
};
|
2013-04-01 18:12:15 -04:00
|
|
|
|
2013-04-02 09:02:57 -04:00
|
|
|
//
|
|
|
|
// En/Decrypts single item
|
|
|
|
//
|
|
|
|
|
|
|
|
this.aesEncrypt = function(plaintext, key, iv, callback) {
|
|
|
|
if (window.Worker) {
|
|
|
|
|
|
|
|
var worker = new Worker(app.config.workerPath + '/crypto/aes-worker.js');
|
2013-04-10 11:14:19 -04:00
|
|
|
worker.onmessage = function(e) {
|
2013-04-02 09:02:57 -04:00
|
|
|
callback(e.data);
|
2013-04-10 11:14:19 -04:00
|
|
|
};
|
2013-04-02 09:02:57 -04:00
|
|
|
worker.postMessage({
|
|
|
|
type: 'encrypt',
|
|
|
|
plaintext: plaintext,
|
|
|
|
key: key,
|
|
|
|
iv: iv
|
2013-04-01 18:12:15 -04:00
|
|
|
});
|
|
|
|
|
2013-04-02 09:02:57 -04:00
|
|
|
} else {
|
|
|
|
var ct = this.aesEncryptSync(plaintext, key, iv);
|
|
|
|
callback(ct);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
this.aesDecrypt = function(ciphertext, key, iv, callback) {
|
|
|
|
if (window.Worker) {
|
|
|
|
|
|
|
|
var worker = new Worker(app.config.workerPath + '/crypto/aes-worker.js');
|
2013-04-10 11:14:19 -04:00
|
|
|
worker.onmessage = function(e) {
|
2013-04-02 09:02:57 -04:00
|
|
|
callback(e.data);
|
2013-04-10 11:14:19 -04:00
|
|
|
};
|
2013-04-02 09:02:57 -04:00
|
|
|
worker.postMessage({
|
|
|
|
type: 'decrypt',
|
|
|
|
ciphertext: ciphertext,
|
|
|
|
key: key,
|
|
|
|
iv: iv
|
|
|
|
});
|
2013-04-01 18:12:15 -04:00
|
|
|
|
2013-04-02 09:02:57 -04:00
|
|
|
} else {
|
|
|
|
var pt = this.aesDecryptSync(ciphertext, key, iv);
|
|
|
|
callback(pt);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
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) {
|
|
|
|
if (window.Worker) {
|
|
|
|
|
|
|
|
var worker = new Worker(app.config.workerPath + '/crypto/aes-batch-worker.js');
|
2013-04-10 11:14:19 -04:00
|
|
|
worker.onmessage = function(e) {
|
2013-04-02 09:02:57 -04:00
|
|
|
callback(e.data);
|
2013-04-10 11:14:19 -04:00
|
|
|
};
|
2013-04-02 09:02:57 -04:00
|
|
|
worker.postMessage({
|
|
|
|
type: 'encrypt',
|
|
|
|
list: list
|
|
|
|
});
|
|
|
|
|
|
|
|
} else {
|
|
|
|
var encryptedList = util.encryptList(aes, list);
|
|
|
|
callback(encryptedList);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
this.aesDecryptList = function(list, callback) {
|
|
|
|
if (window.Worker) {
|
|
|
|
|
|
|
|
var worker = new Worker(app.config.workerPath + '/crypto/aes-batch-worker.js');
|
2013-04-10 11:14:19 -04:00
|
|
|
worker.onmessage = function(e) {
|
2013-04-02 09:02:57 -04:00
|
|
|
callback(e.data);
|
2013-04-10 11:14:19 -04:00
|
|
|
};
|
2013-04-02 09:02:57 -04:00
|
|
|
worker.postMessage({
|
|
|
|
type: 'decrypt',
|
|
|
|
list: list
|
|
|
|
});
|
|
|
|
|
|
|
|
} else {
|
|
|
|
var decryptedList = util.decryptList(aes, list);
|
|
|
|
callback(decryptedList);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
//
|
|
|
|
// En/Decrypt something speficially using the user's secret key
|
|
|
|
//
|
|
|
|
|
|
|
|
this.aesEncryptForUser = function(plaintext, iv, callback) {
|
|
|
|
var ciphertext = aes.encrypt(plaintext, symmetricUserKey, iv);
|
|
|
|
callback(ciphertext);
|
|
|
|
};
|
|
|
|
this.aesDecryptForUser = function(ciphertext, iv, callback) {
|
|
|
|
var decrypted = aes.decrypt(ciphertext, symmetricUserKey, iv);
|
|
|
|
callback(decrypted);
|
|
|
|
};
|
|
|
|
this.aesEncryptForUserSync = function(plaintext, iv) {
|
|
|
|
return aes.encrypt(plaintext, symmetricUserKey, iv);
|
|
|
|
};
|
|
|
|
this.aesDecryptForUserSync = function(ciphertext, iv) {
|
|
|
|
return aes.decrypt(ciphertext, symmetricUserKey, iv);
|
|
|
|
};
|
|
|
|
|
|
|
|
this.aesEncryptListForUser = function(list, callback) {
|
|
|
|
var i, envelope, envelopes = [],
|
|
|
|
self = this;
|
|
|
|
|
|
|
|
// package objects into batchable envelope format
|
|
|
|
for (i = 0; i < list.length; i++) {
|
|
|
|
envelope = {
|
|
|
|
id: list[i].id,
|
|
|
|
plaintext: list[i],
|
|
|
|
key: util.random(self.keySize),
|
|
|
|
iv: util.random(self.ivSize)
|
|
|
|
};
|
|
|
|
envelopes.push(envelope);
|
|
|
|
}
|
|
|
|
|
|
|
|
// encrypt list
|
|
|
|
this.aesEncryptList(envelopes, function(encryptedList) {
|
|
|
|
|
|
|
|
// encrypt keys for user
|
2013-04-01 18:12:15 -04:00
|
|
|
for (i = 0; i < encryptedList.length; i++) {
|
2013-04-02 09:02:57 -04:00
|
|
|
// process new values
|
|
|
|
encryptedList[i].itemIV = encryptedList[i].iv;
|
|
|
|
encryptedList[i].keyIV = util.random(self.ivSize);
|
|
|
|
encryptedList[i].encryptedKey = self.aesEncryptForUserSync(encryptedList[i].key, encryptedList[i].keyIV);
|
|
|
|
// delete old ones
|
|
|
|
delete encryptedList[i].iv;
|
|
|
|
delete encryptedList[i].key;
|
2013-04-01 18:12:15 -04:00
|
|
|
}
|
|
|
|
|
2013-04-02 09:02:57 -04:00
|
|
|
callback(encryptedList);
|
|
|
|
});
|
|
|
|
};
|
2013-04-01 18:12:15 -04:00
|
|
|
|
2013-04-02 09:02:57 -04:00
|
|
|
this.aesDecryptListForUser = function(encryptedList, callback) {
|
|
|
|
var i, list = [];
|
|
|
|
|
|
|
|
// decrypt keys for user
|
|
|
|
for (i = 0; i < encryptedList.length; i++) {
|
|
|
|
// decrypt item key
|
|
|
|
encryptedList[i].key = this.aesDecryptForUserSync(encryptedList[i].encryptedKey, encryptedList[i].keyIV);
|
|
|
|
encryptedList[i].iv = encryptedList[i].itemIV;
|
|
|
|
// delete old values
|
|
|
|
delete encryptedList[i].keyIV;
|
|
|
|
delete encryptedList[i].itemIV;
|
|
|
|
delete encryptedList[i].encryptedKey;
|
|
|
|
}
|
|
|
|
|
|
|
|
// decrypt list
|
|
|
|
this.aesDecryptList(encryptedList, function(decryptedList) {
|
|
|
|
// add plaintext to list
|
|
|
|
for (i = 0; i < decryptedList.length; i++) {
|
|
|
|
list.push(decryptedList[i].plaintext);
|
|
|
|
}
|
2013-03-13 11:58:46 -04:00
|
|
|
|
2013-04-02 09:02:57 -04:00
|
|
|
callback(list);
|
|
|
|
});
|
2013-03-13 11:58:46 -04:00
|
|
|
};
|
2013-04-01 18:12:15 -04:00
|
|
|
|
2013-04-02 09:02:57 -04:00
|
|
|
};
|