mirror of
https://github.com/moparisthebest/mail
synced 2024-11-22 08:52:15 -05:00
refactored all files to use functional strict scope
This commit is contained in:
parent
5ee864fe01
commit
5c0e04cc31
@ -1,4 +1,4 @@
|
|||||||
var app;
|
var app; // container for the application namespace
|
||||||
|
|
||||||
(function() {
|
(function() {
|
||||||
'use strict';
|
'use strict';
|
||||||
|
@ -1,43 +1,42 @@
|
|||||||
'use strict';
|
(function() {
|
||||||
|
'use strict';
|
||||||
|
|
||||||
// import web worker dependencies
|
// import web worker dependencies
|
||||||
importScripts('../../lib/sjcl/sjcl.js');
|
importScripts('../../lib/sjcl/sjcl.js');
|
||||||
importScripts('../../lib/sjcl/bitArray.js');
|
importScripts('../../lib/sjcl/bitArray.js');
|
||||||
importScripts('../../lib/sjcl/codecBase64.js');
|
importScripts('../../lib/sjcl/codecBase64.js');
|
||||||
importScripts('../../lib/sjcl/codecString.js');
|
importScripts('../../lib/sjcl/codecString.js');
|
||||||
importScripts('../../lib/sjcl/aes.js');
|
importScripts('../../lib/sjcl/aes.js');
|
||||||
importScripts('../../lib/sjcl/ccm.js');
|
importScripts('../../lib/sjcl/ccm.js');
|
||||||
importScripts('../app-config.js');
|
importScripts('../app-config.js');
|
||||||
importScripts('./aes-ccm.js');
|
importScripts('./aes-ccm.js');
|
||||||
importScripts('./util.js');
|
importScripts('./util.js');
|
||||||
|
|
||||||
var AESBATCHWORKER = (function () {
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* In the web worker thread context, 'this' and 'self' can be used as a global
|
* In the web worker thread context, 'this' and 'self' can be used as a global
|
||||||
* variable namespace similar to the 'window' object in the main thread
|
* variable namespace similar to the 'window' object in the main thread
|
||||||
*/
|
*/
|
||||||
self.addEventListener('message', function(e) {
|
self.addEventListener('message', function(e) {
|
||||||
|
|
||||||
var args = e.data,
|
var args = e.data,
|
||||||
output = null,
|
output = null,
|
||||||
aes = new app.crypto.AesCCM(),
|
aes = new app.crypto.AesCCM(),
|
||||||
util = new app.crypto.Util(null, null);
|
util = new app.crypto.Util(null, null);
|
||||||
|
|
||||||
if (args.type === 'encrypt' && args.list) {
|
if (args.type === 'encrypt' && args.list) {
|
||||||
// start encryption
|
// start encryption
|
||||||
output = util.encryptList(aes, args.list);
|
output = util.encryptList(aes, args.list);
|
||||||
|
|
||||||
} else if (args.type === 'decrypt' && args.list) {
|
} else if (args.type === 'decrypt' && args.list) {
|
||||||
// start decryption
|
// start decryption
|
||||||
output = util.decryptList(aes, args.list);
|
output = util.decryptList(aes, args.list);
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
throw 'Not all arguments for web worker crypto are defined!';
|
throw 'Not all arguments for web worker crypto are defined!';
|
||||||
}
|
}
|
||||||
|
|
||||||
// pass output back to main thread
|
// pass output back to main thread
|
||||||
self.postMessage(output);
|
self.postMessage(output);
|
||||||
}, false);
|
}, false);
|
||||||
|
|
||||||
}());
|
}());
|
@ -1,48 +1,59 @@
|
|||||||
'use strict';
|
(function() {
|
||||||
|
'use strict';
|
||||||
|
|
||||||
/**
|
|
||||||
* A Wrapper for Crypto.js's AES-CBC encryption
|
|
||||||
*/
|
|
||||||
app.crypto.AesCBC = function() {
|
|
||||||
|
|
||||||
var mode = CryptoJS.mode.CBC; // use CBC mode for Crypto.js
|
|
||||||
var padding = CryptoJS.pad.Pkcs7; // use Pkcs7/Pkcs5 padding for Crypto.js
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Encrypt a String using AES-CBC-Pkcs7 using the provided keysize (e.g. 128, 256)
|
* A Wrapper for Crypto.js's AES-CBC encryption
|
||||||
* @param plaintext [String] The input string in UTF8
|
|
||||||
* @param key [String] The base64 encoded key
|
|
||||||
* @param iv [String] The base64 encoded IV
|
|
||||||
* @return [String] The base64 encoded ciphertext
|
|
||||||
*/
|
*/
|
||||||
this.encrypt = function(plaintext, key, iv) {
|
app.crypto.AesCBC = function() {
|
||||||
// parse base64 input to crypto.js WordArrays
|
|
||||||
var keyWords = CryptoJS.enc.Base64.parse(key);
|
var mode = CryptoJS.mode.CBC; // use CBC mode for Crypto.js
|
||||||
var ivWords = CryptoJS.enc.Base64.parse(iv);
|
var padding = CryptoJS.pad.Pkcs7; // use Pkcs7/Pkcs5 padding for Crypto.js
|
||||||
var plaintextWords = CryptoJS.enc.Utf8.parse(plaintext);
|
|
||||||
|
/**
|
||||||
var encrypted = CryptoJS.AES.encrypt(plaintextWords, keyWords, { iv: ivWords, mode: mode, padding: padding });
|
* Encrypt a String using AES-CBC-Pkcs7 using the provided keysize (e.g. 128, 256)
|
||||||
var ctBase64 = CryptoJS.enc.Base64.stringify(encrypted.ciphertext);
|
* @param plaintext [String] The input string in UTF8
|
||||||
|
* @param key [String] The base64 encoded key
|
||||||
return ctBase64;
|
* @param iv [String] The base64 encoded IV
|
||||||
|
* @return [String] The base64 encoded ciphertext
|
||||||
|
*/
|
||||||
|
this.encrypt = function(plaintext, key, iv) {
|
||||||
|
// parse base64 input to crypto.js WordArrays
|
||||||
|
var keyWords = CryptoJS.enc.Base64.parse(key);
|
||||||
|
var ivWords = CryptoJS.enc.Base64.parse(iv);
|
||||||
|
var plaintextWords = CryptoJS.enc.Utf8.parse(plaintext);
|
||||||
|
|
||||||
|
var encrypted = CryptoJS.AES.encrypt(plaintextWords, keyWords, {
|
||||||
|
iv: ivWords,
|
||||||
|
mode: mode,
|
||||||
|
padding: padding
|
||||||
|
});
|
||||||
|
var ctBase64 = CryptoJS.enc.Base64.stringify(encrypted.ciphertext);
|
||||||
|
|
||||||
|
return ctBase64;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decrypt a String using AES-CBC-Pkcs7 using the provided keysize (e.g. 128, 256)
|
||||||
|
* @param ciphertext [String] The base64 encoded ciphertext
|
||||||
|
* @param key [String] The base64 encoded key
|
||||||
|
* @param iv [String] The base64 encoded IV
|
||||||
|
* @return [String] The decrypted plaintext in UTF8
|
||||||
|
*/
|
||||||
|
this.decrypt = function(ciphertext, key, iv) {
|
||||||
|
// parse base64 input to crypto.js WordArrays
|
||||||
|
var keyWords = CryptoJS.enc.Base64.parse(key);
|
||||||
|
var ivWords = CryptoJS.enc.Base64.parse(iv);
|
||||||
|
|
||||||
|
var decrypted = CryptoJS.AES.decrypt(ciphertext, keyWords, {
|
||||||
|
iv: ivWords,
|
||||||
|
mode: mode,
|
||||||
|
padding: padding
|
||||||
|
});
|
||||||
|
var pt = decrypted.toString(CryptoJS.enc.Utf8);
|
||||||
|
|
||||||
|
return pt;
|
||||||
|
};
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
}());
|
||||||
* Decrypt a String using AES-CBC-Pkcs7 using the provided keysize (e.g. 128, 256)
|
|
||||||
* @param ciphertext [String] The base64 encoded ciphertext
|
|
||||||
* @param key [String] The base64 encoded key
|
|
||||||
* @param iv [String] The base64 encoded IV
|
|
||||||
* @return [String] The decrypted plaintext in UTF8
|
|
||||||
*/
|
|
||||||
this.decrypt = function(ciphertext, key, iv) {
|
|
||||||
// parse base64 input to crypto.js WordArrays
|
|
||||||
var keyWords = CryptoJS.enc.Base64.parse(key);
|
|
||||||
var ivWords = CryptoJS.enc.Base64.parse(iv);
|
|
||||||
|
|
||||||
var decrypted = CryptoJS.AES.decrypt(ciphertext, keyWords, { iv: ivWords, mode: mode, padding: padding });
|
|
||||||
var pt = decrypted.toString(CryptoJS.enc.Utf8);
|
|
||||||
|
|
||||||
return pt;
|
|
||||||
};
|
|
||||||
|
|
||||||
};
|
|
@ -1,51 +1,54 @@
|
|||||||
'use strict';
|
(function() {
|
||||||
|
'use strict';
|
||||||
|
|
||||||
/**
|
|
||||||
* A Wrapper for SJCL's authenticated AES-CCM encryption
|
|
||||||
*/
|
|
||||||
app.crypto.AesCCM = function() {
|
|
||||||
|
|
||||||
var adata = []; // authenticated data (empty by default)
|
|
||||||
var tlen = 64; // The tag length in bits
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Encrypt a String using AES-CCM using the provided keysize (e.g. 128, 256)
|
* A Wrapper for SJCL's authenticated AES-CCM encryption
|
||||||
* @param plaintext [String] The input string in UTF8
|
|
||||||
* @param key [String] The base64 encoded key
|
|
||||||
* @param iv [String] The base64 encoded IV
|
|
||||||
* @return [String] The base64 encoded ciphertext
|
|
||||||
*/
|
*/
|
||||||
this.encrypt = function(plaintext, key, iv) {
|
app.crypto.AesCCM = function() {
|
||||||
// convert parameters to WordArrays
|
|
||||||
var keyWords = sjcl.codec.base64.toBits(key);
|
var adata = []; // authenticated data (empty by default)
|
||||||
var ivWords = sjcl.codec.base64.toBits(iv);
|
var tlen = 64; // The tag length in bits
|
||||||
var plaintextWords = sjcl.codec.utf8String.toBits(plaintext);
|
|
||||||
|
/**
|
||||||
var blockCipher = new sjcl.cipher.aes(keyWords);
|
* Encrypt a String using AES-CCM using the provided keysize (e.g. 128, 256)
|
||||||
var ciphertext = sjcl.mode.ccm.encrypt(blockCipher, plaintextWords, ivWords, adata, tlen);
|
* @param plaintext [String] The input string in UTF8
|
||||||
var ctBase64 = sjcl.codec.base64.fromBits(ciphertext);
|
* @param key [String] The base64 encoded key
|
||||||
|
* @param iv [String] The base64 encoded IV
|
||||||
return ctBase64;
|
* @return [String] The base64 encoded ciphertext
|
||||||
};
|
*/
|
||||||
|
this.encrypt = function(plaintext, key, iv) {
|
||||||
/**
|
// convert parameters to WordArrays
|
||||||
* Decrypt a String using AES-CCM using the provided keysize (e.g. 128, 256)
|
var keyWords = sjcl.codec.base64.toBits(key);
|
||||||
* @param ciphertext [String] The base64 encoded ciphertext
|
var ivWords = sjcl.codec.base64.toBits(iv);
|
||||||
* @param key [String] The base64 encoded key
|
var plaintextWords = sjcl.codec.utf8String.toBits(plaintext);
|
||||||
* @param iv [String] The base64 encoded IV
|
|
||||||
* @return [String] The decrypted plaintext in UTF8
|
var blockCipher = new sjcl.cipher.aes(keyWords);
|
||||||
*/
|
var ciphertext = sjcl.mode.ccm.encrypt(blockCipher, plaintextWords, ivWords, adata, tlen);
|
||||||
this.decrypt = function(ciphertext, key, iv) {
|
var ctBase64 = sjcl.codec.base64.fromBits(ciphertext);
|
||||||
// convert parameters to WordArrays
|
|
||||||
var keyWords = sjcl.codec.base64.toBits(key);
|
return ctBase64;
|
||||||
var ivWords = sjcl.codec.base64.toBits(iv);
|
};
|
||||||
var ciphertextWords = sjcl.codec.base64.toBits(ciphertext);
|
|
||||||
|
/**
|
||||||
var blockCipher = new sjcl.cipher.aes(keyWords);
|
* Decrypt a String using AES-CCM using the provided keysize (e.g. 128, 256)
|
||||||
var decrypted = sjcl.mode.ccm.decrypt(blockCipher, ciphertextWords, ivWords, adata, tlen);
|
* @param ciphertext [String] The base64 encoded ciphertext
|
||||||
var pt = sjcl.codec.utf8String.fromBits(decrypted);
|
* @param key [String] The base64 encoded key
|
||||||
|
* @param iv [String] The base64 encoded IV
|
||||||
return pt;
|
* @return [String] The decrypted plaintext in UTF8
|
||||||
|
*/
|
||||||
|
this.decrypt = function(ciphertext, key, iv) {
|
||||||
|
// convert parameters to WordArrays
|
||||||
|
var keyWords = sjcl.codec.base64.toBits(key);
|
||||||
|
var ivWords = sjcl.codec.base64.toBits(iv);
|
||||||
|
var ciphertextWords = sjcl.codec.base64.toBits(ciphertext);
|
||||||
|
|
||||||
|
var blockCipher = new sjcl.cipher.aes(keyWords);
|
||||||
|
var decrypted = sjcl.mode.ccm.decrypt(blockCipher, ciphertextWords, ivWords, adata, tlen);
|
||||||
|
var pt = sjcl.codec.utf8String.fromBits(decrypted);
|
||||||
|
|
||||||
|
return pt;
|
||||||
|
};
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
};
|
}());
|
@ -1,51 +1,54 @@
|
|||||||
'use strict';
|
(function() {
|
||||||
|
'use strict';
|
||||||
|
|
||||||
/**
|
|
||||||
* A Wrapper for SJCL's authenticated AES-GCM encryption
|
|
||||||
*/
|
|
||||||
app.crypto.AesGCM = function() {
|
|
||||||
|
|
||||||
var adata = []; // authenticated data (empty by default)
|
|
||||||
var tlen = 128; // The tag length in bits
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Encrypt a String using AES-GCM using the provided keysize (e.g. 128, 256)
|
* A Wrapper for SJCL's authenticated AES-GCM encryption
|
||||||
* @param plaintext [String] The input string in UTF8
|
|
||||||
* @param key [String] The base64 encoded key
|
|
||||||
* @param iv [String] The base64 encoded IV
|
|
||||||
* @return [String] The base64 encoded ciphertext
|
|
||||||
*/
|
*/
|
||||||
this.encrypt = function(plaintext, key, iv) {
|
app.crypto.AesGCM = function() {
|
||||||
// convert parameters to WordArrays
|
|
||||||
var keyWords = sjcl.codec.base64.toBits(key);
|
var adata = []; // authenticated data (empty by default)
|
||||||
var ivWords = sjcl.codec.base64.toBits(iv);
|
var tlen = 128; // The tag length in bits
|
||||||
var plaintextWords = sjcl.codec.utf8String.toBits(plaintext);
|
|
||||||
|
/**
|
||||||
var blockCipher = new sjcl.cipher.aes(keyWords);
|
* Encrypt a String using AES-GCM using the provided keysize (e.g. 128, 256)
|
||||||
var ciphertext = sjcl.mode.gcm.encrypt(blockCipher, plaintextWords, ivWords, adata, tlen);
|
* @param plaintext [String] The input string in UTF8
|
||||||
var ctBase64 = sjcl.codec.base64.fromBits(ciphertext);
|
* @param key [String] The base64 encoded key
|
||||||
|
* @param iv [String] The base64 encoded IV
|
||||||
return ctBase64;
|
* @return [String] The base64 encoded ciphertext
|
||||||
};
|
*/
|
||||||
|
this.encrypt = function(plaintext, key, iv) {
|
||||||
/**
|
// convert parameters to WordArrays
|
||||||
* Decrypt a String using AES-GCM using the provided keysize (e.g. 128, 256)
|
var keyWords = sjcl.codec.base64.toBits(key);
|
||||||
* @param ciphertext [String] The base64 encoded ciphertext
|
var ivWords = sjcl.codec.base64.toBits(iv);
|
||||||
* @param key [String] The base64 encoded key
|
var plaintextWords = sjcl.codec.utf8String.toBits(plaintext);
|
||||||
* @param iv [String] The base64 encoded IV
|
|
||||||
* @return [String] The decrypted plaintext in UTF8
|
var blockCipher = new sjcl.cipher.aes(keyWords);
|
||||||
*/
|
var ciphertext = sjcl.mode.gcm.encrypt(blockCipher, plaintextWords, ivWords, adata, tlen);
|
||||||
this.decrypt = function(ciphertext, key, iv) {
|
var ctBase64 = sjcl.codec.base64.fromBits(ciphertext);
|
||||||
// convert parameters to WordArrays
|
|
||||||
var keyWords = sjcl.codec.base64.toBits(key);
|
return ctBase64;
|
||||||
var ivWords = sjcl.codec.base64.toBits(iv);
|
};
|
||||||
var ciphertextWords = sjcl.codec.base64.toBits(ciphertext);
|
|
||||||
|
/**
|
||||||
var blockCipher = new sjcl.cipher.aes(keyWords);
|
* Decrypt a String using AES-GCM using the provided keysize (e.g. 128, 256)
|
||||||
var decrypted = sjcl.mode.gcm.decrypt(blockCipher, ciphertextWords, ivWords, adata, tlen);
|
* @param ciphertext [String] The base64 encoded ciphertext
|
||||||
var pt = sjcl.codec.utf8String.fromBits(decrypted);
|
* @param key [String] The base64 encoded key
|
||||||
|
* @param iv [String] The base64 encoded IV
|
||||||
return pt;
|
* @return [String] The decrypted plaintext in UTF8
|
||||||
|
*/
|
||||||
|
this.decrypt = function(ciphertext, key, iv) {
|
||||||
|
// convert parameters to WordArrays
|
||||||
|
var keyWords = sjcl.codec.base64.toBits(key);
|
||||||
|
var ivWords = sjcl.codec.base64.toBits(iv);
|
||||||
|
var ciphertextWords = sjcl.codec.base64.toBits(ciphertext);
|
||||||
|
|
||||||
|
var blockCipher = new sjcl.cipher.aes(keyWords);
|
||||||
|
var decrypted = sjcl.mode.gcm.decrypt(blockCipher, ciphertextWords, ivWords, adata, tlen);
|
||||||
|
var pt = sjcl.codec.utf8String.fromBits(decrypted);
|
||||||
|
|
||||||
|
return pt;
|
||||||
|
};
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
};
|
}());
|
@ -1,41 +1,40 @@
|
|||||||
'use strict';
|
(function() {
|
||||||
|
'use strict';
|
||||||
|
|
||||||
// import web worker dependencies
|
// import web worker dependencies
|
||||||
importScripts('../../lib/sjcl/sjcl.js');
|
importScripts('../../lib/sjcl/sjcl.js');
|
||||||
importScripts('../../lib/sjcl/bitArray.js');
|
importScripts('../../lib/sjcl/bitArray.js');
|
||||||
importScripts('../../lib/sjcl/codecBase64.js');
|
importScripts('../../lib/sjcl/codecBase64.js');
|
||||||
importScripts('../../lib/sjcl/codecString.js');
|
importScripts('../../lib/sjcl/codecString.js');
|
||||||
importScripts('../../lib/sjcl/aes.js');
|
importScripts('../../lib/sjcl/aes.js');
|
||||||
importScripts('../../lib/sjcl/ccm.js');
|
importScripts('../../lib/sjcl/ccm.js');
|
||||||
importScripts('../app-config.js');
|
importScripts('../app-config.js');
|
||||||
importScripts('./aes-ccm.js');
|
importScripts('./aes-ccm.js');
|
||||||
|
|
||||||
var AESWORKER = (function () {
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* In the web worker thread context, 'this' and 'self' can be used as a global
|
* In the web worker thread context, 'this' and 'self' can be used as a global
|
||||||
* variable namespace similar to the 'window' object in the main thread
|
* variable namespace similar to the 'window' object in the main thread
|
||||||
*/
|
*/
|
||||||
self.addEventListener('message', function(e) {
|
self.addEventListener('message', function(e) {
|
||||||
|
|
||||||
var args = e.data,
|
var args = e.data,
|
||||||
output = null,
|
output = null,
|
||||||
aes = new app.crypto.AesCCM();
|
aes = new app.crypto.AesCCM();
|
||||||
|
|
||||||
if (args.type === 'encrypt' && args.plaintext && args.key && args.iv) {
|
if (args.type === 'encrypt' && args.plaintext && args.key && args.iv) {
|
||||||
// start encryption
|
// start encryption
|
||||||
output = aes.encrypt(args.plaintext, args.key, args.iv);
|
output = aes.encrypt(args.plaintext, args.key, args.iv);
|
||||||
|
|
||||||
} else if (args.type === 'decrypt' && args.ciphertext && args.key && args.iv) {
|
} else if (args.type === 'decrypt' && args.ciphertext && args.key && args.iv) {
|
||||||
// start decryption
|
// start decryption
|
||||||
output = aes.decrypt(args.ciphertext, args.key, args.iv);
|
output = aes.decrypt(args.ciphertext, args.key, args.iv);
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
throw 'Not all arguments for web worker crypto are defined!';
|
throw 'Not all arguments for web worker crypto are defined!';
|
||||||
}
|
}
|
||||||
|
|
||||||
// pass output back to main thread
|
// pass output back to main thread
|
||||||
self.postMessage(output);
|
self.postMessage(output);
|
||||||
}, false);
|
}, false);
|
||||||
|
|
||||||
}());
|
}());
|
@ -1,223 +1,249 @@
|
|||||||
'use strict';
|
(function() {
|
||||||
|
'use strict';
|
||||||
|
|
||||||
/**
|
|
||||||
* High level crypto api that invokes native crypto (if available) and
|
|
||||||
* gracefully degrades to JS crypto (if unavailable)
|
|
||||||
*/
|
|
||||||
app.crypto.Crypto = function(window, util) {
|
|
||||||
|
|
||||||
var symmetricUserKey, // the user's secret key used to encrypt item-keys
|
|
||||||
aes = new app.crypto.AesCCM(); // use authenticated AES-CCM mode by default
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initializes the crypto modules by fetching the user's
|
* High level crypto api that invokes native crypto (if available) and
|
||||||
* encrypted secret key from storage and storing it in memory.
|
* gracefully degrades to JS crypto (if unavailable)
|
||||||
*/
|
*/
|
||||||
this.init = function(emailAddress, password, keySize, ivSize, callback) {
|
app.crypto.Crypto = function(window, util) {
|
||||||
this.emailAddress = emailAddress;
|
|
||||||
this.keySize = keySize;
|
var symmetricUserKey, // the user's secret key used to encrypt item-keys
|
||||||
this.ivSize = ivSize;
|
aes = new app.crypto.AesCCM(); // use authenticated AES-CCM mode by default
|
||||||
|
|
||||||
// derive PBKDF2 from password in web worker thread
|
/**
|
||||||
this.deriveKey(password, keySize, function(pbkdf2) {
|
* Initializes the crypto modules by fetching the user's
|
||||||
|
* encrypted secret key from storage and storing it in memory.
|
||||||
// fetch user's encrypted secret key from keychain/storage
|
*/
|
||||||
var keyStore = new app.dao.LocalStorageDAO(window);
|
this.init = function(emailAddress, password, keySize, ivSize, callback) {
|
||||||
var storageId = emailAddress + '_encryptedSymmetricKey';
|
this.emailAddress = emailAddress;
|
||||||
var encryptedKey = keyStore.read(storageId);
|
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';
|
||||||
|
var encryptedKey = keyStore.read(storageId);
|
||||||
|
|
||||||
|
// check if key exists
|
||||||
|
if (!encryptedKey) {
|
||||||
|
// 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, {
|
||||||
|
key: key,
|
||||||
|
iv: iv
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// decrypt key
|
||||||
|
symmetricUserKey = aes.decrypt(encryptedKey.key, pbkdf2, encryptedKey.iv);
|
||||||
|
}
|
||||||
|
|
||||||
|
callback();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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.addEventListener('message', function(e) {
|
||||||
|
// return derived key from the worker
|
||||||
|
callback(e.data);
|
||||||
|
}, false);
|
||||||
|
|
||||||
|
// send plaintext data to the worker
|
||||||
|
worker.postMessage({
|
||||||
|
password: password,
|
||||||
|
keySize: keySize
|
||||||
|
});
|
||||||
|
|
||||||
// check if key exists
|
|
||||||
if(!encryptedKey) {
|
|
||||||
// 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, { key: key, iv: iv });
|
|
||||||
} else {
|
} else {
|
||||||
// decrypt key
|
// no WebWorker support... do synchronous call
|
||||||
symmetricUserKey = aes.decrypt(encryptedKey.key, pbkdf2, encryptedKey.iv);
|
var pbkdf2 = new app.crypto.PBKDF2();
|
||||||
}
|
var key = pbkdf2.getKey(password, keySize);
|
||||||
|
callback(key);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
callback();
|
//
|
||||||
});
|
// En/Decrypts single item
|
||||||
};
|
//
|
||||||
|
|
||||||
/**
|
|
||||||
* 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.addEventListener('message', function(e) {
|
|
||||||
// return derived key from the worker
|
|
||||||
callback(e.data);
|
|
||||||
}, false);
|
|
||||||
|
|
||||||
// send plaintext data to the worker
|
|
||||||
worker.postMessage({ password:password, keySize:keySize });
|
|
||||||
|
|
||||||
} else {
|
|
||||||
// no WebWorker support... do synchronous call
|
|
||||||
var pbkdf2 = new app.crypto.PBKDF2();
|
|
||||||
var key = pbkdf2.getKey(password, keySize);
|
|
||||||
callback(key);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
//
|
|
||||||
// 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');
|
|
||||||
worker.addEventListener('message', function(e) {
|
|
||||||
callback(e.data);
|
|
||||||
}, false);
|
|
||||||
worker.postMessage({ type:'encrypt', plaintext:plaintext, key:key, iv:iv });
|
|
||||||
|
|
||||||
} 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');
|
|
||||||
worker.addEventListener('message', function(e) {
|
|
||||||
callback(e.data);
|
|
||||||
}, false);
|
|
||||||
worker.postMessage({ type:'decrypt', ciphertext:ciphertext, key:key, iv:iv });
|
|
||||||
|
|
||||||
} 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');
|
|
||||||
worker.addEventListener('message', function(e) {
|
|
||||||
callback(e.data);
|
|
||||||
}, false);
|
|
||||||
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');
|
|
||||||
worker.addEventListener('message', function(e) {
|
|
||||||
callback(e.data);
|
|
||||||
}, false);
|
|
||||||
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
|
this.aesEncrypt = function(plaintext, key, iv, callback) {
|
||||||
|
if (window.Worker) {
|
||||||
|
|
||||||
|
var worker = new Worker(app.config.workerPath + '/crypto/aes-worker.js');
|
||||||
|
worker.addEventListener('message', function(e) {
|
||||||
|
callback(e.data);
|
||||||
|
}, false);
|
||||||
|
worker.postMessage({
|
||||||
|
type: 'encrypt',
|
||||||
|
plaintext: plaintext,
|
||||||
|
key: key,
|
||||||
|
iv: iv
|
||||||
|
});
|
||||||
|
|
||||||
|
} 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');
|
||||||
|
worker.addEventListener('message', function(e) {
|
||||||
|
callback(e.data);
|
||||||
|
}, false);
|
||||||
|
worker.postMessage({
|
||||||
|
type: 'decrypt',
|
||||||
|
ciphertext: ciphertext,
|
||||||
|
key: key,
|
||||||
|
iv: iv
|
||||||
|
});
|
||||||
|
|
||||||
|
} 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');
|
||||||
|
worker.addEventListener('message', function(e) {
|
||||||
|
callback(e.data);
|
||||||
|
}, false);
|
||||||
|
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');
|
||||||
|
worker.addEventListener('message', function(e) {
|
||||||
|
callback(e.data);
|
||||||
|
}, false);
|
||||||
|
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
|
||||||
|
for (i = 0; i < encryptedList.length; i++) {
|
||||||
|
// 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
callback(encryptedList);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
this.aesDecryptListForUser = function(encryptedList, callback) {
|
||||||
|
var i, list = [];
|
||||||
|
|
||||||
|
// decrypt keys for user
|
||||||
for (i = 0; i < encryptedList.length; i++) {
|
for (i = 0; i < encryptedList.length; i++) {
|
||||||
// process new values
|
// decrypt item key
|
||||||
encryptedList[i].itemIV = encryptedList[i].iv;
|
encryptedList[i].key = this.aesDecryptForUserSync(encryptedList[i].encryptedKey, encryptedList[i].keyIV);
|
||||||
encryptedList[i].keyIV = util.random(self.ivSize);
|
encryptedList[i].iv = encryptedList[i].itemIV;
|
||||||
encryptedList[i].encryptedKey = self.aesEncryptForUserSync(encryptedList[i].key, encryptedList[i].keyIV);
|
// delete old values
|
||||||
// delete old ones
|
delete encryptedList[i].keyIV;
|
||||||
delete encryptedList[i].iv;
|
delete encryptedList[i].itemIV;
|
||||||
delete encryptedList[i].key;
|
delete encryptedList[i].encryptedKey;
|
||||||
}
|
|
||||||
|
|
||||||
callback(encryptedList);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
callback(list);
|
// decrypt list
|
||||||
});
|
this.aesDecryptList(encryptedList, function(decryptedList) {
|
||||||
|
// add plaintext to list
|
||||||
|
for (i = 0; i < decryptedList.length; i++) {
|
||||||
|
list.push(decryptedList[i].plaintext);
|
||||||
|
}
|
||||||
|
|
||||||
|
callback(list);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
};
|
}());
|
@ -1,36 +1,35 @@
|
|||||||
'use strict';
|
(function() {
|
||||||
|
'use strict';
|
||||||
|
|
||||||
// import web worker dependencies
|
// import web worker dependencies
|
||||||
importScripts('../../lib/crypto-js/core.js');
|
importScripts('../../lib/crypto-js/core.js');
|
||||||
importScripts('../../lib/crypto-js/enc-base64.js');
|
importScripts('../../lib/crypto-js/enc-base64.js');
|
||||||
importScripts('../../lib/crypto-js/sha1.js');
|
importScripts('../../lib/crypto-js/sha1.js');
|
||||||
importScripts('../../lib/crypto-js/hmac.js');
|
importScripts('../../lib/crypto-js/hmac.js');
|
||||||
importScripts('../../lib/crypto-js/pbkdf2.js');
|
importScripts('../../lib/crypto-js/pbkdf2.js');
|
||||||
importScripts('../app-config.js');
|
importScripts('../app-config.js');
|
||||||
importScripts('./pbkdf2.js');
|
importScripts('./pbkdf2.js');
|
||||||
|
|
||||||
var PBKDF2WORKER = (function () {
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* In the web worker thread context, 'this' and 'self' can be used as a global
|
* In the web worker thread context, 'this' and 'self' can be used as a global
|
||||||
* variable namespace similar to the 'window' object in the main thread
|
* variable namespace similar to the 'window' object in the main thread
|
||||||
*/
|
*/
|
||||||
self.addEventListener('message', function(e) {
|
self.addEventListener('message', function(e) {
|
||||||
|
|
||||||
var args = e.data,
|
var args = e.data,
|
||||||
key = null;
|
key = null;
|
||||||
|
|
||||||
if (e.data.password && e.data.keySize) {
|
if (e.data.password && e.data.keySize) {
|
||||||
// start deriving key
|
// start deriving key
|
||||||
var pbkdf2 = new app.crypto.PBKDF2();
|
var pbkdf2 = new app.crypto.PBKDF2();
|
||||||
key = pbkdf2.getKey(e.data.password, e.data.keySize);
|
key = pbkdf2.getKey(e.data.password, e.data.keySize);
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
throw 'Not all arguments for web worker crypto are defined!';
|
throw 'Not all arguments for web worker crypto are defined!';
|
||||||
}
|
}
|
||||||
|
|
||||||
// pass output back to main thread
|
// pass output back to main thread
|
||||||
self.postMessage(key);
|
self.postMessage(key);
|
||||||
}, false);
|
}, false);
|
||||||
|
|
||||||
}());
|
}());
|
@ -1,22 +1,28 @@
|
|||||||
'use strict';
|
(function() {
|
||||||
|
'use strict';
|
||||||
|
|
||||||
/**
|
|
||||||
* A Wrapper for Crypto.js's PBKDF2 function
|
|
||||||
*/
|
|
||||||
app.crypto.PBKDF2 = function() {
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* PBKDF2-HMAC-SHA1 key derivation with a constant salt and 1000 iterations
|
* A Wrapper for Crypto.js's PBKDF2 function
|
||||||
* @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) {
|
app.crypto.PBKDF2 = function() {
|
||||||
var salt = CryptoJS.enc.Base64.parse("vbhmLjC+Ub6MSbhS6/CkOwxB25wvwRkSLP2DzDtYb+4="); // from random 256 bit value
|
|
||||||
var key = CryptoJS.PBKDF2(password, salt, { keySize: keySize/32, iterations: 1000 });
|
/**
|
||||||
var keyBase64 = CryptoJS.enc.Base64.stringify(key);
|
* PBKDF2-HMAC-SHA1 key derivation with a constant salt and 1000 iterations
|
||||||
|
* @param password [String] The password in UTF8
|
||||||
return keyBase64;
|
* @param keySize [Number] The key size in bits
|
||||||
|
* @return [String] The base64 encoded key
|
||||||
|
*/
|
||||||
|
this.getKey = function(password, keySize) {
|
||||||
|
var salt = CryptoJS.enc.Base64.parse("vbhmLjC+Ub6MSbhS6/CkOwxB25wvwRkSLP2DzDtYb+4="); // from random 256 bit value
|
||||||
|
var key = CryptoJS.PBKDF2(password, salt, {
|
||||||
|
keySize: keySize / 32,
|
||||||
|
iterations: 1000
|
||||||
|
});
|
||||||
|
var keyBase64 = CryptoJS.enc.Base64.stringify(key);
|
||||||
|
|
||||||
|
return keyBase64;
|
||||||
|
};
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
};
|
}());
|
@ -1,322 +1,345 @@
|
|||||||
'use strict';
|
(function() {
|
||||||
|
'use strict';
|
||||||
|
|
||||||
/**
|
|
||||||
* A wrapper for asymmetric OpenPGP encryption logic
|
|
||||||
*/
|
|
||||||
app.crypto.PGP = function(window, openpgp, util, server) {
|
|
||||||
|
|
||||||
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,
|
* A wrapper for asymmetric OpenPGP encryption logic
|
||||||
* generate a new keypait for the user
|
|
||||||
*/
|
*/
|
||||||
self.initKeyPair = function(loginInfo, callback, displayCallback, finishCallback) {
|
app.crypto.PGP = function(window, openpgp, util, server) {
|
||||||
// check if user already has a keypair in local storage
|
|
||||||
if (loginInfo.publicKeyId) {
|
var self = this,
|
||||||
// decode base 64 key ID
|
privateKey, // user's private key
|
||||||
var keyId = window.atob(loginInfo.publicKeyId);
|
publicKey, // user's public key
|
||||||
// read the user's keys from local storage
|
passphrase; // user's passphrase used for decryption
|
||||||
callback(keyId);
|
|
||||||
|
openpgp.init(); // initialize OpenPGP.js
|
||||||
} else {
|
|
||||||
// user has no key pair yet
|
//
|
||||||
displayCallback(function() {
|
// Key management
|
||||||
// generate new key pair with 2048 bit RSA keys
|
//
|
||||||
var keys = self.generateKeys(2048);
|
|
||||||
var keyId = keys.privateKey.getKeyId();
|
/**
|
||||||
|
* Check if user already has a public key on the server and if not,
|
||||||
// display finish
|
* generate a new keypait for the user
|
||||||
finishCallback(keyId);
|
*/
|
||||||
|
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
|
// read the user's keys from local storage
|
||||||
callback(keyId);
|
callback(keyId);
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
} else {
|
||||||
* Generate a key pair for the user
|
// user has no key pair yet
|
||||||
* @param numBits [int] number of bits for the key creation. (should be 1024+, generally)
|
displayCallback(function() {
|
||||||
* @email [string] user's email address
|
// generate new key pair with 2048 bit RSA keys
|
||||||
* @pass [string] a passphrase used to protect the private key
|
var keys = self.generateKeys(2048);
|
||||||
*/
|
var keyId = keys.privateKey.getKeyId();
|
||||||
self.generateKeys = function(numBits) {
|
|
||||||
// check passphrase
|
|
||||||
if (!passphrase && passphrase !== '') { throw 'No passphrase set!'; }
|
|
||||||
|
|
||||||
var userId = 'SafeWith.me User <anonymous@dunno.com>';
|
|
||||||
var keys = openpgp.generate_key_pair(1, numBits, userId, passphrase); // keytype 1=RSA
|
|
||||||
|
|
||||||
self.importKeys(keys.publicKeyArmored, keys.privateKeyArmored);
|
// display finish
|
||||||
|
finishCallback(keyId);
|
||||||
return keys;
|
// read the user's keys from local storage
|
||||||
};
|
callback(keyId);
|
||||||
|
});
|
||||||
/**
|
|
||||||
* 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() {
|
* 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 <anonymous@dunno.com>';
|
||||||
|
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({
|
server.xhr({
|
||||||
type: 'POST',
|
type: 'POST',
|
||||||
uri: '/ws/privateKeys',
|
uri: '/ws/publicKeys',
|
||||||
contentType: 'application/json',
|
contentType: 'application/json',
|
||||||
expected: 201,
|
expected: 201,
|
||||||
body: jsonPrivateKey,
|
body: jsonPublicKey,
|
||||||
success: function(resp) {
|
success: function(resp) {
|
||||||
// read the user's keys from local storage
|
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);
|
callback(keyId);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
|
||||||
};
|
// then upload private key
|
||||||
|
|
||||||
/**
|
function uploadPrivateKeys() {
|
||||||
* Get the keypair from the server and import them into localstorage
|
server.xhr({
|
||||||
*/
|
type: 'POST',
|
||||||
self.fetchKeys = function(email, keyId, callback, errCallback) {
|
uri: '/ws/privateKeys',
|
||||||
var base64Key = window.btoa(keyId);
|
contentType: 'application/json',
|
||||||
var encodedKeyId = encodeURIComponent(base64Key);
|
expected: 201,
|
||||||
|
body: jsonPrivateKey,
|
||||||
// get public key
|
success: function(resp) {
|
||||||
server.xhr({
|
// read the user's keys from local storage
|
||||||
type: 'GET',
|
callback(keyId);
|
||||||
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) {
|
* 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({
|
server.xhr({
|
||||||
type: 'GET',
|
type: 'GET',
|
||||||
uri: '/ws/privateKeys?keyId=' + encodedKeyId,
|
uri: '/ws/publicKeys?keyId=' + encodedKeyId,
|
||||||
expected: 200,
|
expected: 200,
|
||||||
success: function(privKey) {
|
success: function(pubKey) {
|
||||||
// import keys
|
getPrivateKey(pubKey);
|
||||||
self.importKeys(pubKey.asciiArmored, privKey.asciiArmored, email);
|
},
|
||||||
callback({ privateKey:privKey, publicKey: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) {
|
||||||
* Get the current user's private key
|
server.xhr({
|
||||||
*/
|
type: 'GET',
|
||||||
self.getPrivateKey = function() {
|
uri: '/ws/privateKeys?keyId=' + encodedKeyId,
|
||||||
if (!privateKey) { return undefined; }
|
expected: 200,
|
||||||
return privateKey.armored;
|
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!";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
}());
|
||||||
* 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
|
* This function needs to be implemented, since it is used by the openpgp utils
|
||||||
*/
|
*/
|
||||||
|
|
||||||
function showMessages(str) {}
|
function showMessages(str) {}
|
@ -1,153 +1,168 @@
|
|||||||
'use strict';
|
(function() {
|
||||||
|
'use strict';
|
||||||
|
|
||||||
app.crypto.Util = function(window, uuid) {
|
app.crypto.Util = function(window, uuid) {
|
||||||
|
|
||||||
/**
|
|
||||||
* Generates a new RFC 4122 version 4 compliant random UUID
|
|
||||||
*/
|
|
||||||
this.UUID = function() {
|
|
||||||
return uuid.v4();
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Generates a cryptographically secure random base64-encoded key or IV
|
|
||||||
* @param keySize [Number] The size of the key in bits (e.g. 128, 256)
|
|
||||||
* @return [String] The base64 encoded key/IV
|
|
||||||
*/
|
|
||||||
this.random = function(keySize) {
|
|
||||||
var keyBase64, keyBuf;
|
|
||||||
|
|
||||||
if (window.crypto && window.crypto.getRandomValues) {
|
|
||||||
keyBuf = new Uint8Array(keySize / 8);
|
|
||||||
window.crypto.getRandomValues(keyBuf);
|
|
||||||
keyBase64 = window.btoa(this.uint8Arr2BinStr(keyBuf));
|
|
||||||
} else {
|
|
||||||
sjcl.random.addEntropy((new Date()).valueOf(), 2, "calltime");
|
|
||||||
keyBuf = sjcl.random.randomWords(keySize / 32, 0);
|
|
||||||
keyBase64 = sjcl.codec.base64.fromBits(keyBuf);
|
|
||||||
}
|
|
||||||
|
|
||||||
return keyBase64;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Encrypt a list of items
|
|
||||||
* @param aes [Object] The object implementing the aes mode
|
|
||||||
* @list list [Array] The list of items to encrypt
|
|
||||||
*/
|
|
||||||
this.encryptList = function(aes, list) {
|
|
||||||
var i, json, ct, outList = [];
|
|
||||||
|
|
||||||
for (i = 0; i < list.length; i++) {
|
|
||||||
// stringify to JSON before encryption
|
|
||||||
json = JSON.stringify(list[i].plaintext);
|
|
||||||
ct = aes.encrypt(json, list[i].key, list[i].iv);
|
|
||||||
outList.push({ id:list[i].id, ciphertext:ct, key:list[i].key, iv:list[i].iv });
|
|
||||||
}
|
|
||||||
|
|
||||||
return outList;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Decrypt a list of items
|
|
||||||
* @param aes [Object] The object implementing the aes mode
|
|
||||||
* @list list [Array] The list of items to decrypt
|
|
||||||
*/
|
|
||||||
this.decryptList = function(aes, list) {
|
|
||||||
var i, json, pt, outList = [];
|
|
||||||
|
|
||||||
for (i = 0; i < list.length; i++) {
|
/**
|
||||||
// decrypt JSON and parse to object literal
|
* Generates a new RFC 4122 version 4 compliant random UUID
|
||||||
json = aes.decrypt(list[i].ciphertext, list[i].key, list[i].iv);
|
*/
|
||||||
pt = JSON.parse(json);
|
this.UUID = function() {
|
||||||
outList.push({ id:list[i].id, plaintext:pt, key:list[i].key, iv:list[i].iv });
|
return uuid.v4();
|
||||||
}
|
};
|
||||||
|
|
||||||
return outList;
|
/**
|
||||||
};
|
* Generates a cryptographically secure random base64-encoded key or IV
|
||||||
|
* @param keySize [Number] The size of the key in bits (e.g. 128, 256)
|
||||||
/**
|
* @return [String] The base64 encoded key/IV
|
||||||
* Parse a date string with the following format "1900-01-31 18:17:53"
|
*/
|
||||||
*/
|
this.random = function(keySize) {
|
||||||
this.parseDate = function(str) {
|
var keyBase64, keyBuf;
|
||||||
var parts = str.match(/(\d+)/g);
|
|
||||||
return new Date(parts[0], parts[1] - 1, parts[2], parts[3], parts[4], parts[5]);
|
if (window.crypto && window.crypto.getRandomValues) {
|
||||||
};
|
keyBuf = new Uint8Array(keySize / 8);
|
||||||
|
window.crypto.getRandomValues(keyBuf);
|
||||||
/**
|
keyBase64 = window.btoa(this.uint8Arr2BinStr(keyBuf));
|
||||||
* Converts a binary String (e.g. from the FileReader Api) to an ArrayBuffer
|
} else {
|
||||||
* @param str [String] a binary string with integer values (0..255) per character
|
sjcl.random.addEntropy((new Date()).valueOf(), 2, "calltime");
|
||||||
* @return [ArrayBuffer]
|
keyBuf = sjcl.random.randomWords(keySize / 32, 0);
|
||||||
*/
|
keyBase64 = sjcl.codec.base64.fromBits(keyBuf);
|
||||||
this.binStr2ArrBuf = function(str) {
|
}
|
||||||
var b = new ArrayBuffer(str.length);
|
|
||||||
var buf = new Uint8Array(b);
|
return keyBase64;
|
||||||
|
};
|
||||||
for(var i = 0; i < b.byteLength; i++){
|
|
||||||
buf[i] = str.charCodeAt(i);
|
/**
|
||||||
}
|
* Encrypt a list of items
|
||||||
|
* @param aes [Object] The object implementing the aes mode
|
||||||
return b;
|
* @list list [Array] The list of items to encrypt
|
||||||
};
|
*/
|
||||||
|
this.encryptList = function(aes, list) {
|
||||||
/**
|
var i, json, ct, outList = [];
|
||||||
* Creates a Blob from an ArrayBuffer using the BlobBuilder Api
|
|
||||||
* @param str [String] a binary string with integer values (0..255) per character
|
for (i = 0; i < list.length; i++) {
|
||||||
* @return [ArrayBuffer] either a data url or a filesystem url
|
// stringify to JSON before encryption
|
||||||
*/
|
json = JSON.stringify(list[i].plaintext);
|
||||||
this.arrBuf2Blob = function(buf, mimeType) {
|
ct = aes.encrypt(json, list[i].key, list[i].iv);
|
||||||
var b = new Uint8Array(buf);
|
outList.push({
|
||||||
var blob = new Blob([b], {type: mimeType});
|
id: list[i].id,
|
||||||
|
ciphertext: ct,
|
||||||
return blob;
|
key: list[i].key,
|
||||||
};
|
iv: list[i].iv
|
||||||
|
});
|
||||||
/**
|
}
|
||||||
* Creates a binary String from a Blob using the FileReader Api
|
|
||||||
* @param blob [Blob/File] a blob containing the the binary data
|
return outList;
|
||||||
* @return [String] a binary string with integer values (0..255) per character
|
};
|
||||||
*/
|
|
||||||
this.blob2BinStr = function(blob, callback) {
|
/**
|
||||||
var reader = new FileReader();
|
* Decrypt a list of items
|
||||||
|
* @param aes [Object] The object implementing the aes mode
|
||||||
reader.onload = function(event) {
|
* @list list [Array] The list of items to decrypt
|
||||||
callback(event.target.result);
|
*/
|
||||||
|
this.decryptList = function(aes, list) {
|
||||||
|
var i, json, pt, outList = [];
|
||||||
|
|
||||||
|
for (i = 0; i < list.length; i++) {
|
||||||
|
// decrypt JSON and parse to object literal
|
||||||
|
json = aes.decrypt(list[i].ciphertext, list[i].key, list[i].iv);
|
||||||
|
pt = JSON.parse(json);
|
||||||
|
outList.push({
|
||||||
|
id: list[i].id,
|
||||||
|
plaintext: pt,
|
||||||
|
key: list[i].key,
|
||||||
|
iv: list[i].iv
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return outList;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse a date string with the following format "1900-01-31 18:17:53"
|
||||||
|
*/
|
||||||
|
this.parseDate = function(str) {
|
||||||
|
var parts = str.match(/(\d+)/g);
|
||||||
|
return new Date(parts[0], parts[1] - 1, parts[2], parts[3], parts[4], parts[5]);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts a binary String (e.g. from the FileReader Api) to an ArrayBuffer
|
||||||
|
* @param str [String] a binary string with integer values (0..255) per character
|
||||||
|
* @return [ArrayBuffer]
|
||||||
|
*/
|
||||||
|
this.binStr2ArrBuf = function(str) {
|
||||||
|
var b = new ArrayBuffer(str.length);
|
||||||
|
var buf = new Uint8Array(b);
|
||||||
|
|
||||||
|
for (var i = 0; i < b.byteLength; i++) {
|
||||||
|
buf[i] = str.charCodeAt(i);
|
||||||
|
}
|
||||||
|
|
||||||
|
return b;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a Blob from an ArrayBuffer using the BlobBuilder Api
|
||||||
|
* @param str [String] a binary string with integer values (0..255) per character
|
||||||
|
* @return [ArrayBuffer] either a data url or a filesystem url
|
||||||
|
*/
|
||||||
|
this.arrBuf2Blob = function(buf, mimeType) {
|
||||||
|
var b = new Uint8Array(buf);
|
||||||
|
var blob = new Blob([b], {
|
||||||
|
type: mimeType
|
||||||
|
});
|
||||||
|
|
||||||
|
return blob;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a binary String from a Blob using the FileReader Api
|
||||||
|
* @param blob [Blob/File] a blob containing the the binary data
|
||||||
|
* @return [String] a binary string with integer values (0..255) per character
|
||||||
|
*/
|
||||||
|
this.blob2BinStr = function(blob, callback) {
|
||||||
|
var reader = new FileReader();
|
||||||
|
|
||||||
|
reader.onload = function(event) {
|
||||||
|
callback(event.target.result);
|
||||||
|
};
|
||||||
|
|
||||||
|
reader.readAsBinaryString(blob);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts an ArrayBuffer to a binary String. This is a slower alternative to
|
||||||
|
* conversion with arrBuf2Blob -> blob2BinStr, since these use native apis,
|
||||||
|
* but it can be used on browsers without the BlodBuilder Api
|
||||||
|
* @param buf [ArrayBuffer]
|
||||||
|
* @return [String] a binary string with integer values (0..255) per character
|
||||||
|
*/
|
||||||
|
this.arrBuf2BinStr = function(buf) {
|
||||||
|
var b = new Uint8Array(buf);
|
||||||
|
var str = '';
|
||||||
|
|
||||||
|
for (var i = 0; i < b.byteLength; i++) {
|
||||||
|
str += String.fromCharCode(b[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return str;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts a UInt8Array to a binary String.
|
||||||
|
* @param buf [UInt8Array]
|
||||||
|
* @return [String] a binary string with integer values (0..255) per character
|
||||||
|
*/
|
||||||
|
this.uint8Arr2BinStr = function(buf) {
|
||||||
|
var str = '';
|
||||||
|
|
||||||
|
for (var i = 0; i < buf.byteLength; i++) {
|
||||||
|
str += String.fromCharCode(buf[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return str;
|
||||||
};
|
};
|
||||||
|
|
||||||
reader.readAsBinaryString(blob);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
}());
|
||||||
* Converts an ArrayBuffer to a binary String. This is a slower alternative to
|
|
||||||
* conversion with arrBuf2Blob -> blob2BinStr, since these use native apis,
|
|
||||||
* but it can be used on browsers without the BlodBuilder Api
|
|
||||||
* @param buf [ArrayBuffer]
|
|
||||||
* @return [String] a binary string with integer values (0..255) per character
|
|
||||||
*/
|
|
||||||
this.arrBuf2BinStr = function(buf) {
|
|
||||||
var b = new Uint8Array(buf);
|
|
||||||
var str = '';
|
|
||||||
|
|
||||||
for(var i = 0; i < b.byteLength; i++){
|
|
||||||
str += String.fromCharCode(b[i]);
|
|
||||||
}
|
|
||||||
|
|
||||||
return str;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Converts a UInt8Array to a binary String.
|
|
||||||
* @param buf [UInt8Array]
|
|
||||||
* @return [String] a binary string with integer values (0..255) per character
|
|
||||||
*/
|
|
||||||
this.uint8Arr2BinStr = function(buf) {
|
|
||||||
var str = '';
|
|
||||||
|
|
||||||
for(var i = 0; i < buf.byteLength; i++){
|
|
||||||
str += String.fromCharCode(buf[i]);
|
|
||||||
}
|
|
||||||
|
|
||||||
return str;
|
|
||||||
};
|
|
||||||
|
|
||||||
};
|
|
@ -1,110 +1,131 @@
|
|||||||
'use strict';
|
(function() {
|
||||||
|
'use strict';
|
||||||
/**
|
|
||||||
* High level storage api for handling syncing of data to
|
|
||||||
* and from the cloud.
|
|
||||||
*/
|
|
||||||
app.dao.CloudStorage = function(window, $) {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Lists the encrypted items
|
|
||||||
* @param type [String] The type of item e.g. 'email'
|
|
||||||
* @param offset [Number] The offset of items to fetch (0 is the last stored item)
|
|
||||||
* @param num [Number] The number of items to fetch (null means fetch all)
|
|
||||||
*/
|
|
||||||
this.listEncryptedItems = function(type, emailAddress, folderName, callback) {
|
|
||||||
var folder, uri, self = this;
|
|
||||||
|
|
||||||
// fetch encrypted json objects from cloud service
|
|
||||||
uri = app.config.cloudUrl + '/' + type + '/user/' + emailAddress + '/folder/' + folderName;
|
|
||||||
$.ajax({
|
|
||||||
url: uri,
|
|
||||||
type: 'GET',
|
|
||||||
dataType: 'json',
|
|
||||||
success: function(list) {
|
|
||||||
callback(list);
|
|
||||||
},
|
|
||||||
error: function(xhr, textStatus, err) {
|
|
||||||
callback({error: err, status: textStatus});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Persist encrypted user key to cloud service
|
|
||||||
*/
|
|
||||||
this.persistUserSecretKey = function(emailAddress, callback) {
|
|
||||||
// fetch user's encrypted secret key from keychain/storage
|
|
||||||
var keyStore = new app.dao.LocalStorageDAO(window);
|
|
||||||
var storageId = emailAddress + '_encryptedSymmetricKey';
|
|
||||||
var encryptedKey = keyStore.read(storageId);
|
|
||||||
|
|
||||||
var payload = {
|
|
||||||
userId: emailAddress,
|
|
||||||
encryptedKey: encryptedKey.key,
|
|
||||||
keyIV: encryptedKey.iv
|
|
||||||
};
|
|
||||||
|
|
||||||
var uri = app.config.cloudUrl + '/keys/user/' + emailAddress;
|
|
||||||
$.ajax({
|
|
||||||
url: uri,
|
|
||||||
type: 'PUT',
|
|
||||||
data: JSON.stringify(payload),
|
|
||||||
contentType: 'application/json',
|
|
||||||
success: function() {
|
|
||||||
callback();
|
|
||||||
},
|
|
||||||
error: function(xhr, textStatus, err) {
|
|
||||||
callback({error: err, status: textStatus});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get encrypted user key from cloud service
|
* High level storage api for handling syncing of data to
|
||||||
|
* and from the cloud.
|
||||||
*/
|
*/
|
||||||
this.getUserSecretKey = function(emailAddress, callback, replaceCallback) {
|
app.dao.CloudStorage = function(window, $) {
|
||||||
// fetch user's encrypted secret key from keychain/storage
|
|
||||||
var self = this;
|
|
||||||
var keyStore = new app.dao.LocalStorageDAO(window);
|
|
||||||
var storageId = emailAddress + '_encryptedSymmetricKey';
|
|
||||||
var storedKey = keyStore.read(storageId);
|
|
||||||
|
|
||||||
var uri = app.config.cloudUrl + '/keys/user/' + emailAddress;
|
/**
|
||||||
$.ajax({
|
* Lists the encrypted items
|
||||||
url: uri,
|
* @param type [String] The type of item e.g. 'email'
|
||||||
type: 'GET',
|
* @param offset [Number] The offset of items to fetch (0 is the last stored item)
|
||||||
dataType: 'json',
|
* @param num [Number] The number of items to fetch (null means fetch all)
|
||||||
success: function(fetchedKey) {
|
*/
|
||||||
if ((!storedKey || !storedKey.key) && fetchedKey && fetchedKey.encryptedKey && fetchedKey.keyIV) {
|
this.listEncryptedItems = function(type, emailAddress, folderName, callback) {
|
||||||
// no local key... persist fetched key
|
var folder, uri, self = this;
|
||||||
keyStore.persist(storageId, { key: fetchedKey.encryptedKey, iv: fetchedKey.keyIV });
|
|
||||||
replaceCallback();
|
// fetch encrypted json objects from cloud service
|
||||||
|
uri = app.config.cloudUrl + '/' + type + '/user/' + emailAddress + '/folder/' + folderName;
|
||||||
} else if (storedKey && fetchedKey && (storedKey.key !== fetchedKey.encryptedKey || storedKey.iv !== fetchedKey.keyIV)){
|
$.ajax({
|
||||||
// local and fetched keys are not equal
|
url: uri,
|
||||||
if (confirm('Swap local key?')) {
|
type: 'GET',
|
||||||
// replace local key with fetched key
|
dataType: 'json',
|
||||||
keyStore.persist(storageId, { key: fetchedKey.encryptedKey, iv: fetchedKey.keyIV });
|
success: function(list) {
|
||||||
replaceCallback();
|
callback(list);
|
||||||
} else {
|
},
|
||||||
if (confirm('Swap cloud key?')) {
|
error: function(xhr, textStatus, err) {
|
||||||
// upload local key to cloud
|
callback({
|
||||||
self.persistUserSecretKey(emailAddress, callback);
|
error: err,
|
||||||
} else {
|
status: textStatus
|
||||||
callback({error: 'err', status: 'Key not synced!'});
|
});
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
} else {
|
|
||||||
// local and cloud keys are equal or cloud key is null
|
|
||||||
callback();
|
|
||||||
}
|
}
|
||||||
},
|
});
|
||||||
error: function(xhr, textStatus, err) {
|
};
|
||||||
callback({error: err, status: textStatus});
|
|
||||||
}
|
/**
|
||||||
});
|
* Persist encrypted user key to cloud service
|
||||||
|
*/
|
||||||
|
this.persistUserSecretKey = function(emailAddress, callback) {
|
||||||
|
// fetch user's encrypted secret key from keychain/storage
|
||||||
|
var keyStore = new app.dao.LocalStorageDAO(window);
|
||||||
|
var storageId = emailAddress + '_encryptedSymmetricKey';
|
||||||
|
var encryptedKey = keyStore.read(storageId);
|
||||||
|
|
||||||
|
var payload = {
|
||||||
|
userId: emailAddress,
|
||||||
|
encryptedKey: encryptedKey.key,
|
||||||
|
keyIV: encryptedKey.iv
|
||||||
|
};
|
||||||
|
|
||||||
|
var uri = app.config.cloudUrl + '/keys/user/' + emailAddress;
|
||||||
|
$.ajax({
|
||||||
|
url: uri,
|
||||||
|
type: 'PUT',
|
||||||
|
data: JSON.stringify(payload),
|
||||||
|
contentType: 'application/json',
|
||||||
|
success: function() {
|
||||||
|
callback();
|
||||||
|
},
|
||||||
|
error: function(xhr, textStatus, err) {
|
||||||
|
callback({
|
||||||
|
error: err,
|
||||||
|
status: textStatus
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get encrypted user key from cloud service
|
||||||
|
*/
|
||||||
|
this.getUserSecretKey = function(emailAddress, callback, replaceCallback) {
|
||||||
|
// fetch user's encrypted secret key from keychain/storage
|
||||||
|
var self = this;
|
||||||
|
var keyStore = new app.dao.LocalStorageDAO(window);
|
||||||
|
var storageId = emailAddress + '_encryptedSymmetricKey';
|
||||||
|
var storedKey = keyStore.read(storageId);
|
||||||
|
|
||||||
|
var uri = app.config.cloudUrl + '/keys/user/' + emailAddress;
|
||||||
|
$.ajax({
|
||||||
|
url: uri,
|
||||||
|
type: 'GET',
|
||||||
|
dataType: 'json',
|
||||||
|
success: function(fetchedKey) {
|
||||||
|
if ((!storedKey || !storedKey.key) && fetchedKey && fetchedKey.encryptedKey && fetchedKey.keyIV) {
|
||||||
|
// no local key... persist fetched key
|
||||||
|
keyStore.persist(storageId, {
|
||||||
|
key: fetchedKey.encryptedKey,
|
||||||
|
iv: fetchedKey.keyIV
|
||||||
|
});
|
||||||
|
replaceCallback();
|
||||||
|
|
||||||
|
} else if (storedKey && fetchedKey && (storedKey.key !== fetchedKey.encryptedKey || storedKey.iv !== fetchedKey.keyIV)) {
|
||||||
|
// local and fetched keys are not equal
|
||||||
|
if (confirm('Swap local key?')) {
|
||||||
|
// replace local key with fetched key
|
||||||
|
keyStore.persist(storageId, {
|
||||||
|
key: fetchedKey.encryptedKey,
|
||||||
|
iv: fetchedKey.keyIV
|
||||||
|
});
|
||||||
|
replaceCallback();
|
||||||
|
} else {
|
||||||
|
if (confirm('Swap cloud key?')) {
|
||||||
|
// upload local key to cloud
|
||||||
|
self.persistUserSecretKey(emailAddress, callback);
|
||||||
|
} else {
|
||||||
|
callback({
|
||||||
|
error: 'err',
|
||||||
|
status: 'Key not synced!'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// local and cloud keys are equal or cloud key is null
|
||||||
|
callback();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
error: function(xhr, textStatus, err) {
|
||||||
|
callback({
|
||||||
|
error: err,
|
||||||
|
status: textStatus
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
};
|
}());
|
@ -1,63 +1,69 @@
|
|||||||
'use strict';
|
(function() {
|
||||||
|
'use strict';
|
||||||
|
|
||||||
/**
|
|
||||||
* High level storage api that handles all persistence on the device. If
|
|
||||||
* SQLcipher/SQLite is available, all data is securely persisted there,
|
|
||||||
* through transparent encryption. If not, the crypto API is
|
|
||||||
* used to encrypt data on the fly before persisting via a JSON store.
|
|
||||||
*/
|
|
||||||
app.dao.DeviceStorage = function(util, crypto, jsonDao, sqlcipherDao) {
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Stores a list of encrypted items in the object store
|
* High level storage api that handles all persistence on the device. If
|
||||||
* @param list [Array] The list of items to be persisted
|
* SQLcipher/SQLite is available, all data is securely persisted there,
|
||||||
* @param type [String] The type of item to be persisted e.g. 'email'
|
* through transparent encryption. If not, the crypto API is
|
||||||
|
* used to encrypt data on the fly before persisting via a JSON store.
|
||||||
*/
|
*/
|
||||||
this.storeEcryptedList = function(list, type, callback) {
|
app.dao.DeviceStorage = function(util, crypto, jsonDao, sqlcipherDao) {
|
||||||
var i, date, key, items = [];
|
|
||||||
|
/**
|
||||||
// format items for batch storing in dao
|
* Stores a list of encrypted items in the object store
|
||||||
for (i = 0; i < list.length; i++) {
|
* @param list [Array] The list of items to be persisted
|
||||||
|
* @param type [String] The type of item to be persisted e.g. 'email'
|
||||||
// put date in key if available... for easy querying
|
*/
|
||||||
if (list[i].sentDate) {
|
this.storeEcryptedList = function(list, type, callback) {
|
||||||
date = util.parseDate(list[i].sentDate);
|
var i, date, key, items = [];
|
||||||
key = crypto.emailAddress + '_' + type + '_' + date.getTime() + '_' + list[i].id;
|
|
||||||
} else {
|
// format items for batch storing in dao
|
||||||
key = crypto.emailAddress + '_' + type + '_' + list[i].id;
|
for (i = 0; i < list.length; i++) {
|
||||||
}
|
|
||||||
|
// put date in key if available... for easy querying
|
||||||
items.push({ key:key, object:list[i] });
|
if (list[i].sentDate) {
|
||||||
}
|
date = util.parseDate(list[i].sentDate);
|
||||||
|
key = crypto.emailAddress + '_' + type + '_' + date.getTime() + '_' + list[i].id;
|
||||||
jsonDao.batch(items, function() {
|
} else {
|
||||||
callback();
|
key = crypto.emailAddress + '_' + type + '_' + list[i].id;
|
||||||
});
|
}
|
||||||
};
|
|
||||||
|
items.push({
|
||||||
/**
|
key: key,
|
||||||
* Decrypts the stored items of a given type and returns them
|
object: list[i]
|
||||||
* @param type [String] The type of item e.g. 'email'
|
});
|
||||||
* @param offset [Number] The offset of items to fetch (0 is the last stored item)
|
}
|
||||||
* @param num [Number] The number of items to fetch (null means fetch all)
|
|
||||||
*/
|
jsonDao.batch(items, function() {
|
||||||
this.listItems = function(type, offset, num, callback) {
|
callback();
|
||||||
|
});
|
||||||
// fetch all items of a certain type from the data-store
|
};
|
||||||
jsonDao.list(crypto.emailAddress + '_' + type, offset, num, function(encryptedList) {
|
|
||||||
|
/**
|
||||||
|
* Decrypts the stored items of a given type and returns them
|
||||||
|
* @param type [String] The type of item e.g. 'email'
|
||||||
|
* @param offset [Number] The offset of items to fetch (0 is the last stored item)
|
||||||
|
* @param num [Number] The number of items to fetch (null means fetch all)
|
||||||
|
*/
|
||||||
|
this.listItems = function(type, offset, num, callback) {
|
||||||
|
|
||||||
|
// fetch all items of a certain type from the data-store
|
||||||
|
jsonDao.list(crypto.emailAddress + '_' + type, offset, num, function(encryptedList) {
|
||||||
|
|
||||||
|
// decrypt list
|
||||||
|
crypto.aesDecryptListForUser(encryptedList, function(decryptedList) {
|
||||||
|
callback(decryptedList);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clear the whole device data-store
|
||||||
|
*/
|
||||||
|
this.clear = function(callback) {
|
||||||
|
jsonDao.clear(callback);
|
||||||
|
};
|
||||||
|
|
||||||
// decrypt list
|
|
||||||
crypto.aesDecryptListForUser(encryptedList, function(decryptedList) {
|
|
||||||
callback(decryptedList);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
}());
|
||||||
* Clear the whole device data-store
|
|
||||||
*/
|
|
||||||
this.clear = function(callback) {
|
|
||||||
jsonDao.clear(callback);
|
|
||||||
};
|
|
||||||
|
|
||||||
};
|
|
@ -1,110 +1,121 @@
|
|||||||
'use strict';
|
(function() {
|
||||||
|
'use strict';
|
||||||
|
|
||||||
/**
|
|
||||||
* A high-level Data-Access Api for handling Email synchronization
|
|
||||||
* between the cloud service and the device's local storage
|
|
||||||
*/
|
|
||||||
app.dao.EmailDAO = function(_, crypto, devicestorage, cloudstorage) {
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Inits all dependencies
|
* A high-level Data-Access Api for handling Email synchronization
|
||||||
|
* between the cloud service and the device's local storage
|
||||||
*/
|
*/
|
||||||
this.init = function(account, password, callback) {
|
app.dao.EmailDAO = function(_, crypto, devicestorage, cloudstorage) {
|
||||||
this.account = account;
|
|
||||||
|
/**
|
||||||
// sync user's cloud key with local storage
|
* Inits all dependencies
|
||||||
cloudstorage.getUserSecretKey(account.get('emailAddress'), function(err) {
|
*/
|
||||||
if (err) {
|
this.init = function(account, password, callback) {
|
||||||
console.log('Could not sync user key from server: ' + JSON.stringify(err));
|
this.account = account;
|
||||||
}
|
|
||||||
// init crypto
|
// sync user's cloud key with local storage
|
||||||
initCrypto();
|
cloudstorage.getUserSecretKey(account.get('emailAddress'), function(err) {
|
||||||
|
if (err) {
|
||||||
}, function() {
|
console.log('Could not sync user key from server: ' + JSON.stringify(err));
|
||||||
// replaced local key with cloud key... whipe local storage
|
}
|
||||||
devicestorage.clear(function() {
|
// init crypto
|
||||||
initCrypto();
|
initCrypto();
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
function initCrypto() {
|
|
||||||
crypto.init(account.get('emailAddress'), password, account.get('symKeySize'), account.get('symIvSize'), function() {
|
|
||||||
callback();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Fetch an email with the following id
|
|
||||||
*/
|
|
||||||
this.getItem = function(folderName, itemId) {
|
|
||||||
var folder = this.account.get('folders').where({name: folderName})[0];
|
|
||||||
var mail = _.find(folder.get('items').models, function(email) {
|
|
||||||
return email.id+'' === itemId+'';
|
|
||||||
});
|
|
||||||
return mail;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Fetch a list of emails from the device's local storage
|
|
||||||
* @param offset [Number] The offset of items to fetch (0 is the last stored item)
|
|
||||||
* @param num [Number] The number of items to fetch (null means fetch all)
|
|
||||||
*/
|
|
||||||
this.listItems = function(folderName, offset, num, callback) {
|
|
||||||
var collection, folder, self = this;
|
|
||||||
|
|
||||||
// check if items are in memory already (account.folders model)
|
|
||||||
folder = this.account.get('folders').where({name: folderName})[0];
|
|
||||||
|
|
||||||
if (!folder) {
|
|
||||||
// get items from storage
|
|
||||||
devicestorage.listItems('email_' + folderName ,offset ,num, function(decryptedList) {
|
|
||||||
// parse to backbone model collection
|
|
||||||
collection = new app.model.EmailCollection(decryptedList);
|
|
||||||
|
|
||||||
// cache collection in folder memory
|
}, function() {
|
||||||
if (decryptedList.length > 0) {
|
// replaced local key with cloud key... whipe local storage
|
||||||
folder = new app.model.Folder({name: folderName});
|
devicestorage.clear(function() {
|
||||||
folder.set('items', collection);
|
initCrypto();
|
||||||
self.account.get('folders').add(folder);
|
});
|
||||||
}
|
|
||||||
|
|
||||||
callback(collection);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
} else {
|
function initCrypto() {
|
||||||
// read items from memory
|
crypto.init(account.get('emailAddress'), password, account.get('symKeySize'), account.get('symIvSize'), function() {
|
||||||
collection = folder.get('items');
|
callback();
|
||||||
callback(collection);
|
});
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Synchronize a folder's items from the cloud to the device-storage
|
|
||||||
* @param folderName [String] The name of the folder e.g. 'inbox'
|
|
||||||
*/
|
|
||||||
this.syncFromCloud = function(folderName, callback) {
|
|
||||||
var folder, self = this;
|
|
||||||
|
|
||||||
cloudstorage.listEncryptedItems('email', this.account.get('emailAddress'), folderName, function(res) {
|
|
||||||
// return if an error occured or if fetched list from cloud storage is empty
|
|
||||||
if (!res || res.status || res.length === 0) {
|
|
||||||
callback(res); // error
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
};
|
||||||
// TODO: remove old folder items from devicestorage
|
|
||||||
|
/**
|
||||||
// persist encrypted list in device storage
|
* Fetch an email with the following id
|
||||||
devicestorage.storeEcryptedList(res, 'email_' + folderName, function() {
|
*/
|
||||||
// remove cached folder in account model
|
this.getItem = function(folderName, itemId) {
|
||||||
folder = self.account.get('folders').where({name: folderName})[0];
|
var folder = this.account.get('folders').where({
|
||||||
if (folder) {
|
name: folderName
|
||||||
self.account.get('folders').remove(folder);
|
})[0];
|
||||||
}
|
var mail = _.find(folder.get('items').models, function(email) {
|
||||||
callback();
|
return email.id + '' === itemId + '';
|
||||||
});
|
});
|
||||||
});
|
return mail;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch a list of emails from the device's local storage
|
||||||
|
* @param offset [Number] The offset of items to fetch (0 is the last stored item)
|
||||||
|
* @param num [Number] The number of items to fetch (null means fetch all)
|
||||||
|
*/
|
||||||
|
this.listItems = function(folderName, offset, num, callback) {
|
||||||
|
var collection, folder, self = this;
|
||||||
|
|
||||||
|
// check if items are in memory already (account.folders model)
|
||||||
|
folder = this.account.get('folders').where({
|
||||||
|
name: folderName
|
||||||
|
})[0];
|
||||||
|
|
||||||
|
if (!folder) {
|
||||||
|
// get items from storage
|
||||||
|
devicestorage.listItems('email_' + folderName, offset, num, function(decryptedList) {
|
||||||
|
// parse to backbone model collection
|
||||||
|
collection = new app.model.EmailCollection(decryptedList);
|
||||||
|
|
||||||
|
// cache collection in folder memory
|
||||||
|
if (decryptedList.length > 0) {
|
||||||
|
folder = new app.model.Folder({
|
||||||
|
name: folderName
|
||||||
|
});
|
||||||
|
folder.set('items', collection);
|
||||||
|
self.account.get('folders').add(folder);
|
||||||
|
}
|
||||||
|
|
||||||
|
callback(collection);
|
||||||
|
});
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// read items from memory
|
||||||
|
collection = folder.get('items');
|
||||||
|
callback(collection);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Synchronize a folder's items from the cloud to the device-storage
|
||||||
|
* @param folderName [String] The name of the folder e.g. 'inbox'
|
||||||
|
*/
|
||||||
|
this.syncFromCloud = function(folderName, callback) {
|
||||||
|
var folder, self = this;
|
||||||
|
|
||||||
|
cloudstorage.listEncryptedItems('email', this.account.get('emailAddress'), folderName, function(res) {
|
||||||
|
// return if an error occured or if fetched list from cloud storage is empty
|
||||||
|
if (!res || res.status || res.length === 0) {
|
||||||
|
callback(res); // error
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: remove old folder items from devicestorage
|
||||||
|
|
||||||
|
// persist encrypted list in device storage
|
||||||
|
devicestorage.storeEcryptedList(res, 'email_' + folderName, function() {
|
||||||
|
// remove cached folder in account model
|
||||||
|
folder = self.account.get('folders').where({
|
||||||
|
name: folderName
|
||||||
|
})[0];
|
||||||
|
if (folder) {
|
||||||
|
self.account.get('folders').remove(folder);
|
||||||
|
}
|
||||||
|
callback();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
};
|
}());
|
@ -1,73 +1,74 @@
|
|||||||
'use strict';
|
(function() {
|
||||||
|
'use strict';
|
||||||
|
|
||||||
/**
|
|
||||||
* Handles generic caching of JSON objects in a lawnchair adapter
|
|
||||||
*/
|
|
||||||
app.dao.LawnchairDAO = function(window) {
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create or update an object
|
* Handles generic caching of JSON objects in a lawnchair adapter
|
||||||
*/
|
*/
|
||||||
this.persist = function(key, object, callback) {
|
app.dao.LawnchairDAO = function(window) {
|
||||||
Lawnchair(function() {
|
|
||||||
this.save({ key:key, object:object }, callback);
|
var db = new Lawnchair({
|
||||||
});
|
name: 'data-store'
|
||||||
};
|
}, function() {});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Persist a bunch of items at once
|
* Create or update an object
|
||||||
*/
|
*/
|
||||||
this.batch = function(list, callback) {
|
this.persist = function(key, object, callback) {
|
||||||
Lawnchair(function() {
|
db.save({
|
||||||
this.batch(list, callback);
|
key: key,
|
||||||
});
|
object: object
|
||||||
};
|
}, callback);
|
||||||
|
};
|
||||||
/**
|
|
||||||
* Read a single item by its key
|
/**
|
||||||
*/
|
* Persist a bunch of items at once
|
||||||
this.read = function(key, callback) {
|
*/
|
||||||
Lawnchair(function() {
|
this.batch = function(list, callback) {
|
||||||
this.get(key, function(o) {
|
db.batch(list, callback);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read a single item by its key
|
||||||
|
*/
|
||||||
|
this.read = function(key, callback) {
|
||||||
|
db.get(key, function(o) {
|
||||||
if (o) {
|
if (o) {
|
||||||
callback(o.object);
|
callback(o.object);
|
||||||
} else {
|
} else {
|
||||||
callback(null);
|
callback(null);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
};
|
||||||
};
|
|
||||||
|
/**
|
||||||
/**
|
* List all the items of a certain type
|
||||||
* List all the items of a certain type
|
* @param type [String] The type of item e.g. 'email'
|
||||||
* @param type [String] The type of item e.g. 'email'
|
* @param offset [Number] The offset of items to fetch (0 is the last stored item)
|
||||||
* @param offset [Number] The offset of items to fetch (0 is the last stored item)
|
* @param num [Number] The number of items to fetch (null means fetch all)
|
||||||
* @param num [Number] The number of items to fetch (null means fetch all)
|
*/
|
||||||
*/
|
this.list = function(type, offset, num, callback) {
|
||||||
this.list = function(type, offset, num, callback) {
|
var i, list = [],
|
||||||
var i, list = [], matchingKeys = [], parts, timeStr, time;
|
matchingKeys = [],
|
||||||
|
parts, timeStr, time;
|
||||||
Lawnchair(function() {
|
|
||||||
var self = this;
|
|
||||||
|
|
||||||
// get all keys
|
// get all keys
|
||||||
this.keys(function(keys) {
|
db.keys(function(keys) {
|
||||||
|
|
||||||
// check if key begins with type
|
// check if key begins with type
|
||||||
for (i = 0; i < keys.length; i++) {
|
for (i = 0; i < keys.length; i++) {
|
||||||
if (keys[i].indexOf(type) === 0) {
|
if (keys[i].indexOf(type) === 0) {
|
||||||
matchingKeys.push(keys[i]);
|
matchingKeys.push(keys[i]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// sort keys by type and date
|
// sort keys by type and date
|
||||||
matchingKeys = _.sortBy(matchingKeys, function(key) {
|
matchingKeys = _.sortBy(matchingKeys, function(key) {
|
||||||
parts = key.split('_');
|
parts = key.split('_');
|
||||||
timeStr = parts[parts.length-2];
|
timeStr = parts[parts.length - 2];
|
||||||
time = parseInt(timeStr, 10);
|
time = parseInt(timeStr, 10);
|
||||||
return time;
|
return time;
|
||||||
});
|
});
|
||||||
|
|
||||||
// if num is null, list all items
|
// if num is null, list all items
|
||||||
num = (num !== null) ? num : matchingKeys.length;
|
num = (num !== null) ? num : matchingKeys.length;
|
||||||
|
|
||||||
@ -78,44 +79,41 @@ app.dao.LawnchairDAO = function(window) {
|
|||||||
matchingKeys = matchingKeys.splice(0, matchingKeys.length - offset);
|
matchingKeys = matchingKeys.splice(0, matchingKeys.length - offset);
|
||||||
} else {
|
} else {
|
||||||
matchingKeys = [];
|
matchingKeys = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
// return if there are no matching keys
|
// return if there are no matching keys
|
||||||
if (matchingKeys.length === 0) {
|
if (matchingKeys.length === 0) {
|
||||||
callback(list);
|
callback(list);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// fetch all items from data-store with matching key
|
// fetch all items from data-store with matching key
|
||||||
self.get(matchingKeys, function(matchingList) {
|
db.get(matchingKeys, function(matchingList) {
|
||||||
for (i = 0; i < matchingList.length; i++) {
|
for (i = 0; i < matchingList.length; i++) {
|
||||||
list.push(matchingList[i].object);
|
list.push(matchingList[i].object);
|
||||||
}
|
}
|
||||||
|
|
||||||
// return only the interval between offset and num
|
// return only the interval between offset and num
|
||||||
callback(list);
|
callback(list);
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
});
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes an object liter from local storage by its key (delete)
|
||||||
|
*/
|
||||||
|
this.remove = function(key, callback) {
|
||||||
|
db.remove(key, callback);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clears the whole local storage cache
|
||||||
|
*/
|
||||||
|
this.clear = function(callback) {
|
||||||
|
db.nuke(callback);
|
||||||
|
};
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
}());
|
||||||
* Removes an object liter from local storage by its key (delete)
|
|
||||||
*/
|
|
||||||
this.remove = function(key, callback) {
|
|
||||||
Lawnchair(function() {
|
|
||||||
this.remove(key, callback);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Clears the whole local storage cache
|
|
||||||
*/
|
|
||||||
this.clear = function(callback) {
|
|
||||||
Lawnchair(function() {
|
|
||||||
this.nuke(callback);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
};
|
|
@ -1,56 +1,59 @@
|
|||||||
'use strict';
|
(function() {
|
||||||
|
'use strict';
|
||||||
|
|
||||||
/**
|
|
||||||
* Handles generic caching of JSON objects in LocalStorage
|
|
||||||
*/
|
|
||||||
app.dao.LocalStorageDAO = function(window) {
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Stringifies an object literal to JSON and perists it
|
* Handles generic caching of JSON objects in LocalStorage
|
||||||
*/
|
*/
|
||||||
this.persist = function(key, object) {
|
app.dao.LocalStorageDAO = function(window) {
|
||||||
var json = JSON.stringify(object);
|
|
||||||
window.localStorage.setItem(key, json);
|
/**
|
||||||
};
|
* Stringifies an object literal to JSON and perists it
|
||||||
|
*/
|
||||||
/**
|
this.persist = function(key, object) {
|
||||||
* Fetches a json string from local storage by its key and parses it to an object literal
|
var json = JSON.stringify(object);
|
||||||
*/
|
window.localStorage.setItem(key, json);
|
||||||
this.read = function(key) {
|
};
|
||||||
var json = window.localStorage.getItem(key);
|
|
||||||
return JSON.parse(json);
|
/**
|
||||||
};
|
* Fetches a json string from local storage by its key and parses it to an object literal
|
||||||
|
*/
|
||||||
/**
|
this.read = function(key) {
|
||||||
* List all the items of a certain type in local storage
|
var json = window.localStorage.getItem(key);
|
||||||
* @param type [String] The type of item e.g. 'email'
|
return JSON.parse(json);
|
||||||
*/
|
};
|
||||||
this.list = function(type) {
|
|
||||||
var i, key, json, list = [];
|
/**
|
||||||
|
* List all the items of a certain type in local storage
|
||||||
for (i = 0; i < window.localStorage.length; i++) {
|
* @param type [String] The type of item e.g. 'email'
|
||||||
key = window.localStorage.key(i);
|
*/
|
||||||
if (key.indexOf(type) === 0) {
|
this.list = function(type) {
|
||||||
json = window.localStorage.getItem(key);
|
var i, key, json, list = [];
|
||||||
list.push(JSON.parse(json));
|
|
||||||
|
for (i = 0; i < window.localStorage.length; i++) {
|
||||||
|
key = window.localStorage.key(i);
|
||||||
|
if (key.indexOf(type) === 0) {
|
||||||
|
json = window.localStorage.getItem(key);
|
||||||
|
list.push(JSON.parse(json));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
return list;
|
||||||
return list;
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes an object liter from local storage by its key (delete)
|
||||||
|
*/
|
||||||
|
this.remove = function(key) {
|
||||||
|
window.localStorage.removeItem(key);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clears the whole local storage cache
|
||||||
|
*/
|
||||||
|
this.clear = function() {
|
||||||
|
window.localStorage.clear();
|
||||||
|
};
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
}());
|
||||||
* Removes an object liter from local storage by its key (delete)
|
|
||||||
*/
|
|
||||||
this.remove = function(key) {
|
|
||||||
window.localStorage.removeItem(key);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Clears the whole local storage cache
|
|
||||||
*/
|
|
||||||
this.clear = function() {
|
|
||||||
window.localStorage.clear();
|
|
||||||
};
|
|
||||||
|
|
||||||
};
|
|
@ -1,25 +1,27 @@
|
|||||||
'use strict';
|
(function() {
|
||||||
|
'use strict';
|
||||||
|
|
||||||
app.model.Account = Backbone.Model.extend({
|
app.model.Account = Backbone.Model.extend({
|
||||||
|
|
||||||
defaults: {
|
|
||||||
emailAddress: null,
|
|
||||||
symKeySize: null,
|
|
||||||
symIvSize: null,
|
|
||||||
folders: null
|
|
||||||
},
|
|
||||||
|
|
||||||
initialize: function () {
|
defaults: {
|
||||||
this.set('folders', new app.model.FolderCollection());
|
emailAddress: null,
|
||||||
}
|
symKeySize: null,
|
||||||
|
symIvSize: null,
|
||||||
|
folders: null
|
||||||
|
},
|
||||||
|
|
||||||
});
|
initialize: function() {
|
||||||
|
this.set('folders', new app.model.FolderCollection());
|
||||||
|
}
|
||||||
|
|
||||||
app.model.AccountCollection = Backbone.Collection.extend({
|
});
|
||||||
|
|
||||||
model: app.model.Account,
|
app.model.AccountCollection = Backbone.Collection.extend({
|
||||||
|
|
||||||
findByName: function (key) {
|
model: app.model.Account,
|
||||||
}
|
|
||||||
|
|
||||||
});
|
findByName: function(key) {}
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
}());
|
@ -1,35 +1,37 @@
|
|||||||
'use strict';
|
(function() {
|
||||||
|
'use strict';
|
||||||
|
|
||||||
app.model.Email = Backbone.Model.extend({
|
app.model.Email = Backbone.Model.extend({
|
||||||
|
|
||||||
defaults: {
|
|
||||||
id: null,
|
|
||||||
from: null,
|
|
||||||
to: [],
|
|
||||||
cc: [],
|
|
||||||
bcc: [],
|
|
||||||
subject: null,
|
|
||||||
body: null,
|
|
||||||
sentDate: null
|
|
||||||
},
|
|
||||||
|
|
||||||
initialize: function () {
|
defaults: {
|
||||||
// decode body
|
id: null,
|
||||||
try {
|
from: null,
|
||||||
var decodedBody = window.atob(this.get('body'));
|
to: [],
|
||||||
this.set('body', decodedBody);
|
cc: [],
|
||||||
} catch (ex) {
|
bcc: [],
|
||||||
console.log(ex);
|
subject: null,
|
||||||
|
body: null,
|
||||||
|
sentDate: null
|
||||||
|
},
|
||||||
|
|
||||||
|
initialize: function() {
|
||||||
|
// decode body
|
||||||
|
try {
|
||||||
|
var decodedBody = window.atob(this.get('body'));
|
||||||
|
this.set('body', decodedBody);
|
||||||
|
} catch (ex) {
|
||||||
|
console.log(ex);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
app.model.EmailCollection = Backbone.Collection.extend({
|
app.model.EmailCollection = Backbone.Collection.extend({
|
||||||
|
|
||||||
model: app.model.Email,
|
model: app.model.Email,
|
||||||
|
|
||||||
findByName: function (key) {
|
findByName: function(key) {}
|
||||||
}
|
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
}());
|
@ -1,22 +1,23 @@
|
|||||||
'use strict';
|
(function() {
|
||||||
|
'use strict';
|
||||||
|
|
||||||
app.model.Folder = Backbone.Model.extend({
|
app.model.Folder = Backbone.Model.extend({
|
||||||
|
|
||||||
defaults: {
|
|
||||||
name: null,
|
|
||||||
items: null
|
|
||||||
},
|
|
||||||
|
|
||||||
initialize: function () {
|
defaults: {
|
||||||
}
|
name: null,
|
||||||
|
items: null
|
||||||
|
},
|
||||||
|
|
||||||
});
|
initialize: function() {}
|
||||||
|
|
||||||
app.model.FolderCollection = Backbone.Collection.extend({
|
});
|
||||||
|
|
||||||
model: app.model.Folder,
|
app.model.FolderCollection = Backbone.Collection.extend({
|
||||||
|
|
||||||
findByName: function (key) {
|
model: app.model.Folder,
|
||||||
}
|
|
||||||
|
|
||||||
});
|
findByName: function(key) {}
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
}());
|
@ -1,13 +1,16 @@
|
|||||||
'use strict';
|
(function() {
|
||||||
|
'use strict';
|
||||||
|
|
||||||
app.view.AccountsView = Backbone.View.extend({
|
app.view.AccountsView = Backbone.View.extend({
|
||||||
|
|
||||||
initialize:function () {
|
initialize: function() {
|
||||||
this.template = _.template(app.util.tpl.get('accounts'));
|
this.template = _.template(app.util.tpl.get('accounts'));
|
||||||
},
|
},
|
||||||
|
|
||||||
render:function (eventName) {
|
render: function(eventName) {
|
||||||
$(this.el).html(this.template());
|
$(this.el).html(this.template());
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
}());
|
@ -1,13 +1,16 @@
|
|||||||
'use strict';
|
(function() {
|
||||||
|
'use strict';
|
||||||
|
|
||||||
app.view.ComposeView = Backbone.View.extend({
|
app.view.ComposeView = Backbone.View.extend({
|
||||||
|
|
||||||
initialize:function () {
|
initialize: function() {
|
||||||
this.template = _.template(app.util.tpl.get('compose'));
|
this.template = _.template(app.util.tpl.get('compose'));
|
||||||
},
|
},
|
||||||
|
|
||||||
render:function (eventName) {
|
render: function(eventName) {
|
||||||
$(this.el).html(this.template());
|
$(this.el).html(this.template());
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
}());
|
@ -1,23 +1,26 @@
|
|||||||
'use strict';
|
(function() {
|
||||||
|
'use strict';
|
||||||
|
|
||||||
app.view.FolderListView = Backbone.View.extend({
|
app.view.FolderListView = Backbone.View.extend({
|
||||||
|
|
||||||
initialize:function () {
|
initialize: function() {
|
||||||
this.template = _.template(app.util.tpl.get('folderlist'));
|
this.template = _.template(app.util.tpl.get('folderlist'));
|
||||||
},
|
},
|
||||||
|
|
||||||
render:function (eventName) {
|
render: function(eventName) {
|
||||||
var page = $(this.el);
|
var page = $(this.el);
|
||||||
|
|
||||||
page.html(this.template(this.options));
|
|
||||||
|
|
||||||
// change page for folder links on vmousedown instead of waiting on vmouseup
|
|
||||||
page.on('vmousedown', 'li a', function(e) {
|
|
||||||
e.preventDefault();
|
|
||||||
var href = $(e.currentTarget).attr('href');
|
|
||||||
window.location = href;
|
|
||||||
});
|
|
||||||
|
|
||||||
return this;
|
page.html(this.template(this.options));
|
||||||
}
|
|
||||||
});
|
// change page for folder links on vmousedown instead of waiting on vmouseup
|
||||||
|
page.on('vmousedown', 'li a', function(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
var href = $(e.currentTarget).attr('href');
|
||||||
|
window.location = href;
|
||||||
|
});
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
}());
|
@ -1,41 +1,49 @@
|
|||||||
'use strict';
|
(function() {
|
||||||
|
'use strict';
|
||||||
|
|
||||||
app.view.LoginView = Backbone.View.extend({
|
app.view.LoginView = Backbone.View.extend({
|
||||||
|
|
||||||
initialize: function(args) {
|
initialize: function(args) {
|
||||||
this.template = _.template(app.util.tpl.get('login'));
|
this.template = _.template(app.util.tpl.get('login'));
|
||||||
this.dao = args.dao;
|
this.dao = args.dao;
|
||||||
},
|
},
|
||||||
|
|
||||||
render: function(eventName) {
|
render: function(eventName) {
|
||||||
var self = this, page = $(this.el);
|
var self = this,
|
||||||
|
page = $(this.el);
|
||||||
page.html(this.template());
|
|
||||||
page.attr('data-theme', 'a');
|
|
||||||
|
|
||||||
page.find('#loginBtn').on('vmousedown', function() {
|
|
||||||
self.login();
|
|
||||||
});
|
|
||||||
|
|
||||||
return this;
|
|
||||||
},
|
|
||||||
|
|
||||||
login: function() {
|
page.html(this.template());
|
||||||
var page = $(this.el),
|
page.attr('data-theme', 'a');
|
||||||
userId = page.find('#userId').val(),
|
|
||||||
password = page.find('#password').val();
|
page.find('#loginBtn').on('vmousedown', function() {
|
||||||
|
self.login();
|
||||||
var account = new app.model.Account({
|
});
|
||||||
emailAddress: userId,
|
|
||||||
symKeySize: app.config.symKeySize,
|
return this;
|
||||||
symIvSize: app.config.symIvSize
|
},
|
||||||
});
|
|
||||||
|
login: function() {
|
||||||
// show loading msg during init
|
var page = $(this.el),
|
||||||
$.mobile.loading('show', { text: 'Unlocking...', textVisible: true, theme: 'c' });
|
userId = page.find('#userId').val(),
|
||||||
this.dao.init(account, password, function() {
|
password = page.find('#password').val();
|
||||||
$.mobile.loading('hide');
|
|
||||||
window.location = '#accounts/' + account.get('emailAddress') + '/folders';
|
var account = new app.model.Account({
|
||||||
});
|
emailAddress: userId,
|
||||||
}
|
symKeySize: app.config.symKeySize,
|
||||||
});
|
symIvSize: app.config.symIvSize
|
||||||
|
});
|
||||||
|
|
||||||
|
// show loading msg during init
|
||||||
|
$.mobile.loading('show', {
|
||||||
|
text: 'Unlocking...',
|
||||||
|
textVisible: true,
|
||||||
|
theme: 'c'
|
||||||
|
});
|
||||||
|
this.dao.init(account, password, function() {
|
||||||
|
$.mobile.loading('hide');
|
||||||
|
window.location = '#accounts/' + account.get('emailAddress') + '/folders';
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
}());
|
@ -1,73 +1,86 @@
|
|||||||
'use strict';
|
(function() {
|
||||||
|
'use strict';
|
||||||
|
|
||||||
app.view.MessageListView = Backbone.View.extend({
|
app.view.MessageListView = Backbone.View.extend({
|
||||||
|
|
||||||
initialize: function(args) {
|
initialize: function(args) {
|
||||||
this.template = _.template(app.util.tpl.get('messagelist'));
|
this.template = _.template(app.util.tpl.get('messagelist'));
|
||||||
this.folder = args.folder;
|
this.folder = args.folder;
|
||||||
this.dao = args.dao;
|
this.dao = args.dao;
|
||||||
},
|
},
|
||||||
|
|
||||||
render: function(eventName) {
|
render: function(eventName) {
|
||||||
var self = this,
|
var self = this,
|
||||||
page = $(this.el);
|
page = $(this.el);
|
||||||
|
|
||||||
page.html(this.template(this.options));
|
page.html(this.template(this.options));
|
||||||
|
|
||||||
page.find('#refreshBtn').on('vmousedown', function() {
|
page.find('#refreshBtn').on('vmousedown', function() {
|
||||||
self.syncFolder();
|
self.syncFolder();
|
||||||
});
|
});
|
||||||
|
|
||||||
return this;
|
return this;
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Synchronize emails from the cloud
|
* Synchronize emails from the cloud
|
||||||
*/
|
*/
|
||||||
syncFolder: function() {
|
syncFolder: function() {
|
||||||
var self = this;
|
var self = this;
|
||||||
|
|
||||||
$.mobile.loading('show', { text: 'Syncing...', textVisible: true });
|
$.mobile.loading('show', {
|
||||||
// sync from cloud
|
text: 'Syncing...',
|
||||||
this.dao.syncFromCloud(this.folder, function(res) {
|
textVisible: true
|
||||||
$.mobile.loading('hide');
|
});
|
||||||
|
// sync from cloud
|
||||||
// check for error
|
this.dao.syncFromCloud(this.folder, function(res) {
|
||||||
if (res && res.status) {
|
$.mobile.loading('hide');
|
||||||
alert('Syncing failed!');
|
|
||||||
return;
|
// check for error
|
||||||
}
|
if (res && res.status) {
|
||||||
|
alert('Syncing failed!');
|
||||||
// read local storage and add to list view
|
return;
|
||||||
self.loadItems();
|
}
|
||||||
});
|
|
||||||
},
|
// read local storage and add to list view
|
||||||
|
self.loadItems();
|
||||||
/**
|
});
|
||||||
* Load items from local storage
|
},
|
||||||
*/
|
|
||||||
loadItems: function() {
|
/**
|
||||||
var self = this,
|
* Load items from local storage
|
||||||
page = $(this.el),
|
*/
|
||||||
list = page.find('#message-list'),
|
loadItems: function() {
|
||||||
listItemArgs, i, email;
|
var self = this,
|
||||||
|
page = $(this.el),
|
||||||
$.mobile.loading('show', { text: 'decrypting...', textVisible: true });
|
list = page.find('#message-list'),
|
||||||
this.dao.listItems(this.folder, 0, 10, function(collection) {
|
listItemArgs, i, email;
|
||||||
// clear list
|
|
||||||
list.html('');
|
$.mobile.loading('show', {
|
||||||
|
text: 'decrypting...',
|
||||||
// append items to list in reverse order so mails with the most recent date will be displayed first
|
textVisible: true
|
||||||
for (i = collection.models.length - 1; i >= 0; i--) {
|
});
|
||||||
email = collection.at(i);
|
this.dao.listItems(this.folder, 0, 10, function(collection) {
|
||||||
listItemArgs = {account: self.options.account, folder: self.folder, model: email};
|
// clear list
|
||||||
list.append(new app.view.MessageListItemView(listItemArgs).render().el);
|
list.html('');
|
||||||
}
|
|
||||||
|
// append items to list in reverse order so mails with the most recent date will be displayed first
|
||||||
// refresh list view
|
for (i = collection.models.length - 1; i >= 0; i--) {
|
||||||
list.listview('refresh');
|
email = collection.at(i);
|
||||||
$.mobile.loading('hide');
|
listItemArgs = {
|
||||||
});
|
account: self.options.account,
|
||||||
}
|
folder: self.folder,
|
||||||
|
model: email
|
||||||
});
|
};
|
||||||
|
list.append(new app.view.MessageListItemView(listItemArgs).render().el);
|
||||||
|
}
|
||||||
|
|
||||||
|
// refresh list view
|
||||||
|
list.listview('refresh');
|
||||||
|
$.mobile.loading('hide');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
}());
|
@ -1,24 +1,27 @@
|
|||||||
'use strict';
|
(function() {
|
||||||
|
'use strict';
|
||||||
|
|
||||||
app.view.MessageListItemView = Backbone.View.extend({
|
app.view.MessageListItemView = Backbone.View.extend({
|
||||||
|
|
||||||
tagName:"li",
|
|
||||||
|
|
||||||
initialize:function () {
|
tagName: "li",
|
||||||
this.template = _.template(app.util.tpl.get('messagelistitem'));
|
|
||||||
},
|
|
||||||
|
|
||||||
render:function (eventName) {
|
initialize: function() {
|
||||||
var params = this.model.toJSON();
|
this.template = _.template(app.util.tpl.get('messagelistitem'));
|
||||||
params.account = this.options.account;
|
},
|
||||||
params.folder = this.options.folder;
|
|
||||||
params.id = encodeURIComponent(params.id);
|
render: function(eventName) {
|
||||||
|
var params = this.model.toJSON();
|
||||||
var util = new app.crypto.Util(window, null);
|
params.account = this.options.account;
|
||||||
var date = util.parseDate(params.sentDate);
|
params.folder = this.options.folder;
|
||||||
params.displayDate = date.getDate() + '.' + (date.getMonth() + 1) + '. ' + date.getHours() + ':' + date.getMinutes();
|
params.id = encodeURIComponent(params.id);
|
||||||
|
|
||||||
$(this.el).html(this.template(params));
|
var util = new app.crypto.Util(window, null);
|
||||||
return this;
|
var date = util.parseDate(params.sentDate);
|
||||||
}
|
params.displayDate = date.getDate() + '.' + (date.getMonth() + 1) + '. ' + date.getHours() + ':' + date.getMinutes();
|
||||||
});
|
|
||||||
|
$(this.el).html(this.template(params));
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
}());
|
@ -1,32 +1,35 @@
|
|||||||
'use strict';
|
(function() {
|
||||||
|
'use strict';
|
||||||
|
|
||||||
app.view.ReadView = Backbone.View.extend({
|
app.view.ReadView = Backbone.View.extend({
|
||||||
|
|
||||||
initialize: function(args) {
|
initialize: function(args) {
|
||||||
this.template = _.template(app.util.tpl.get('read'));
|
this.template = _.template(app.util.tpl.get('read'));
|
||||||
this.model = args.dao.getItem(args.folder, args.messageId);
|
this.model = args.dao.getItem(args.folder, args.messageId);
|
||||||
},
|
},
|
||||||
|
|
||||||
render: function(eventName) {
|
render: function(eventName) {
|
||||||
$(this.el).html(this.template(this.model.toJSON()));
|
$(this.el).html(this.template(this.model.toJSON()));
|
||||||
return this;
|
return this;
|
||||||
},
|
},
|
||||||
|
|
||||||
renderBody: function() {
|
renderBody: function() {
|
||||||
var emailBody = this.model.get('body'),
|
var emailBody = this.model.get('body'),
|
||||||
iframe = $('#idMailContent'),
|
iframe = $('#idMailContent'),
|
||||||
iframeDoc = iframe[0].contentDocument || iframe[0].contentWindow.document;
|
iframeDoc = iframe[0].contentDocument || iframe[0].contentWindow.document;
|
||||||
|
|
||||||
iframe.load(function() {
|
iframe.load(function() {
|
||||||
// resize
|
// resize
|
||||||
var newheight = iframeDoc.body.scrollHeight;
|
var newheight = iframeDoc.body.scrollHeight;
|
||||||
var newwidth = iframeDoc.body.scrollWidth;
|
var newwidth = iframeDoc.body.scrollWidth;
|
||||||
iframe[0].height = (newheight) + 'px';
|
iframe[0].height = (newheight) + 'px';
|
||||||
iframe[0].width = (newwidth) + 'px';
|
iframe[0].width = (newwidth) + 'px';
|
||||||
});
|
});
|
||||||
|
|
||||||
iframeDoc.write(emailBody);
|
iframeDoc.write(emailBody);
|
||||||
iframeDoc.close();
|
iframeDoc.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
}());
|
Loading…
Reference in New Issue
Block a user