diff --git a/src/index.html b/src/index.html index 1dad8497..5b54043c 100644 --- a/src/index.html +++ b/src/index.html @@ -319,6 +319,30 @@
>>6*(3-v)&63));if(l=t.charAt(64))for(;d.length%4;)d.push(l);return d.join("")},parse:function(d){var l=d.length,s=this._map,t=s.charAt(64);t&&(t=d.indexOf(t),-1!=t&&(l=t));for(var t=[],r=0,w=0;w< +l;w++)if(w%4){var v=s.indexOf(d.charAt(w-1))<<2*(w%4),b=s.indexOf(d.charAt(w))>>>6-2*(w%4);t[r>>>2]|=(v|b)<<24-8*(r%4);r++}return p.create(t,r)},_map:"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="}})(); +(function(u){function p(b,n,a,c,e,j,k){b=b+(n&a|~n&c)+e+k;return(b<>>32-j)+n}function d(b,n,a,c,e,j,k){b=b+(n&c|a&~c)+e+k;return(b<>>32-j)+n}function l(b,n,a,c,e,j,k){b=b+(n^a^c)+e+k;return(b<>>32-j)+n}function s(b,n,a,c,e,j,k){b=b+(a^(n|~c))+e+k;return(b<>>32-j)+n}for(var t=CryptoJS,r=t.lib,w=r.WordArray,v=r.Hasher,r=t.algo,b=[],x=0;64>x;x++)b[x]=4294967296*u.abs(u.sin(x+1))|0;r=r.MD5=v.extend({_doReset:function(){this._hash=new w.init([1732584193,4023233417,2562383102,271733878])}, +_doProcessBlock:function(q,n){for(var a=0;16>a;a++){var c=n+a,e=q[c];q[c]=(e<<8|e>>>24)&16711935|(e<<24|e>>>8)&4278255360}var a=this._hash.words,c=q[n+0],e=q[n+1],j=q[n+2],k=q[n+3],z=q[n+4],r=q[n+5],t=q[n+6],w=q[n+7],v=q[n+8],A=q[n+9],B=q[n+10],C=q[n+11],u=q[n+12],D=q[n+13],E=q[n+14],x=q[n+15],f=a[0],m=a[1],g=a[2],h=a[3],f=p(f,m,g,h,c,7,b[0]),h=p(h,f,m,g,e,12,b[1]),g=p(g,h,f,m,j,17,b[2]),m=p(m,g,h,f,k,22,b[3]),f=p(f,m,g,h,z,7,b[4]),h=p(h,f,m,g,r,12,b[5]),g=p(g,h,f,m,t,17,b[6]),m=p(m,g,h,f,w,22,b[7]), +f=p(f,m,g,h,v,7,b[8]),h=p(h,f,m,g,A,12,b[9]),g=p(g,h,f,m,B,17,b[10]),m=p(m,g,h,f,C,22,b[11]),f=p(f,m,g,h,u,7,b[12]),h=p(h,f,m,g,D,12,b[13]),g=p(g,h,f,m,E,17,b[14]),m=p(m,g,h,f,x,22,b[15]),f=d(f,m,g,h,e,5,b[16]),h=d(h,f,m,g,t,9,b[17]),g=d(g,h,f,m,C,14,b[18]),m=d(m,g,h,f,c,20,b[19]),f=d(f,m,g,h,r,5,b[20]),h=d(h,f,m,g,B,9,b[21]),g=d(g,h,f,m,x,14,b[22]),m=d(m,g,h,f,z,20,b[23]),f=d(f,m,g,h,A,5,b[24]),h=d(h,f,m,g,E,9,b[25]),g=d(g,h,f,m,k,14,b[26]),m=d(m,g,h,f,v,20,b[27]),f=d(f,m,g,h,D,5,b[28]),h=d(h,f, +m,g,j,9,b[29]),g=d(g,h,f,m,w,14,b[30]),m=d(m,g,h,f,u,20,b[31]),f=l(f,m,g,h,r,4,b[32]),h=l(h,f,m,g,v,11,b[33]),g=l(g,h,f,m,C,16,b[34]),m=l(m,g,h,f,E,23,b[35]),f=l(f,m,g,h,e,4,b[36]),h=l(h,f,m,g,z,11,b[37]),g=l(g,h,f,m,w,16,b[38]),m=l(m,g,h,f,B,23,b[39]),f=l(f,m,g,h,D,4,b[40]),h=l(h,f,m,g,c,11,b[41]),g=l(g,h,f,m,k,16,b[42]),m=l(m,g,h,f,t,23,b[43]),f=l(f,m,g,h,A,4,b[44]),h=l(h,f,m,g,u,11,b[45]),g=l(g,h,f,m,x,16,b[46]),m=l(m,g,h,f,j,23,b[47]),f=s(f,m,g,h,c,6,b[48]),h=s(h,f,m,g,w,10,b[49]),g=s(g,h,f,m, +E,15,b[50]),m=s(m,g,h,f,r,21,b[51]),f=s(f,m,g,h,u,6,b[52]),h=s(h,f,m,g,k,10,b[53]),g=s(g,h,f,m,B,15,b[54]),m=s(m,g,h,f,e,21,b[55]),f=s(f,m,g,h,v,6,b[56]),h=s(h,f,m,g,x,10,b[57]),g=s(g,h,f,m,t,15,b[58]),m=s(m,g,h,f,D,21,b[59]),f=s(f,m,g,h,z,6,b[60]),h=s(h,f,m,g,C,10,b[61]),g=s(g,h,f,m,j,15,b[62]),m=s(m,g,h,f,A,21,b[63]);a[0]=a[0]+f|0;a[1]=a[1]+m|0;a[2]=a[2]+g|0;a[3]=a[3]+h|0},_doFinalize:function(){var b=this._data,n=b.words,a=8*this._nDataBytes,c=8*b.sigBytes;n[c>>>5]|=128<<24-c%32;var e=u.floor(a/ +4294967296);n[(c+64>>>9<<4)+15]=(e<<8|e>>>24)&16711935|(e<<24|e>>>8)&4278255360;n[(c+64>>>9<<4)+14]=(a<<8|a>>>24)&16711935|(a<<24|a>>>8)&4278255360;b.sigBytes=4*(n.length+1);this._process();b=this._hash;n=b.words;for(a=0;4>a;a++)c=n[a],n[a]=(c<<8|c>>>24)&16711935|(c<<24|c>>>8)&4278255360;return b},clone:function(){var b=v.clone.call(this);b._hash=this._hash.clone();return b}});t.MD5=v._createHelper(r);t.HmacMD5=v._createHmacHelper(r)})(Math); +(function(){var u=CryptoJS,p=u.lib,d=p.Base,l=p.WordArray,p=u.algo,s=p.EvpKDF=d.extend({cfg:d.extend({keySize:4,hasher:p.MD5,iterations:1}),init:function(d){this.cfg=this.cfg.extend(d)},compute:function(d,r){for(var p=this.cfg,s=p.hasher.create(),b=l.create(),u=b.words,q=p.keySize,p=p.iterations;u.length>>2]&255}};d.BlockCipher=v.extend({cfg:v.cfg.extend({mode:b,padding:q}),reset:function(){v.reset.call(this);var a=this.cfg,b=a.iv,a=a.mode;if(this._xformMode==this._ENC_XFORM_MODE)var c=a.createEncryptor;else c=a.createDecryptor,this._minBufferSize=1;this._mode=c.call(a, +this,b&&b.words)},_doProcessBlock:function(a,b){this._mode.processBlock(a,b)},_doFinalize:function(){var a=this.cfg.padding;if(this._xformMode==this._ENC_XFORM_MODE){a.pad(this._data,this.blockSize);var b=this._process(!0)}else b=this._process(!0),a.unpad(b);return b},blockSize:4});var n=d.CipherParams=l.extend({init:function(a){this.mixIn(a)},toString:function(a){return(a||this.formatter).stringify(this)}}),b=(p.format={}).OpenSSL={stringify:function(a){var b=a.ciphertext;a=a.salt;return(a?s.create([1398893684, +1701076831]).concat(a).concat(b):b).toString(r)},parse:function(a){a=r.parse(a);var b=a.words;if(1398893684==b[0]&&1701076831==b[1]){var c=s.create(b.slice(2,4));b.splice(0,4);a.sigBytes-=16}return n.create({ciphertext:a,salt:c})}},a=d.SerializableCipher=l.extend({cfg:l.extend({format:b}),encrypt:function(a,b,c,d){d=this.cfg.extend(d);var l=a.createEncryptor(c,d);b=l.finalize(b);l=l.cfg;return n.create({ciphertext:b,key:c,iv:l.iv,algorithm:a,mode:l.mode,padding:l.padding,blockSize:a.blockSize,formatter:d.format})}, +decrypt:function(a,b,c,d){d=this.cfg.extend(d);b=this._parse(b,d.format);return a.createDecryptor(c,d).finalize(b.ciphertext)},_parse:function(a,b){return"string"==typeof a?b.parse(a,this):a}}),p=(p.kdf={}).OpenSSL={execute:function(a,b,c,d){d||(d=s.random(8));a=w.create({keySize:b+c}).compute(a,d);c=s.create(a.words.slice(b),4*c);a.sigBytes=4*b;return n.create({key:a,iv:c,salt:d})}},c=d.PasswordBasedCipher=a.extend({cfg:a.cfg.extend({kdf:p}),encrypt:function(b,c,d,l){l=this.cfg.extend(l);d=l.kdf.execute(d, +b.keySize,b.ivSize);l.iv=d.iv;b=a.encrypt.call(this,b,c,d.key,l);b.mixIn(d);return b},decrypt:function(b,c,d,l){l=this.cfg.extend(l);c=this._parse(c,l.format);d=l.kdf.execute(d,b.keySize,b.ivSize,c.salt);l.iv=d.iv;return a.decrypt.call(this,b,c,d.key,l)}})}(); +(function(){for(var u=CryptoJS,p=u.lib.BlockCipher,d=u.algo,l=[],s=[],t=[],r=[],w=[],v=[],b=[],x=[],q=[],n=[],a=[],c=0;256>c;c++)a[c]=128>c?c<<1:c<<1^283;for(var e=0,j=0,c=0;256>c;c++){var k=j^j<<1^j<<2^j<<3^j<<4,k=k>>>8^k&255^99;l[e]=k;s[k]=e;var z=a[e],F=a[z],G=a[F],y=257*a[k]^16843008*k;t[e]=y<<24|y>>>8;r[e]=y<<16|y>>>16;w[e]=y<<8|y>>>24;v[e]=y;y=16843009*G^65537*F^257*z^16843008*e;b[k]=y<<24|y>>>8;x[k]=y<<16|y>>>16;q[k]=y<<8|y>>>24;n[k]=y;e?(e=z^a[a[a[G^z]]],j^=a[a[j]]):e=j=1}var H=[0,1,2,4,8, +16,32,64,128,27,54],d=d.AES=p.extend({_doReset:function(){for(var a=this._key,c=a.words,d=a.sigBytes/4,a=4*((this._nRounds=d+6)+1),e=this._keySchedule=[],j=0;j>>24]<<24|l[k>>>16&255]<<16|l[k>>>8&255]<<8|l[k&255]):(k=k<<8|k>>>24,k=l[k>>>24]<<24|l[k>>>16&255]<<16|l[k>>>8&255]<<8|l[k&255],k^=H[j/d|0]<<24);e[j]=e[j-d]^k}c=this._invKeySchedule=[];for(d=0;dd||4>=j?k:b[l[k>>>24]]^x[l[k>>>16&255]]^q[l[k>>> +8&255]]^n[l[k&255]]},encryptBlock:function(a,b){this._doCryptBlock(a,b,this._keySchedule,t,r,w,v,l)},decryptBlock:function(a,c){var d=a[c+1];a[c+1]=a[c+3];a[c+3]=d;this._doCryptBlock(a,c,this._invKeySchedule,b,x,q,n,s);d=a[c+1];a[c+1]=a[c+3];a[c+3]=d},_doCryptBlock:function(a,b,c,d,e,j,l,f){for(var m=this._nRounds,g=a[b]^c[0],h=a[b+1]^c[1],k=a[b+2]^c[2],n=a[b+3]^c[3],p=4,r=1;r>>24]^e[h>>>16&255]^j[k>>>8&255]^l[n&255]^c[p++],s=d[h>>>24]^e[k>>>16&255]^j[n>>>8&255]^l[g&255]^c[p++],t= +d[k>>>24]^e[n>>>16&255]^j[g>>>8&255]^l[h&255]^c[p++],n=d[n>>>24]^e[g>>>16&255]^j[h>>>8&255]^l[k&255]^c[p++],g=q,h=s,k=t;q=(f[g>>>24]<<24|f[h>>>16&255]<<16|f[k>>>8&255]<<8|f[n&255])^c[p++];s=(f[h>>>24]<<24|f[k>>>16&255]<<16|f[n>>>8&255]<<8|f[g&255])^c[p++];t=(f[k>>>24]<<24|f[n>>>16&255]<<16|f[g>>>8&255]<<8|f[h&255])^c[p++];n=(f[n>>>24]<<24|f[g>>>16&255]<<16|f[h>>>8&255]<<8|f[k&255])^c[p++];a[b]=q;a[b+1]=s;a[b+2]=t;a[b+3]=n},keySize:8});u.AES=p._createHelper(d)})(); diff --git a/src/scripts/cryptoJS/cipher-core.js b/src/scripts/cryptoJS/cipher-core.js new file mode 100755 index 00000000..09862098 --- /dev/null +++ b/src/scripts/cryptoJS/cipher-core.js @@ -0,0 +1,863 @@ +/* +CryptoJS v3.1.2 +code.google.com/p/crypto-js +(c) 2009-2013 by Jeff Mott. All rights reserved. +code.google.com/p/crypto-js/wiki/License +*/ +/** + * Cipher core components. + */ +CryptoJS.lib.Cipher || (function (undefined) { + // Shortcuts + var C = CryptoJS; + var C_lib = C.lib; + var Base = C_lib.Base; + var WordArray = C_lib.WordArray; + var BufferedBlockAlgorithm = C_lib.BufferedBlockAlgorithm; + var C_enc = C.enc; + var Utf8 = C_enc.Utf8; + var Base64 = C_enc.Base64; + var C_algo = C.algo; + var EvpKDF = C_algo.EvpKDF; + + /** + * Abstract base cipher template. + * + * @property {number} keySize This cipher's key size. Default: 4 (128 bits) + * @property {number} ivSize This cipher's IV size. Default: 4 (128 bits) + * @property {number} _ENC_XFORM_MODE A constant representing encryption mode. + * @property {number} _DEC_XFORM_MODE A constant representing decryption mode. + */ + var Cipher = C_lib.Cipher = BufferedBlockAlgorithm.extend({ + /** + * Configuration options. + * + * @property {WordArray} iv The IV to use for this operation. + */ + cfg: Base.extend(), + + /** + * Creates this cipher in encryption mode. + * + * @param {WordArray} key The key. + * @param {Object} cfg (Optional) The configuration options to use for this operation. + * + * @return {Cipher} A cipher instance. + * + * @static + * + * @example + * + * var cipher = CryptoJS.algo.AES.createEncryptor(keyWordArray, { iv: ivWordArray }); + */ + createEncryptor: function (key, cfg) { + return this.create(this._ENC_XFORM_MODE, key, cfg); + }, + + /** + * Creates this cipher in decryption mode. + * + * @param {WordArray} key The key. + * @param {Object} cfg (Optional) The configuration options to use for this operation. + * + * @return {Cipher} A cipher instance. + * + * @static + * + * @example + * + * var cipher = CryptoJS.algo.AES.createDecryptor(keyWordArray, { iv: ivWordArray }); + */ + createDecryptor: function (key, cfg) { + return this.create(this._DEC_XFORM_MODE, key, cfg); + }, + + /** + * Initializes a newly created cipher. + * + * @param {number} xformMode Either the encryption or decryption transormation mode constant. + * @param {WordArray} key The key. + * @param {Object} cfg (Optional) The configuration options to use for this operation. + * + * @example + * + * var cipher = CryptoJS.algo.AES.create(CryptoJS.algo.AES._ENC_XFORM_MODE, keyWordArray, { iv: ivWordArray }); + */ + init: function (xformMode, key, cfg) { + // Apply config defaults + this.cfg = this.cfg.extend(cfg); + + // Store transform mode and key + this._xformMode = xformMode; + this._key = key; + + // Set initial values + this.reset(); + }, + + /** + * Resets this cipher to its initial state. + * + * @example + * + * cipher.reset(); + */ + reset: function () { + // Reset data buffer + BufferedBlockAlgorithm.reset.call(this); + + // Perform concrete-cipher logic + this._doReset(); + }, + + /** + * Adds data to be encrypted or decrypted. + * + * @param {WordArray|string} dataUpdate The data to encrypt or decrypt. + * + * @return {WordArray} The data after processing. + * + * @example + * + * var encrypted = cipher.process('data'); + * var encrypted = cipher.process(wordArray); + */ + process: function (dataUpdate) { + // Append + this._append(dataUpdate); + + // Process available blocks + return this._process(); + }, + + /** + * Finalizes the encryption or decryption process. + * Note that the finalize operation is effectively a destructive, read-once operation. + * + * @param {WordArray|string} dataUpdate The final data to encrypt or decrypt. + * + * @return {WordArray} The data after final processing. + * + * @example + * + * var encrypted = cipher.finalize(); + * var encrypted = cipher.finalize('data'); + * var encrypted = cipher.finalize(wordArray); + */ + finalize: function (dataUpdate) { + // Final data update + if (dataUpdate) { + this._append(dataUpdate); + } + + // Perform concrete-cipher logic + var finalProcessedData = this._doFinalize(); + + return finalProcessedData; + }, + + keySize: 128/32, + + ivSize: 128/32, + + _ENC_XFORM_MODE: 1, + + _DEC_XFORM_MODE: 2, + + /** + * Creates shortcut functions to a cipher's object interface. + * + * @param {Cipher} cipher The cipher to create a helper for. + * + * @return {Object} An object with encrypt and decrypt shortcut functions. + * + * @static + * + * @example + * + * var AES = CryptoJS.lib.Cipher._createHelper(CryptoJS.algo.AES); + */ + _createHelper: (function () { + function selectCipherStrategy(key) { + if (typeof key == 'string') { + return PasswordBasedCipher; + } else { + return SerializableCipher; + } + } + + return function (cipher) { + return { + encrypt: function (message, key, cfg) { + return selectCipherStrategy(key).encrypt(cipher, message, key, cfg); + }, + + decrypt: function (ciphertext, key, cfg) { + return selectCipherStrategy(key).decrypt(cipher, ciphertext, key, cfg); + } + }; + }; + }()) + }); + + /** + * Abstract base stream cipher template. + * + * @property {number} blockSize The number of 32-bit words this cipher operates on. Default: 1 (32 bits) + */ + var StreamCipher = C_lib.StreamCipher = Cipher.extend({ + _doFinalize: function () { + // Process partial blocks + var finalProcessedBlocks = this._process(!!'flush'); + + return finalProcessedBlocks; + }, + + blockSize: 1 + }); + + /** + * Mode namespace. + */ + var C_mode = C.mode = {}; + + /** + * Abstract base block cipher mode template. + */ + var BlockCipherMode = C_lib.BlockCipherMode = Base.extend({ + /** + * Creates this mode for encryption. + * + * @param {Cipher} cipher A block cipher instance. + * @param {Array} iv The IV words. + * + * @static + * + * @example + * + * var mode = CryptoJS.mode.CBC.createEncryptor(cipher, iv.words); + */ + createEncryptor: function (cipher, iv) { + return this.Encryptor.create(cipher, iv); + }, + + /** + * Creates this mode for decryption. + * + * @param {Cipher} cipher A block cipher instance. + * @param {Array} iv The IV words. + * + * @static + * + * @example + * + * var mode = CryptoJS.mode.CBC.createDecryptor(cipher, iv.words); + */ + createDecryptor: function (cipher, iv) { + return this.Decryptor.create(cipher, iv); + }, + + /** + * Initializes a newly created mode. + * + * @param {Cipher} cipher A block cipher instance. + * @param {Array} iv The IV words. + * + * @example + * + * var mode = CryptoJS.mode.CBC.Encryptor.create(cipher, iv.words); + */ + init: function (cipher, iv) { + this._cipher = cipher; + this._iv = iv; + } + }); + + /** + * Cipher Block Chaining mode. + */ + var CBC = C_mode.CBC = (function () { + /** + * Abstract base CBC mode. + */ + var CBC = BlockCipherMode.extend(); + + /** + * CBC encryptor. + */ + CBC.Encryptor = CBC.extend({ + /** + * Processes the data block at offset. + * + * @param {Array} words The data words to operate on. + * @param {number} offset The offset where the block starts. + * + * @example + * + * mode.processBlock(data.words, offset); + */ + processBlock: function (words, offset) { + // Shortcuts + var cipher = this._cipher; + var blockSize = cipher.blockSize; + + // XOR and encrypt + xorBlock.call(this, words, offset, blockSize); + cipher.encryptBlock(words, offset); + + // Remember this block to use with next block + this._prevBlock = words.slice(offset, offset + blockSize); + } + }); + + /** + * CBC decryptor. + */ + CBC.Decryptor = CBC.extend({ + /** + * Processes the data block at offset. + * + * @param {Array} words The data words to operate on. + * @param {number} offset The offset where the block starts. + * + * @example + * + * mode.processBlock(data.words, offset); + */ + processBlock: function (words, offset) { + // Shortcuts + var cipher = this._cipher; + var blockSize = cipher.blockSize; + + // Remember this block to use with next block + var thisBlock = words.slice(offset, offset + blockSize); + + // Decrypt and XOR + cipher.decryptBlock(words, offset); + xorBlock.call(this, words, offset, blockSize); + + // This block becomes the previous block + this._prevBlock = thisBlock; + } + }); + + function xorBlock(words, offset, blockSize) { + // Shortcut + var iv = this._iv; + + // Choose mixing block + if (iv) { + var block = iv; + + // Remove IV for subsequent blocks + this._iv = undefined; + } else { + var block = this._prevBlock; + } + + // XOR blocks + for (var i = 0; i < blockSize; i++) { + words[offset + i] ^= block[i]; + } + } + + return CBC; + }()); + + /** + * Padding namespace. + */ + var C_pad = C.pad = {}; + + /** + * PKCS #5/7 padding strategy. + */ + var Pkcs7 = C_pad.Pkcs7 = { + /** + * Pads data using the algorithm defined in PKCS #5/7. + * + * @param {WordArray} data The data to pad. + * @param {number} blockSize The multiple that the data should be padded to. + * + * @static + * + * @example + * + * CryptoJS.pad.Pkcs7.pad(wordArray, 4); + */ + pad: function (data, blockSize) { + // Shortcut + var blockSizeBytes = blockSize * 4; + + // Count padding bytes + var nPaddingBytes = blockSizeBytes - data.sigBytes % blockSizeBytes; + + // Create padding word + var paddingWord = (nPaddingBytes << 24) | (nPaddingBytes << 16) | (nPaddingBytes << 8) | nPaddingBytes; + + // Create padding + var paddingWords = []; + for (var i = 0; i < nPaddingBytes; i += 4) { + paddingWords.push(paddingWord); + } + var padding = WordArray.create(paddingWords, nPaddingBytes); + + // Add padding + data.concat(padding); + }, + + /** + * Unpads data that had been padded using the algorithm defined in PKCS #5/7. + * + * @param {WordArray} data The data to unpad. + * + * @static + * + * @example + * + * CryptoJS.pad.Pkcs7.unpad(wordArray); + */ + unpad: function (data) { + // Get number of padding bytes from last byte + var nPaddingBytes = data.words[(data.sigBytes - 1) >>> 2] & 0xff; + + // Remove padding + data.sigBytes -= nPaddingBytes; + } + }; + + /** + * Abstract base block cipher template. + * + * @property {number} blockSize The number of 32-bit words this cipher operates on. Default: 4 (128 bits) + */ + var BlockCipher = C_lib.BlockCipher = Cipher.extend({ + /** + * Configuration options. + * + * @property {Mode} mode The block mode to use. Default: CBC + * @property {Padding} padding The padding strategy to use. Default: Pkcs7 + */ + cfg: Cipher.cfg.extend({ + mode: CBC, + padding: Pkcs7 + }), + + reset: function () { + // Reset cipher + Cipher.reset.call(this); + + // Shortcuts + var cfg = this.cfg; + var iv = cfg.iv; + var mode = cfg.mode; + + // Reset block mode + if (this._xformMode == this._ENC_XFORM_MODE) { + var modeCreator = mode.createEncryptor; + } else /* if (this._xformMode == this._DEC_XFORM_MODE) */ { + var modeCreator = mode.createDecryptor; + + // Keep at least one block in the buffer for unpadding + this._minBufferSize = 1; + } + this._mode = modeCreator.call(mode, this, iv && iv.words); + }, + + _doProcessBlock: function (words, offset) { + this._mode.processBlock(words, offset); + }, + + _doFinalize: function () { + // Shortcut + var padding = this.cfg.padding; + + // Finalize + if (this._xformMode == this._ENC_XFORM_MODE) { + // Pad data + padding.pad(this._data, this.blockSize); + + // Process final blocks + var finalProcessedBlocks = this._process(!!'flush'); + } else /* if (this._xformMode == this._DEC_XFORM_MODE) */ { + // Process final blocks + var finalProcessedBlocks = this._process(!!'flush'); + + // Unpad data + padding.unpad(finalProcessedBlocks); + } + + return finalProcessedBlocks; + }, + + blockSize: 128/32 + }); + + /** + * A collection of cipher parameters. + * + * @property {WordArray} ciphertext The raw ciphertext. + * @property {WordArray} key The key to this ciphertext. + * @property {WordArray} iv The IV used in the ciphering operation. + * @property {WordArray} salt The salt used with a key derivation function. + * @property {Cipher} algorithm The cipher algorithm. + * @property {Mode} mode The block mode used in the ciphering operation. + * @property {Padding} padding The padding scheme used in the ciphering operation. + * @property {number} blockSize The block size of the cipher. + * @property {Format} formatter The default formatting strategy to convert this cipher params object to a string. + */ + var CipherParams = C_lib.CipherParams = Base.extend({ + /** + * Initializes a newly created cipher params object. + * + * @param {Object} cipherParams An object with any of the possible cipher parameters. + * + * @example + * + * var cipherParams = CryptoJS.lib.CipherParams.create({ + * ciphertext: ciphertextWordArray, + * key: keyWordArray, + * iv: ivWordArray, + * salt: saltWordArray, + * algorithm: CryptoJS.algo.AES, + * mode: CryptoJS.mode.CBC, + * padding: CryptoJS.pad.PKCS7, + * blockSize: 4, + * formatter: CryptoJS.format.OpenSSL + * }); + */ + init: function (cipherParams) { + this.mixIn(cipherParams); + }, + + /** + * Converts this cipher params object to a string. + * + * @param {Format} formatter (Optional) The formatting strategy to use. + * + * @return {string} The stringified cipher params. + * + * @throws Error If neither the formatter nor the default formatter is set. + * + * @example + * + * var string = cipherParams + ''; + * var string = cipherParams.toString(); + * var string = cipherParams.toString(CryptoJS.format.OpenSSL); + */ + toString: function (formatter) { + return (formatter || this.formatter).stringify(this); + } + }); + + /** + * Format namespace. + */ + var C_format = C.format = {}; + + /** + * OpenSSL formatting strategy. + */ + var OpenSSLFormatter = C_format.OpenSSL = { + /** + * Converts a cipher params object to an OpenSSL-compatible string. + * + * @param {CipherParams} cipherParams The cipher params object. + * + * @return {string} The OpenSSL-compatible string. + * + * @static + * + * @example + * + * var openSSLString = CryptoJS.format.OpenSSL.stringify(cipherParams); + */ + stringify: function (cipherParams) { + // Shortcuts + var ciphertext = cipherParams.ciphertext; + var salt = cipherParams.salt; + + // Format + if (salt) { + var wordArray = WordArray.create([0x53616c74, 0x65645f5f]).concat(salt).concat(ciphertext); + } else { + var wordArray = ciphertext; + } + + return wordArray.toString(Base64); + }, + + /** + * Converts an OpenSSL-compatible string to a cipher params object. + * + * @param {string} openSSLStr The OpenSSL-compatible string. + * + * @return {CipherParams} The cipher params object. + * + * @static + * + * @example + * + * var cipherParams = CryptoJS.format.OpenSSL.parse(openSSLString); + */ + parse: function (openSSLStr) { + // Parse base64 + var ciphertext = Base64.parse(openSSLStr); + + // Shortcut + var ciphertextWords = ciphertext.words; + + // Test for salt + if (ciphertextWords[0] == 0x53616c74 && ciphertextWords[1] == 0x65645f5f) { + // Extract salt + var salt = WordArray.create(ciphertextWords.slice(2, 4)); + + // Remove salt from ciphertext + ciphertextWords.splice(0, 4); + ciphertext.sigBytes -= 16; + } + + return CipherParams.create({ ciphertext: ciphertext, salt: salt }); + } + }; + + /** + * A cipher wrapper that returns ciphertext as a serializable cipher params object. + */ + var SerializableCipher = C_lib.SerializableCipher = Base.extend({ + /** + * Configuration options. + * + * @property {Formatter} format The formatting strategy to convert cipher param objects to and from a string. Default: OpenSSL + */ + cfg: Base.extend({ + format: OpenSSLFormatter + }), + + /** + * Encrypts a message. + * + * @param {Cipher} cipher The cipher algorithm to use. + * @param {WordArray|string} message The message to encrypt. + * @param {WordArray} key The key. + * @param {Object} cfg (Optional) The configuration options to use for this operation. + * + * @return {CipherParams} A cipher params object. + * + * @static + * + * @example + * + * var ciphertextParams = CryptoJS.lib.SerializableCipher.encrypt(CryptoJS.algo.AES, message, key); + * var ciphertextParams = CryptoJS.lib.SerializableCipher.encrypt(CryptoJS.algo.AES, message, key, { iv: iv }); + * var ciphertextParams = CryptoJS.lib.SerializableCipher.encrypt(CryptoJS.algo.AES, message, key, { iv: iv, format: CryptoJS.format.OpenSSL }); + */ + encrypt: function (cipher, message, key, cfg) { + // Apply config defaults + cfg = this.cfg.extend(cfg); + + // Encrypt + var encryptor = cipher.createEncryptor(key, cfg); + var ciphertext = encryptor.finalize(message); + + // Shortcut + var cipherCfg = encryptor.cfg; + + // Create and return serializable cipher params + return CipherParams.create({ + ciphertext: ciphertext, + key: key, + iv: cipherCfg.iv, + algorithm: cipher, + mode: cipherCfg.mode, + padding: cipherCfg.padding, + blockSize: cipher.blockSize, + formatter: cfg.format + }); + }, + + /** + * Decrypts serialized ciphertext. + * + * @param {Cipher} cipher The cipher algorithm to use. + * @param {CipherParams|string} ciphertext The ciphertext to decrypt. + * @param {WordArray} key The key. + * @param {Object} cfg (Optional) The configuration options to use for this operation. + * + * @return {WordArray} The plaintext. + * + * @static + * + * @example + * + * var plaintext = CryptoJS.lib.SerializableCipher.decrypt(CryptoJS.algo.AES, formattedCiphertext, key, { iv: iv, format: CryptoJS.format.OpenSSL }); + * var plaintext = CryptoJS.lib.SerializableCipher.decrypt(CryptoJS.algo.AES, ciphertextParams, key, { iv: iv, format: CryptoJS.format.OpenSSL }); + */ + decrypt: function (cipher, ciphertext, key, cfg) { + // Apply config defaults + cfg = this.cfg.extend(cfg); + + // Convert string to CipherParams + ciphertext = this._parse(ciphertext, cfg.format); + + // Decrypt + var plaintext = cipher.createDecryptor(key, cfg).finalize(ciphertext.ciphertext); + + return plaintext; + }, + + /** + * Converts serialized ciphertext to CipherParams, + * else assumed CipherParams already and returns ciphertext unchanged. + * + * @param {CipherParams|string} ciphertext The ciphertext. + * @param {Formatter} format The formatting strategy to use to parse serialized ciphertext. + * + * @return {CipherParams} The unserialized ciphertext. + * + * @static + * + * @example + * + * var ciphertextParams = CryptoJS.lib.SerializableCipher._parse(ciphertextStringOrParams, format); + */ + _parse: function (ciphertext, format) { + if (typeof ciphertext == 'string') { + return format.parse(ciphertext, this); + } else { + return ciphertext; + } + } + }); + + /** + * Key derivation function namespace. + */ + var C_kdf = C.kdf = {}; + + /** + * OpenSSL key derivation function. + */ + var OpenSSLKdf = C_kdf.OpenSSL = { + /** + * Derives a key and IV from a password. + * + * @param {string} password The password to derive from. + * @param {number} keySize The size in words of the key to generate. + * @param {number} ivSize The size in words of the IV to generate. + * @param {WordArray|string} salt (Optional) A 64-bit salt to use. If omitted, a salt will be generated randomly. + * + * @return {CipherParams} A cipher params object with the key, IV, and salt. + * + * @static + * + * @example + * + * var derivedParams = CryptoJS.kdf.OpenSSL.execute('Password', 256/32, 128/32); + * var derivedParams = CryptoJS.kdf.OpenSSL.execute('Password', 256/32, 128/32, 'saltsalt'); + */ + execute: function (password, keySize, ivSize, salt) { + // Generate random salt + if (!salt) { + salt = WordArray.random(64/8); + } + + // Derive key and IV + var key = EvpKDF.create({ keySize: keySize + ivSize }).compute(password, salt); + + // Separate key and IV + var iv = WordArray.create(key.words.slice(keySize), ivSize * 4); + key.sigBytes = keySize * 4; + + // Return params + return CipherParams.create({ key: key, iv: iv, salt: salt }); + } + }; + + /** + * A serializable cipher wrapper that derives the key from a password, + * and returns ciphertext as a serializable cipher params object. + */ + var PasswordBasedCipher = C_lib.PasswordBasedCipher = SerializableCipher.extend({ + /** + * Configuration options. + * + * @property {KDF} kdf The key derivation function to use to generate a key and IV from a password. Default: OpenSSL + */ + cfg: SerializableCipher.cfg.extend({ + kdf: OpenSSLKdf + }), + + /** + * Encrypts a message using a password. + * + * @param {Cipher} cipher The cipher algorithm to use. + * @param {WordArray|string} message The message to encrypt. + * @param {string} password The password. + * @param {Object} cfg (Optional) The configuration options to use for this operation. + * + * @return {CipherParams} A cipher params object. + * + * @static + * + * @example + * + * var ciphertextParams = CryptoJS.lib.PasswordBasedCipher.encrypt(CryptoJS.algo.AES, message, 'password'); + * var ciphertextParams = CryptoJS.lib.PasswordBasedCipher.encrypt(CryptoJS.algo.AES, message, 'password', { format: CryptoJS.format.OpenSSL }); + */ + encrypt: function (cipher, message, password, cfg) { + // Apply config defaults + cfg = this.cfg.extend(cfg); + + // Derive key and other params + var derivedParams = cfg.kdf.execute(password, cipher.keySize, cipher.ivSize); + + // Add IV to config + cfg.iv = derivedParams.iv; + + // Encrypt + var ciphertext = SerializableCipher.encrypt.call(this, cipher, message, derivedParams.key, cfg); + + // Mix in derived params + ciphertext.mixIn(derivedParams); + + return ciphertext; + }, + + /** + * Decrypts serialized ciphertext using a password. + * + * @param {Cipher} cipher The cipher algorithm to use. + * @param {CipherParams|string} ciphertext The ciphertext to decrypt. + * @param {string} password The password. + * @param {Object} cfg (Optional) The configuration options to use for this operation. + * + * @return {WordArray} The plaintext. + * + * @static + * + * @example + * + * var plaintext = CryptoJS.lib.PasswordBasedCipher.decrypt(CryptoJS.algo.AES, formattedCiphertext, 'password', { format: CryptoJS.format.OpenSSL }); + * var plaintext = CryptoJS.lib.PasswordBasedCipher.decrypt(CryptoJS.algo.AES, ciphertextParams, 'password', { format: CryptoJS.format.OpenSSL }); + */ + decrypt: function (cipher, ciphertext, password, cfg) { + // Apply config defaults + cfg = this.cfg.extend(cfg); + + // Convert string to CipherParams + ciphertext = this._parse(ciphertext, cfg.format); + + // Derive key and other params + var derivedParams = cfg.kdf.execute(password, cipher.keySize, cipher.ivSize, ciphertext.salt); + + // Add IV to config + cfg.iv = derivedParams.iv; + + // Decrypt + var plaintext = SerializableCipher.decrypt.call(this, cipher, ciphertext, derivedParams.key, cfg); + + return plaintext; + } + }); +}()); diff --git a/src/scripts/cryptoJS/evpkdf.js b/src/scripts/cryptoJS/evpkdf.js new file mode 100755 index 00000000..98dea85c --- /dev/null +++ b/src/scripts/cryptoJS/evpkdf.js @@ -0,0 +1,118 @@ +/* +CryptoJS v3.1.2 +code.google.com/p/crypto-js +(c) 2009-2013 by Jeff Mott. All rights reserved. +code.google.com/p/crypto-js/wiki/License +*/ +(function () { + // Shortcuts + var C = CryptoJS; + var C_lib = C.lib; + var Base = C_lib.Base; + var WordArray = C_lib.WordArray; + var C_algo = C.algo; + var MD5 = C_algo.MD5; + + /** + * This key derivation function is meant to conform with EVP_BytesToKey. + * www.openssl.org/docs/crypto/EVP_BytesToKey.html + */ + var EvpKDF = C_algo.EvpKDF = Base.extend({ + /** + * Configuration options. + * + * @property {number} keySize The key size in words to generate. Default: 4 (128 bits) + * @property {Hasher} hasher The hash algorithm to use. Default: MD5 + * @property {number} iterations The number of iterations to perform. Default: 1 + */ + cfg: Base.extend({ + keySize: 128/32, + hasher: MD5, + iterations: 1 + }), + + /** + * Initializes a newly created key derivation function. + * + * @param {Object} cfg (Optional) The configuration options to use for the derivation. + * + * @example + * + * var kdf = CryptoJS.algo.EvpKDF.create(); + * var kdf = CryptoJS.algo.EvpKDF.create({ keySize: 8 }); + * var kdf = CryptoJS.algo.EvpKDF.create({ keySize: 8, iterations: 1000 }); + */ + init: function (cfg) { + this.cfg = this.cfg.extend(cfg); + }, + + /** + * Derives a key from a password. + * + * @param {WordArray|string} password The password. + * @param {WordArray|string} salt A salt. + * + * @return {WordArray} The derived key. + * + * @example + * + * var key = kdf.compute(password, salt); + */ + compute: function (password, salt) { + // Shortcut + var cfg = this.cfg; + + // Init hasher + var hasher = cfg.hasher.create(); + + // Initial values + var derivedKey = WordArray.create(); + + // Shortcuts + var derivedKeyWords = derivedKey.words; + var keySize = cfg.keySize; + var iterations = cfg.iterations; + + // Generate key + while (derivedKeyWords.length < keySize) { + if (block) { + hasher.update(block); + } + var block = hasher.update(password).finalize(salt); + hasher.reset(); + + // Iterations + for (var i = 1; i < iterations; i++) { + block = hasher.finalize(block); + hasher.reset(); + } + + derivedKey.concat(block); + } + derivedKey.sigBytes = keySize * 4; + + return derivedKey; + } + }); + + /** + * Derives a key from a password. + * + * @param {WordArray|string} password The password. + * @param {WordArray|string} salt A salt. + * @param {Object} cfg (Optional) The configuration options to use for this computation. + * + * @return {WordArray} The derived key. + * + * @static + * + * @example + * + * var key = CryptoJS.EvpKDF(password, salt); + * var key = CryptoJS.EvpKDF(password, salt, { keySize: 8 }); + * var key = CryptoJS.EvpKDF(password, salt, { keySize: 8, iterations: 1000 }); + */ + C.EvpKDF = function (password, salt, cfg) { + return EvpKDF.create(cfg).compute(password, salt); + }; +}()); diff --git a/src/scripts/loqui/app.js b/src/scripts/loqui/app.js index de7ad5f0..db3e9488 100644 --- a/src/scripts/loqui/app.js +++ b/src/scripts/loqui/app.js @@ -21,7 +21,7 @@ var App = { pathFiles: 'loqui/files/', pathBackup: 'loqui/backup/', caps: {}, - + // Default values defaults: { App: { @@ -31,12 +31,12 @@ var App = { csn: true, boltGet: true, readReceipts: true, - lockOrientation : true, + lockOrientation : true, devMode: false }, devsettings: { - devConsole: false, - debug: false + devConsole: false, + debug: false }, online: true }, @@ -62,7 +62,7 @@ var App = { } }, Connector: { - presence: { + presence: { show: 'a' } }, @@ -72,15 +72,15 @@ var App = { } } }, - + get accounts () { - return this._accounts.get(); + return this._accounts.get(); }, set accounts (val) { this._accounts.set([].concat(val)); this.accountsCores = [].concat(this.accountsCores); }, - + get accountsCores () { return this.accounts.map(function (e, i, a) { return e.core; @@ -89,7 +89,7 @@ var App = { set accountsCores (val) { Store.put('accountsCores', val); }, - + get settings () { return this._settings.get(); }, @@ -100,7 +100,7 @@ var App = { Store.put('settings', val); this._settings.set($.extend({}, val)); }, - + get devsettings () { return this._devsettings.get(); }, @@ -111,7 +111,7 @@ var App = { Store.put('devsettings', val); this._devsettings.set($.extend({}, val)); }, - + get online () { return this._online.get(); }, @@ -119,7 +119,7 @@ var App = { this._online.set(val); $('body')[0].dataset.online = val; }, - + get avatars () { return this._avatars.get(); }, @@ -138,60 +138,60 @@ var App = { Store.init(); // Load settings and data from storage App.load().then(function () { - // Log in or show wizard + // Log in or show wizard App.upgrade(); App.start(); }); }, - + // Load settings and data from storage load: function () { return Promise.all([ - new Promise(function (callback) { - Store.get('accountsCores', function (cores) { - if (cores && cores.length) { - var accounts = App.accounts; - // Inflate accounts - for (let [i, core] in Iterator(cores)) { - var account = new Account(core); - for (let [i, core] in Iterator(core.chats)) { - let chat = new Chat(core, account); - account.chats.push(chat); - } - accounts.push(account); + new Promise(function (callback) { + Store.get('accountsCores', function (cores) { + if (cores && cores.length) { + var accounts = App.accounts; + // Inflate accounts + for (let [i, core] in Iterator(cores)) { + var account = new Account(core); + for (let [i, core] in Iterator(core.chats)) { + let chat = new Chat(core, account); + account.chats.push(chat); } - App.accounts = accounts; + accounts.push(account); } - callback(null); - }); - }), - new Promise(function (callback) { - Store.get('settings', function (val) { - App.settings = (val && Object.keys(val).length) ? val : App.defaults.App.settings; - callback(null); - }); - }), - new Promise(function (callback) { - Store.get('devsettings', function (val) { - App.devsettings = (val && Object.keys(val).length) ? val : App.defaults.App.devsettings; - callback(null); - }); - }), - new Promise(function (callback) { - Store.get('avatars', function (val) { - App.avatars = val || {}; - callback(null); - }); - }), - new Promise(function (callback) { - Store.get('caps', function (val) { - App.caps = val || {}; - callback(null); - }); - }) - ]); + App.accounts = accounts; + } + callback(null); + }); + }), + new Promise(function (callback) { + Store.get('settings', function (val) { + App.settings = (val && Object.keys(val).length) ? val : App.defaults.App.settings; + callback(null); + }); + }), + new Promise(function (callback) { + Store.get('devsettings', function (val) { + App.devsettings = (val && Object.keys(val).length) ? val : App.defaults.App.devsettings; + callback(null); + }); + }), + new Promise(function (callback) { + Store.get('avatars', function (val) { + App.avatars = val || {}; + callback(null); + }); + }), + new Promise(function (callback) { + Store.get('caps', function (val) { + App.caps = val || {}; + callback(null); + }); + }) + ]); }, - + // Perform special processes if upgrading from older version upgrade: function () { var last = localStorage.getItem('version'); @@ -277,16 +277,16 @@ var App = { } localStorage.setItem('version', App.version); }, - + // Bootstrap logins and so on start: function (last) { App.online = App.online; // If there is already a configured account if (App.accounts.length) { - screen.unlockOrientation= screen.unlockOrientation || screen.mozUnlockOrientation; - if(!App.settings.lockOrientation && screen.unlockOrientation){ - screen.unlockOrientation(); - } + screen.unlockOrientation= screen.unlockOrientation || screen.mozUnlockOrientation; + if(!App.settings.lockOrientation && screen.unlockOrientation){ + screen.unlockOrientation(); + } App.alarmSet({}); this.connect(); Menu.show('main'); @@ -307,7 +307,7 @@ var App = { Menu.show('providers', null, 500); } }, - + // Connect with every enabled account connect: function () { for (var i in this.accounts) { @@ -317,7 +317,7 @@ var App = { } } }, - + // Disconnect from every account disconnect: function () { for (var i in this.accounts) { @@ -330,20 +330,20 @@ var App = { } $('section#main').attr('data-show', 'na'); }, - + // Update an array and put it in storage smartpush: function (key, value, callback) { this[key].push(value); Tools.log('PUSHING', value, 'TO', key, this[key]); Store.put(key, this[key], callback); }, - + // Update an object and put it in storage smartupdate: function (key, callback) { /*Tools.log('SAVING', key, this[key]); Store.put(key, this[key], callback);*/ }, - + // Disconnect from every account killAll: function () { this.settings.reconnect = false; @@ -351,11 +351,11 @@ var App = { this.accounts[i].connector.disconnect(); } }, - + // Display a system notification or play a sound accordingly notify: function (core, altSound, force) { var alt = function () { - App.audio(altSound); + App.audio(altSound); }; if (force || document.hidden) { var notification= null; @@ -381,14 +381,14 @@ var App = { alt(); } }, - + // Play a sound audio: function (file) { if (App.settings.sound && !document.hidden) { $('audio[src="audio/' + file + '.ogg"]')[0].play(); } }, - + // Set an alarm for 90 seconds later so that app automatically reopens alarmSet: function (data) { if (navigator.mozAlarms) { @@ -397,7 +397,7 @@ var App = { req.onerror = function () { }; } }, - + // Bring app to foreground toForeground: function () { navigator.mozApps.getSelf().onsuccess = function (e) { @@ -406,7 +406,260 @@ var App = { app.launch('Loqui IM'); } }; + }, + + + exportData : function(){ + var accounts= null; + var chatChunks= {}; + var avatars= {}; + var avatarChunks= {}; + + Store.get('accountsCores', function(accounts){ + var jobs= []; + + Tools.log('EXPORTING DATA', accounts); + Lungo.Notification.show('upload', _('ExportData')); + + accounts.forEach(function(account){ + + account.chats.forEach(function(chat){ + + chat.chunks.forEach(function(index){ + + jobs.push(new Promise(function(done){ + + Store.recover(index, function(chunk){ + chatChunks[index.toString()]= chunk; + done(); + }); + + })); + + }); + + }); + + avatars[account.fullJid]= App.avatars[account.fullJid]; + + jobs.push(new Promise(function(done){ + Store.recover(avatars[account.fullJid].chunk, function(chunk){ + avatarChunks[avatars[account.fullJid].chunk.toString()]= chunk; + done(); + }); + })); + + if(avatars[account.fullJid].original){ + jobs.push(new Promise(function(done){ + Store.recover(avatars[account.fullJid].original, function(chunk){ + avatarChunks[avatars[account.fullJid].original.toString()]= chunk; + done(); + }); + })); + } + + }); + + Promise.all(jobs).then(function(){ + Lungo.Notification.hide(); + App.requestPassword('Backup', function(password){ + Lungo.Notification.show('lock', _('EncryptingData')); + setTimeout(function(){ + Object.keys(chatChunks).forEach(function(key){ + this[key]= CryptoJS.AES.encrypt(JSON.stringify(this[key]), password).toString(); + }.bind(chatChunks)); + + accounts.forEach(function(account, key){ + this[key]= CryptoJS.AES.encrypt(JSON.stringify(account), password).toString(); + }.bind(accounts)); + + Object.keys(avatars).forEach(function(key){ + this[key]= CryptoJS.AES.encrypt(JSON.stringify(this[key]), password).toString(); + }.bind(avatars)); + + Object.keys(avatarChunks).forEach(function(key){ + this[key]= CryptoJS.AES.encrypt(JSON.stringify(this[key]), password).toString(); + }.bind(avatarChunks)); + + Lungo.Notification.show('save', _('SavingBackup')); + var blob= new Blob([JSON.stringify({accounts : accounts, avatars : avatars, chatChunks : chatChunks, avatarChunks : avatarChunks})], { type : 'application/json' }); + + Store.SD.save('loqui/backups/'+ (new Date()).getTime() +'.backup', blob, function(){ + Lungo.Notification.success(_('Backup'), _('BackupStored'), 'save', 3); + }, function(e){ + Lungo.Notification.error(_('Backup'), _('BackupFailed'), 'info-sign', 5); + console.log('FAILED TO SAVE THE BACKUP', e, blob); + }); + + }, 100); + }); + }); + + }); + }, + + importData : function(){ + var restore= function(backupPack){ + App.requestPassword('Restore', function(password){ + var backup= JSON.parse(backupPack); + Lungo.Notification.show('unlock', _('DecryptingData')); + setTimeout(function(){ + try{ + Object.keys(backup.chatChunks).forEach(function(key){ + var chat= backup.chatChunks[key]; + + backup.chatChunks[key]= JSON.parse(CryptoJS.AES.decrypt(chat, password).toString(CryptoJS.enc.Utf8)); + }); + + backup.accounts.forEach(function(account, key){ + backup.accounts[key]= JSON.parse(CryptoJS.AES.decrypt(account, password).toString(CryptoJS.enc.Utf8)); + }); + + Object.keys(backup.avatars).forEach(function(key){ + var avatar= backup.avatars[key]; + + backup.avatars[key]= JSON.parse(CryptoJS.AES.decrypt(avatar, password).toString(CryptoJS.enc.Utf8)); + }); + + Object.keys(backup.avatarChunks).forEach(function(key){ + var avatar= backup.avatarChunks[key]; + + backup.avatarChunks[key]= JSON.parse(CryptoJS.AES.decrypt(avatar, password).toString(CryptoJS.enc.Utf8)); + }); + + console.log('BACKUP DECRYPTED', backup); + + if(confirm(_('BackupApplyRequest'))){ + console.log('REMOVING CURRENT DATA'); + Lungo.Notification.show('trash', _('RemovingCurrentData')); + + var jobs= []; + + for(var i= 0; i <= Store.size; i++){ + jobs.push(new Promise(function(done){ Store.blockDrop(i, done); })); + } + + Promise.all(jobs).then(function(){ + Lungo.Notification.show('download', _('ApplyingBackup')); + + Store.size= 0; + jobs= []; + + backup.accounts.forEach(function(account){ + + account.chats.forEach(function(chat){ + + chat.chunks.forEach(function(index, i){ + + jobs.push(new Promise(function(done){ + Store.save(backup.chatChunks[index.toString()], function(index){ + chat.chunks[i]= index; + done(); + }); + })); + + }); + + }); + + }); + + Object.keys(backup.avatars).forEach(function(index){ + var avatar= backup.avatars[index]; + + jobs.push(new Promise(function(done){ + Store.save(backup.avatarChunks[avatar.chunk.toString()], function(index){ + avatar.chunk= index; + done(); + }); + })); + + if(avatar.original){ + jobs.push(new Promise(function(done){ + Store.save(backup.avatarChunks[avatar.original.toString()], function(index){ + avatar.original= index; + done(); + }); + })); + } + + }); + + Promise.all(jobs).then(function(){ + App.accountsCores= backup.accounts; + App.avatars= backup.avatars; + + Store.update(0, Store.size, function(){ + window.location.reload(); + }); + }); + + }); + } + }catch(e){ + Lungo.Notification.error(_('DecryptionFailed'), _('DecryptionFailedLong'), 'info-sign', 4, restore.bind(null, [backupPack])); + } + }, 100); + }); + }; + + Store.SD.dir('loqui/backups', function(list){ + var files= []; + + list.forEach(function(item){ + if(item.name.substr(item.name.lastIndexOf('.')+1) == 'backup'){ + files.push({ + + from : Tools.convenientDate((new Date(parseInt( + item.name.substring( + item.name.lastIndexOf('/')+1, + item.name.lastIndexOf('.') + ) + ))).toISOString()), + + file : item, + name : item.name.substr(item.name.lastIndexOf('/')+1) + + }); + } + }); + + $('#pickBackup ul')[0].innerHTML= ''; + + files.sort(function(a, b){ + a = parseInt(a.name.substring(0, a.name.lastIndexOf('.'))); + b = parseInt(b.name.substring(0, b.name.lastIndexOf('.'))); + + return (a > b) ? 1 : 0; + }).forEach(function(item, index){ + var element= document.createElement('li'); + $('#pickBackup ul').append(element); + + element.dataset.index= index; + element.textContent= _('BackupRestoreLabel') + ' ' + item.from.join(' '); + }); + + Lungo.Router.section('pickBackup'); + + $('#pickBackup ul')[0].onclick= function(e){ + Lungo.Router.section('back'); + var index= e.target.dataset.index; + + App.disconnect(); + Tools.textUnblob(files[index].file, restore); + }; + }); + }, + + requestPassword : function(type, callback){ + $('#backupPassword p')[0].dataset.l10nId= "PasswordExplanation" + type; + + Lungo.Router.section('backupPassword'); + $('#selectPassword')[0].onclick= function(){ + Lungo.Router.section('back'); + callback($('section#backupPassword input')[0].value); + }; } - + }; diff --git a/src/scripts/loqui/chungo.js b/src/scripts/loqui/chungo.js index e03626ef..3a2e2cb4 100644 --- a/src/scripts/loqui/chungo.js +++ b/src/scripts/loqui/chungo.js @@ -245,6 +245,7 @@ var Chungo = { this._timeout = [ setTimeout(function () { this.hide(); + if(cb) cb(); }.bind(this), seconds ? seconds * 1000 : 20000), cb ]; diff --git a/src/scripts/loqui/store.js b/src/scripts/loqui/store.js index 1a92afa4..2552fdec 100644 --- a/src/scripts/loqui/store.js +++ b/src/scripts/loqui/store.js @@ -158,6 +158,30 @@ var Store = { } }); } + }, + + dir : function(path, onsuccess, onerror){ + if(this.card){ + var list= []; + var req = this.card.enumerate(path); + + req.onsuccess= function(){ + if(this.result){ + list.push(this.result); + this.continue(); + }else{ + onsuccess(list); + } + }; + + req.onerror= function(){ + if(onerror){ + onerror(this.error); + Lungo.Notification.error(_('Error'), _('NoSDAccess'), 'cloud-download', 5); + } + }; + + } } } diff --git a/src/style/loqui/index.css b/src/style/loqui/index.css index c107825d..cfa6191b 100644 --- a/src/style/loqui/index.css +++ b/src/style/loqui/index.css @@ -25,7 +25,7 @@ input[type='search'] { width: 100%; border-radius: 0; } -input[type='text'] { +input[type='text'], input[type='password'] { width: calc(100% - 2rem); height: 4.2rem; font-size: 1.5rem; @@ -1228,7 +1228,7 @@ section.profile #settings li .caption { display: none; } #status input, -#nick input { +#nick input, input[type="password"] { margin: .5rem 0 1.5rem 0; } section.profile article > div:not(#card) { @@ -1285,6 +1285,16 @@ ul.mini li img { top: .9rem; } +section#pickBackup.extended article { + top: 5.6rem; +} + +ul.fileList li { + border-bottom: 0.1rem solid #B4B2B2; + font-size: 1.6rem; + padding: 2.2rem 3rem; +} + aside .cover { display: block; position: relative;
>>2]&255}};d.BlockCipher=v.extend({cfg:v.cfg.extend({mode:b,padding:q}),reset:function(){v.reset.call(this);var a=this.cfg,b=a.iv,a=a.mode;if(this._xformMode==this._ENC_XFORM_MODE)var c=a.createEncryptor;else c=a.createDecryptor,this._minBufferSize=1;this._mode=c.call(a, +this,b&&b.words)},_doProcessBlock:function(a,b){this._mode.processBlock(a,b)},_doFinalize:function(){var a=this.cfg.padding;if(this._xformMode==this._ENC_XFORM_MODE){a.pad(this._data,this.blockSize);var b=this._process(!0)}else b=this._process(!0),a.unpad(b);return b},blockSize:4});var n=d.CipherParams=l.extend({init:function(a){this.mixIn(a)},toString:function(a){return(a||this.formatter).stringify(this)}}),b=(p.format={}).OpenSSL={stringify:function(a){var b=a.ciphertext;a=a.salt;return(a?s.create([1398893684, +1701076831]).concat(a).concat(b):b).toString(r)},parse:function(a){a=r.parse(a);var b=a.words;if(1398893684==b[0]&&1701076831==b[1]){var c=s.create(b.slice(2,4));b.splice(0,4);a.sigBytes-=16}return n.create({ciphertext:a,salt:c})}},a=d.SerializableCipher=l.extend({cfg:l.extend({format:b}),encrypt:function(a,b,c,d){d=this.cfg.extend(d);var l=a.createEncryptor(c,d);b=l.finalize(b);l=l.cfg;return n.create({ciphertext:b,key:c,iv:l.iv,algorithm:a,mode:l.mode,padding:l.padding,blockSize:a.blockSize,formatter:d.format})}, +decrypt:function(a,b,c,d){d=this.cfg.extend(d);b=this._parse(b,d.format);return a.createDecryptor(c,d).finalize(b.ciphertext)},_parse:function(a,b){return"string"==typeof a?b.parse(a,this):a}}),p=(p.kdf={}).OpenSSL={execute:function(a,b,c,d){d||(d=s.random(8));a=w.create({keySize:b+c}).compute(a,d);c=s.create(a.words.slice(b),4*c);a.sigBytes=4*b;return n.create({key:a,iv:c,salt:d})}},c=d.PasswordBasedCipher=a.extend({cfg:a.cfg.extend({kdf:p}),encrypt:function(b,c,d,l){l=this.cfg.extend(l);d=l.kdf.execute(d, +b.keySize,b.ivSize);l.iv=d.iv;b=a.encrypt.call(this,b,c,d.key,l);b.mixIn(d);return b},decrypt:function(b,c,d,l){l=this.cfg.extend(l);c=this._parse(c,l.format);d=l.kdf.execute(d,b.keySize,b.ivSize,c.salt);l.iv=d.iv;return a.decrypt.call(this,b,c,d.key,l)}})}(); +(function(){for(var u=CryptoJS,p=u.lib.BlockCipher,d=u.algo,l=[],s=[],t=[],r=[],w=[],v=[],b=[],x=[],q=[],n=[],a=[],c=0;256>c;c++)a[c]=128>c?c<<1:c<<1^283;for(var e=0,j=0,c=0;256>c;c++){var k=j^j<<1^j<<2^j<<3^j<<4,k=k>>>8^k&255^99;l[e]=k;s[k]=e;var z=a[e],F=a[z],G=a[F],y=257*a[k]^16843008*k;t[e]=y<<24|y>>>8;r[e]=y<<16|y>>>16;w[e]=y<<8|y>>>24;v[e]=y;y=16843009*G^65537*F^257*z^16843008*e;b[k]=y<<24|y>>>8;x[k]=y<<16|y>>>16;q[k]=y<<8|y>>>24;n[k]=y;e?(e=z^a[a[a[G^z]]],j^=a[a[j]]):e=j=1}var H=[0,1,2,4,8, +16,32,64,128,27,54],d=d.AES=p.extend({_doReset:function(){for(var a=this._key,c=a.words,d=a.sigBytes/4,a=4*((this._nRounds=d+6)+1),e=this._keySchedule=[],j=0;j>>24]<<24|l[k>>>16&255]<<16|l[k>>>8&255]<<8|l[k&255]):(k=k<<8|k>>>24,k=l[k>>>24]<<24|l[k>>>16&255]<<16|l[k>>>8&255]<<8|l[k&255],k^=H[j/d|0]<<24);e[j]=e[j-d]^k}c=this._invKeySchedule=[];for(d=0;dd||4>=j?k:b[l[k>>>24]]^x[l[k>>>16&255]]^q[l[k>>> +8&255]]^n[l[k&255]]},encryptBlock:function(a,b){this._doCryptBlock(a,b,this._keySchedule,t,r,w,v,l)},decryptBlock:function(a,c){var d=a[c+1];a[c+1]=a[c+3];a[c+3]=d;this._doCryptBlock(a,c,this._invKeySchedule,b,x,q,n,s);d=a[c+1];a[c+1]=a[c+3];a[c+3]=d},_doCryptBlock:function(a,b,c,d,e,j,l,f){for(var m=this._nRounds,g=a[b]^c[0],h=a[b+1]^c[1],k=a[b+2]^c[2],n=a[b+3]^c[3],p=4,r=1;r>>24]^e[h>>>16&255]^j[k>>>8&255]^l[n&255]^c[p++],s=d[h>>>24]^e[k>>>16&255]^j[n>>>8&255]^l[g&255]^c[p++],t= +d[k>>>24]^e[n>>>16&255]^j[g>>>8&255]^l[h&255]^c[p++],n=d[n>>>24]^e[g>>>16&255]^j[h>>>8&255]^l[k&255]^c[p++],g=q,h=s,k=t;q=(f[g>>>24]<<24|f[h>>>16&255]<<16|f[k>>>8&255]<<8|f[n&255])^c[p++];s=(f[h>>>24]<<24|f[k>>>16&255]<<16|f[n>>>8&255]<<8|f[g&255])^c[p++];t=(f[k>>>24]<<24|f[n>>>16&255]<<16|f[g>>>8&255]<<8|f[h&255])^c[p++];n=(f[n>>>24]<<24|f[g>>>16&255]<<16|f[h>>>8&255]<<8|f[k&255])^c[p++];a[b]=q;a[b+1]=s;a[b+2]=t;a[b+3]=n},keySize:8});u.AES=p._createHelper(d)})(); diff --git a/src/scripts/cryptoJS/cipher-core.js b/src/scripts/cryptoJS/cipher-core.js new file mode 100755 index 00000000..09862098 --- /dev/null +++ b/src/scripts/cryptoJS/cipher-core.js @@ -0,0 +1,863 @@ +/* +CryptoJS v3.1.2 +code.google.com/p/crypto-js +(c) 2009-2013 by Jeff Mott. All rights reserved. +code.google.com/p/crypto-js/wiki/License +*/ +/** + * Cipher core components. + */ +CryptoJS.lib.Cipher || (function (undefined) { + // Shortcuts + var C = CryptoJS; + var C_lib = C.lib; + var Base = C_lib.Base; + var WordArray = C_lib.WordArray; + var BufferedBlockAlgorithm = C_lib.BufferedBlockAlgorithm; + var C_enc = C.enc; + var Utf8 = C_enc.Utf8; + var Base64 = C_enc.Base64; + var C_algo = C.algo; + var EvpKDF = C_algo.EvpKDF; + + /** + * Abstract base cipher template. + * + * @property {number} keySize This cipher's key size. Default: 4 (128 bits) + * @property {number} ivSize This cipher's IV size. Default: 4 (128 bits) + * @property {number} _ENC_XFORM_MODE A constant representing encryption mode. + * @property {number} _DEC_XFORM_MODE A constant representing decryption mode. + */ + var Cipher = C_lib.Cipher = BufferedBlockAlgorithm.extend({ + /** + * Configuration options. + * + * @property {WordArray} iv The IV to use for this operation. + */ + cfg: Base.extend(), + + /** + * Creates this cipher in encryption mode. + * + * @param {WordArray} key The key. + * @param {Object} cfg (Optional) The configuration options to use for this operation. + * + * @return {Cipher} A cipher instance. + * + * @static + * + * @example + * + * var cipher = CryptoJS.algo.AES.createEncryptor(keyWordArray, { iv: ivWordArray }); + */ + createEncryptor: function (key, cfg) { + return this.create(this._ENC_XFORM_MODE, key, cfg); + }, + + /** + * Creates this cipher in decryption mode. + * + * @param {WordArray} key The key. + * @param {Object} cfg (Optional) The configuration options to use for this operation. + * + * @return {Cipher} A cipher instance. + * + * @static + * + * @example + * + * var cipher = CryptoJS.algo.AES.createDecryptor(keyWordArray, { iv: ivWordArray }); + */ + createDecryptor: function (key, cfg) { + return this.create(this._DEC_XFORM_MODE, key, cfg); + }, + + /** + * Initializes a newly created cipher. + * + * @param {number} xformMode Either the encryption or decryption transormation mode constant. + * @param {WordArray} key The key. + * @param {Object} cfg (Optional) The configuration options to use for this operation. + * + * @example + * + * var cipher = CryptoJS.algo.AES.create(CryptoJS.algo.AES._ENC_XFORM_MODE, keyWordArray, { iv: ivWordArray }); + */ + init: function (xformMode, key, cfg) { + // Apply config defaults + this.cfg = this.cfg.extend(cfg); + + // Store transform mode and key + this._xformMode = xformMode; + this._key = key; + + // Set initial values + this.reset(); + }, + + /** + * Resets this cipher to its initial state. + * + * @example + * + * cipher.reset(); + */ + reset: function () { + // Reset data buffer + BufferedBlockAlgorithm.reset.call(this); + + // Perform concrete-cipher logic + this._doReset(); + }, + + /** + * Adds data to be encrypted or decrypted. + * + * @param {WordArray|string} dataUpdate The data to encrypt or decrypt. + * + * @return {WordArray} The data after processing. + * + * @example + * + * var encrypted = cipher.process('data'); + * var encrypted = cipher.process(wordArray); + */ + process: function (dataUpdate) { + // Append + this._append(dataUpdate); + + // Process available blocks + return this._process(); + }, + + /** + * Finalizes the encryption or decryption process. + * Note that the finalize operation is effectively a destructive, read-once operation. + * + * @param {WordArray|string} dataUpdate The final data to encrypt or decrypt. + * + * @return {WordArray} The data after final processing. + * + * @example + * + * var encrypted = cipher.finalize(); + * var encrypted = cipher.finalize('data'); + * var encrypted = cipher.finalize(wordArray); + */ + finalize: function (dataUpdate) { + // Final data update + if (dataUpdate) { + this._append(dataUpdate); + } + + // Perform concrete-cipher logic + var finalProcessedData = this._doFinalize(); + + return finalProcessedData; + }, + + keySize: 128/32, + + ivSize: 128/32, + + _ENC_XFORM_MODE: 1, + + _DEC_XFORM_MODE: 2, + + /** + * Creates shortcut functions to a cipher's object interface. + * + * @param {Cipher} cipher The cipher to create a helper for. + * + * @return {Object} An object with encrypt and decrypt shortcut functions. + * + * @static + * + * @example + * + * var AES = CryptoJS.lib.Cipher._createHelper(CryptoJS.algo.AES); + */ + _createHelper: (function () { + function selectCipherStrategy(key) { + if (typeof key == 'string') { + return PasswordBasedCipher; + } else { + return SerializableCipher; + } + } + + return function (cipher) { + return { + encrypt: function (message, key, cfg) { + return selectCipherStrategy(key).encrypt(cipher, message, key, cfg); + }, + + decrypt: function (ciphertext, key, cfg) { + return selectCipherStrategy(key).decrypt(cipher, ciphertext, key, cfg); + } + }; + }; + }()) + }); + + /** + * Abstract base stream cipher template. + * + * @property {number} blockSize The number of 32-bit words this cipher operates on. Default: 1 (32 bits) + */ + var StreamCipher = C_lib.StreamCipher = Cipher.extend({ + _doFinalize: function () { + // Process partial blocks + var finalProcessedBlocks = this._process(!!'flush'); + + return finalProcessedBlocks; + }, + + blockSize: 1 + }); + + /** + * Mode namespace. + */ + var C_mode = C.mode = {}; + + /** + * Abstract base block cipher mode template. + */ + var BlockCipherMode = C_lib.BlockCipherMode = Base.extend({ + /** + * Creates this mode for encryption. + * + * @param {Cipher} cipher A block cipher instance. + * @param {Array} iv The IV words. + * + * @static + * + * @example + * + * var mode = CryptoJS.mode.CBC.createEncryptor(cipher, iv.words); + */ + createEncryptor: function (cipher, iv) { + return this.Encryptor.create(cipher, iv); + }, + + /** + * Creates this mode for decryption. + * + * @param {Cipher} cipher A block cipher instance. + * @param {Array} iv The IV words. + * + * @static + * + * @example + * + * var mode = CryptoJS.mode.CBC.createDecryptor(cipher, iv.words); + */ + createDecryptor: function (cipher, iv) { + return this.Decryptor.create(cipher, iv); + }, + + /** + * Initializes a newly created mode. + * + * @param {Cipher} cipher A block cipher instance. + * @param {Array} iv The IV words. + * + * @example + * + * var mode = CryptoJS.mode.CBC.Encryptor.create(cipher, iv.words); + */ + init: function (cipher, iv) { + this._cipher = cipher; + this._iv = iv; + } + }); + + /** + * Cipher Block Chaining mode. + */ + var CBC = C_mode.CBC = (function () { + /** + * Abstract base CBC mode. + */ + var CBC = BlockCipherMode.extend(); + + /** + * CBC encryptor. + */ + CBC.Encryptor = CBC.extend({ + /** + * Processes the data block at offset. + * + * @param {Array} words The data words to operate on. + * @param {number} offset The offset where the block starts. + * + * @example + * + * mode.processBlock(data.words, offset); + */ + processBlock: function (words, offset) { + // Shortcuts + var cipher = this._cipher; + var blockSize = cipher.blockSize; + + // XOR and encrypt + xorBlock.call(this, words, offset, blockSize); + cipher.encryptBlock(words, offset); + + // Remember this block to use with next block + this._prevBlock = words.slice(offset, offset + blockSize); + } + }); + + /** + * CBC decryptor. + */ + CBC.Decryptor = CBC.extend({ + /** + * Processes the data block at offset. + * + * @param {Array} words The data words to operate on. + * @param {number} offset The offset where the block starts. + * + * @example + * + * mode.processBlock(data.words, offset); + */ + processBlock: function (words, offset) { + // Shortcuts + var cipher = this._cipher; + var blockSize = cipher.blockSize; + + // Remember this block to use with next block + var thisBlock = words.slice(offset, offset + blockSize); + + // Decrypt and XOR + cipher.decryptBlock(words, offset); + xorBlock.call(this, words, offset, blockSize); + + // This block becomes the previous block + this._prevBlock = thisBlock; + } + }); + + function xorBlock(words, offset, blockSize) { + // Shortcut + var iv = this._iv; + + // Choose mixing block + if (iv) { + var block = iv; + + // Remove IV for subsequent blocks + this._iv = undefined; + } else { + var block = this._prevBlock; + } + + // XOR blocks + for (var i = 0; i < blockSize; i++) { + words[offset + i] ^= block[i]; + } + } + + return CBC; + }()); + + /** + * Padding namespace. + */ + var C_pad = C.pad = {}; + + /** + * PKCS #5/7 padding strategy. + */ + var Pkcs7 = C_pad.Pkcs7 = { + /** + * Pads data using the algorithm defined in PKCS #5/7. + * + * @param {WordArray} data The data to pad. + * @param {number} blockSize The multiple that the data should be padded to. + * + * @static + * + * @example + * + * CryptoJS.pad.Pkcs7.pad(wordArray, 4); + */ + pad: function (data, blockSize) { + // Shortcut + var blockSizeBytes = blockSize * 4; + + // Count padding bytes + var nPaddingBytes = blockSizeBytes - data.sigBytes % blockSizeBytes; + + // Create padding word + var paddingWord = (nPaddingBytes << 24) | (nPaddingBytes << 16) | (nPaddingBytes << 8) | nPaddingBytes; + + // Create padding + var paddingWords = []; + for (var i = 0; i < nPaddingBytes; i += 4) { + paddingWords.push(paddingWord); + } + var padding = WordArray.create(paddingWords, nPaddingBytes); + + // Add padding + data.concat(padding); + }, + + /** + * Unpads data that had been padded using the algorithm defined in PKCS #5/7. + * + * @param {WordArray} data The data to unpad. + * + * @static + * + * @example + * + * CryptoJS.pad.Pkcs7.unpad(wordArray); + */ + unpad: function (data) { + // Get number of padding bytes from last byte + var nPaddingBytes = data.words[(data.sigBytes - 1) >>> 2] & 0xff; + + // Remove padding + data.sigBytes -= nPaddingBytes; + } + }; + + /** + * Abstract base block cipher template. + * + * @property {number} blockSize The number of 32-bit words this cipher operates on. Default: 4 (128 bits) + */ + var BlockCipher = C_lib.BlockCipher = Cipher.extend({ + /** + * Configuration options. + * + * @property {Mode} mode The block mode to use. Default: CBC + * @property {Padding} padding The padding strategy to use. Default: Pkcs7 + */ + cfg: Cipher.cfg.extend({ + mode: CBC, + padding: Pkcs7 + }), + + reset: function () { + // Reset cipher + Cipher.reset.call(this); + + // Shortcuts + var cfg = this.cfg; + var iv = cfg.iv; + var mode = cfg.mode; + + // Reset block mode + if (this._xformMode == this._ENC_XFORM_MODE) { + var modeCreator = mode.createEncryptor; + } else /* if (this._xformMode == this._DEC_XFORM_MODE) */ { + var modeCreator = mode.createDecryptor; + + // Keep at least one block in the buffer for unpadding + this._minBufferSize = 1; + } + this._mode = modeCreator.call(mode, this, iv && iv.words); + }, + + _doProcessBlock: function (words, offset) { + this._mode.processBlock(words, offset); + }, + + _doFinalize: function () { + // Shortcut + var padding = this.cfg.padding; + + // Finalize + if (this._xformMode == this._ENC_XFORM_MODE) { + // Pad data + padding.pad(this._data, this.blockSize); + + // Process final blocks + var finalProcessedBlocks = this._process(!!'flush'); + } else /* if (this._xformMode == this._DEC_XFORM_MODE) */ { + // Process final blocks + var finalProcessedBlocks = this._process(!!'flush'); + + // Unpad data + padding.unpad(finalProcessedBlocks); + } + + return finalProcessedBlocks; + }, + + blockSize: 128/32 + }); + + /** + * A collection of cipher parameters. + * + * @property {WordArray} ciphertext The raw ciphertext. + * @property {WordArray} key The key to this ciphertext. + * @property {WordArray} iv The IV used in the ciphering operation. + * @property {WordArray} salt The salt used with a key derivation function. + * @property {Cipher} algorithm The cipher algorithm. + * @property {Mode} mode The block mode used in the ciphering operation. + * @property {Padding} padding The padding scheme used in the ciphering operation. + * @property {number} blockSize The block size of the cipher. + * @property {Format} formatter The default formatting strategy to convert this cipher params object to a string. + */ + var CipherParams = C_lib.CipherParams = Base.extend({ + /** + * Initializes a newly created cipher params object. + * + * @param {Object} cipherParams An object with any of the possible cipher parameters. + * + * @example + * + * var cipherParams = CryptoJS.lib.CipherParams.create({ + * ciphertext: ciphertextWordArray, + * key: keyWordArray, + * iv: ivWordArray, + * salt: saltWordArray, + * algorithm: CryptoJS.algo.AES, + * mode: CryptoJS.mode.CBC, + * padding: CryptoJS.pad.PKCS7, + * blockSize: 4, + * formatter: CryptoJS.format.OpenSSL + * }); + */ + init: function (cipherParams) { + this.mixIn(cipherParams); + }, + + /** + * Converts this cipher params object to a string. + * + * @param {Format} formatter (Optional) The formatting strategy to use. + * + * @return {string} The stringified cipher params. + * + * @throws Error If neither the formatter nor the default formatter is set. + * + * @example + * + * var string = cipherParams + ''; + * var string = cipherParams.toString(); + * var string = cipherParams.toString(CryptoJS.format.OpenSSL); + */ + toString: function (formatter) { + return (formatter || this.formatter).stringify(this); + } + }); + + /** + * Format namespace. + */ + var C_format = C.format = {}; + + /** + * OpenSSL formatting strategy. + */ + var OpenSSLFormatter = C_format.OpenSSL = { + /** + * Converts a cipher params object to an OpenSSL-compatible string. + * + * @param {CipherParams} cipherParams The cipher params object. + * + * @return {string} The OpenSSL-compatible string. + * + * @static + * + * @example + * + * var openSSLString = CryptoJS.format.OpenSSL.stringify(cipherParams); + */ + stringify: function (cipherParams) { + // Shortcuts + var ciphertext = cipherParams.ciphertext; + var salt = cipherParams.salt; + + // Format + if (salt) { + var wordArray = WordArray.create([0x53616c74, 0x65645f5f]).concat(salt).concat(ciphertext); + } else { + var wordArray = ciphertext; + } + + return wordArray.toString(Base64); + }, + + /** + * Converts an OpenSSL-compatible string to a cipher params object. + * + * @param {string} openSSLStr The OpenSSL-compatible string. + * + * @return {CipherParams} The cipher params object. + * + * @static + * + * @example + * + * var cipherParams = CryptoJS.format.OpenSSL.parse(openSSLString); + */ + parse: function (openSSLStr) { + // Parse base64 + var ciphertext = Base64.parse(openSSLStr); + + // Shortcut + var ciphertextWords = ciphertext.words; + + // Test for salt + if (ciphertextWords[0] == 0x53616c74 && ciphertextWords[1] == 0x65645f5f) { + // Extract salt + var salt = WordArray.create(ciphertextWords.slice(2, 4)); + + // Remove salt from ciphertext + ciphertextWords.splice(0, 4); + ciphertext.sigBytes -= 16; + } + + return CipherParams.create({ ciphertext: ciphertext, salt: salt }); + } + }; + + /** + * A cipher wrapper that returns ciphertext as a serializable cipher params object. + */ + var SerializableCipher = C_lib.SerializableCipher = Base.extend({ + /** + * Configuration options. + * + * @property {Formatter} format The formatting strategy to convert cipher param objects to and from a string. Default: OpenSSL + */ + cfg: Base.extend({ + format: OpenSSLFormatter + }), + + /** + * Encrypts a message. + * + * @param {Cipher} cipher The cipher algorithm to use. + * @param {WordArray|string} message The message to encrypt. + * @param {WordArray} key The key. + * @param {Object} cfg (Optional) The configuration options to use for this operation. + * + * @return {CipherParams} A cipher params object. + * + * @static + * + * @example + * + * var ciphertextParams = CryptoJS.lib.SerializableCipher.encrypt(CryptoJS.algo.AES, message, key); + * var ciphertextParams = CryptoJS.lib.SerializableCipher.encrypt(CryptoJS.algo.AES, message, key, { iv: iv }); + * var ciphertextParams = CryptoJS.lib.SerializableCipher.encrypt(CryptoJS.algo.AES, message, key, { iv: iv, format: CryptoJS.format.OpenSSL }); + */ + encrypt: function (cipher, message, key, cfg) { + // Apply config defaults + cfg = this.cfg.extend(cfg); + + // Encrypt + var encryptor = cipher.createEncryptor(key, cfg); + var ciphertext = encryptor.finalize(message); + + // Shortcut + var cipherCfg = encryptor.cfg; + + // Create and return serializable cipher params + return CipherParams.create({ + ciphertext: ciphertext, + key: key, + iv: cipherCfg.iv, + algorithm: cipher, + mode: cipherCfg.mode, + padding: cipherCfg.padding, + blockSize: cipher.blockSize, + formatter: cfg.format + }); + }, + + /** + * Decrypts serialized ciphertext. + * + * @param {Cipher} cipher The cipher algorithm to use. + * @param {CipherParams|string} ciphertext The ciphertext to decrypt. + * @param {WordArray} key The key. + * @param {Object} cfg (Optional) The configuration options to use for this operation. + * + * @return {WordArray} The plaintext. + * + * @static + * + * @example + * + * var plaintext = CryptoJS.lib.SerializableCipher.decrypt(CryptoJS.algo.AES, formattedCiphertext, key, { iv: iv, format: CryptoJS.format.OpenSSL }); + * var plaintext = CryptoJS.lib.SerializableCipher.decrypt(CryptoJS.algo.AES, ciphertextParams, key, { iv: iv, format: CryptoJS.format.OpenSSL }); + */ + decrypt: function (cipher, ciphertext, key, cfg) { + // Apply config defaults + cfg = this.cfg.extend(cfg); + + // Convert string to CipherParams + ciphertext = this._parse(ciphertext, cfg.format); + + // Decrypt + var plaintext = cipher.createDecryptor(key, cfg).finalize(ciphertext.ciphertext); + + return plaintext; + }, + + /** + * Converts serialized ciphertext to CipherParams, + * else assumed CipherParams already and returns ciphertext unchanged. + * + * @param {CipherParams|string} ciphertext The ciphertext. + * @param {Formatter} format The formatting strategy to use to parse serialized ciphertext. + * + * @return {CipherParams} The unserialized ciphertext. + * + * @static + * + * @example + * + * var ciphertextParams = CryptoJS.lib.SerializableCipher._parse(ciphertextStringOrParams, format); + */ + _parse: function (ciphertext, format) { + if (typeof ciphertext == 'string') { + return format.parse(ciphertext, this); + } else { + return ciphertext; + } + } + }); + + /** + * Key derivation function namespace. + */ + var C_kdf = C.kdf = {}; + + /** + * OpenSSL key derivation function. + */ + var OpenSSLKdf = C_kdf.OpenSSL = { + /** + * Derives a key and IV from a password. + * + * @param {string} password The password to derive from. + * @param {number} keySize The size in words of the key to generate. + * @param {number} ivSize The size in words of the IV to generate. + * @param {WordArray|string} salt (Optional) A 64-bit salt to use. If omitted, a salt will be generated randomly. + * + * @return {CipherParams} A cipher params object with the key, IV, and salt. + * + * @static + * + * @example + * + * var derivedParams = CryptoJS.kdf.OpenSSL.execute('Password', 256/32, 128/32); + * var derivedParams = CryptoJS.kdf.OpenSSL.execute('Password', 256/32, 128/32, 'saltsalt'); + */ + execute: function (password, keySize, ivSize, salt) { + // Generate random salt + if (!salt) { + salt = WordArray.random(64/8); + } + + // Derive key and IV + var key = EvpKDF.create({ keySize: keySize + ivSize }).compute(password, salt); + + // Separate key and IV + var iv = WordArray.create(key.words.slice(keySize), ivSize * 4); + key.sigBytes = keySize * 4; + + // Return params + return CipherParams.create({ key: key, iv: iv, salt: salt }); + } + }; + + /** + * A serializable cipher wrapper that derives the key from a password, + * and returns ciphertext as a serializable cipher params object. + */ + var PasswordBasedCipher = C_lib.PasswordBasedCipher = SerializableCipher.extend({ + /** + * Configuration options. + * + * @property {KDF} kdf The key derivation function to use to generate a key and IV from a password. Default: OpenSSL + */ + cfg: SerializableCipher.cfg.extend({ + kdf: OpenSSLKdf + }), + + /** + * Encrypts a message using a password. + * + * @param {Cipher} cipher The cipher algorithm to use. + * @param {WordArray|string} message The message to encrypt. + * @param {string} password The password. + * @param {Object} cfg (Optional) The configuration options to use for this operation. + * + * @return {CipherParams} A cipher params object. + * + * @static + * + * @example + * + * var ciphertextParams = CryptoJS.lib.PasswordBasedCipher.encrypt(CryptoJS.algo.AES, message, 'password'); + * var ciphertextParams = CryptoJS.lib.PasswordBasedCipher.encrypt(CryptoJS.algo.AES, message, 'password', { format: CryptoJS.format.OpenSSL }); + */ + encrypt: function (cipher, message, password, cfg) { + // Apply config defaults + cfg = this.cfg.extend(cfg); + + // Derive key and other params + var derivedParams = cfg.kdf.execute(password, cipher.keySize, cipher.ivSize); + + // Add IV to config + cfg.iv = derivedParams.iv; + + // Encrypt + var ciphertext = SerializableCipher.encrypt.call(this, cipher, message, derivedParams.key, cfg); + + // Mix in derived params + ciphertext.mixIn(derivedParams); + + return ciphertext; + }, + + /** + * Decrypts serialized ciphertext using a password. + * + * @param {Cipher} cipher The cipher algorithm to use. + * @param {CipherParams|string} ciphertext The ciphertext to decrypt. + * @param {string} password The password. + * @param {Object} cfg (Optional) The configuration options to use for this operation. + * + * @return {WordArray} The plaintext. + * + * @static + * + * @example + * + * var plaintext = CryptoJS.lib.PasswordBasedCipher.decrypt(CryptoJS.algo.AES, formattedCiphertext, 'password', { format: CryptoJS.format.OpenSSL }); + * var plaintext = CryptoJS.lib.PasswordBasedCipher.decrypt(CryptoJS.algo.AES, ciphertextParams, 'password', { format: CryptoJS.format.OpenSSL }); + */ + decrypt: function (cipher, ciphertext, password, cfg) { + // Apply config defaults + cfg = this.cfg.extend(cfg); + + // Convert string to CipherParams + ciphertext = this._parse(ciphertext, cfg.format); + + // Derive key and other params + var derivedParams = cfg.kdf.execute(password, cipher.keySize, cipher.ivSize, ciphertext.salt); + + // Add IV to config + cfg.iv = derivedParams.iv; + + // Decrypt + var plaintext = SerializableCipher.decrypt.call(this, cipher, ciphertext, derivedParams.key, cfg); + + return plaintext; + } + }); +}()); diff --git a/src/scripts/cryptoJS/evpkdf.js b/src/scripts/cryptoJS/evpkdf.js new file mode 100755 index 00000000..98dea85c --- /dev/null +++ b/src/scripts/cryptoJS/evpkdf.js @@ -0,0 +1,118 @@ +/* +CryptoJS v3.1.2 +code.google.com/p/crypto-js +(c) 2009-2013 by Jeff Mott. All rights reserved. +code.google.com/p/crypto-js/wiki/License +*/ +(function () { + // Shortcuts + var C = CryptoJS; + var C_lib = C.lib; + var Base = C_lib.Base; + var WordArray = C_lib.WordArray; + var C_algo = C.algo; + var MD5 = C_algo.MD5; + + /** + * This key derivation function is meant to conform with EVP_BytesToKey. + * www.openssl.org/docs/crypto/EVP_BytesToKey.html + */ + var EvpKDF = C_algo.EvpKDF = Base.extend({ + /** + * Configuration options. + * + * @property {number} keySize The key size in words to generate. Default: 4 (128 bits) + * @property {Hasher} hasher The hash algorithm to use. Default: MD5 + * @property {number} iterations The number of iterations to perform. Default: 1 + */ + cfg: Base.extend({ + keySize: 128/32, + hasher: MD5, + iterations: 1 + }), + + /** + * Initializes a newly created key derivation function. + * + * @param {Object} cfg (Optional) The configuration options to use for the derivation. + * + * @example + * + * var kdf = CryptoJS.algo.EvpKDF.create(); + * var kdf = CryptoJS.algo.EvpKDF.create({ keySize: 8 }); + * var kdf = CryptoJS.algo.EvpKDF.create({ keySize: 8, iterations: 1000 }); + */ + init: function (cfg) { + this.cfg = this.cfg.extend(cfg); + }, + + /** + * Derives a key from a password. + * + * @param {WordArray|string} password The password. + * @param {WordArray|string} salt A salt. + * + * @return {WordArray} The derived key. + * + * @example + * + * var key = kdf.compute(password, salt); + */ + compute: function (password, salt) { + // Shortcut + var cfg = this.cfg; + + // Init hasher + var hasher = cfg.hasher.create(); + + // Initial values + var derivedKey = WordArray.create(); + + // Shortcuts + var derivedKeyWords = derivedKey.words; + var keySize = cfg.keySize; + var iterations = cfg.iterations; + + // Generate key + while (derivedKeyWords.length < keySize) { + if (block) { + hasher.update(block); + } + var block = hasher.update(password).finalize(salt); + hasher.reset(); + + // Iterations + for (var i = 1; i < iterations; i++) { + block = hasher.finalize(block); + hasher.reset(); + } + + derivedKey.concat(block); + } + derivedKey.sigBytes = keySize * 4; + + return derivedKey; + } + }); + + /** + * Derives a key from a password. + * + * @param {WordArray|string} password The password. + * @param {WordArray|string} salt A salt. + * @param {Object} cfg (Optional) The configuration options to use for this computation. + * + * @return {WordArray} The derived key. + * + * @static + * + * @example + * + * var key = CryptoJS.EvpKDF(password, salt); + * var key = CryptoJS.EvpKDF(password, salt, { keySize: 8 }); + * var key = CryptoJS.EvpKDF(password, salt, { keySize: 8, iterations: 1000 }); + */ + C.EvpKDF = function (password, salt, cfg) { + return EvpKDF.create(cfg).compute(password, salt); + }; +}()); diff --git a/src/scripts/loqui/app.js b/src/scripts/loqui/app.js index de7ad5f0..db3e9488 100644 --- a/src/scripts/loqui/app.js +++ b/src/scripts/loqui/app.js @@ -21,7 +21,7 @@ var App = { pathFiles: 'loqui/files/', pathBackup: 'loqui/backup/', caps: {}, - + // Default values defaults: { App: { @@ -31,12 +31,12 @@ var App = { csn: true, boltGet: true, readReceipts: true, - lockOrientation : true, + lockOrientation : true, devMode: false }, devsettings: { - devConsole: false, - debug: false + devConsole: false, + debug: false }, online: true }, @@ -62,7 +62,7 @@ var App = { } }, Connector: { - presence: { + presence: { show: 'a' } }, @@ -72,15 +72,15 @@ var App = { } } }, - + get accounts () { - return this._accounts.get(); + return this._accounts.get(); }, set accounts (val) { this._accounts.set([].concat(val)); this.accountsCores = [].concat(this.accountsCores); }, - + get accountsCores () { return this.accounts.map(function (e, i, a) { return e.core; @@ -89,7 +89,7 @@ var App = { set accountsCores (val) { Store.put('accountsCores', val); }, - + get settings () { return this._settings.get(); }, @@ -100,7 +100,7 @@ var App = { Store.put('settings', val); this._settings.set($.extend({}, val)); }, - + get devsettings () { return this._devsettings.get(); }, @@ -111,7 +111,7 @@ var App = { Store.put('devsettings', val); this._devsettings.set($.extend({}, val)); }, - + get online () { return this._online.get(); }, @@ -119,7 +119,7 @@ var App = { this._online.set(val); $('body')[0].dataset.online = val; }, - + get avatars () { return this._avatars.get(); }, @@ -138,60 +138,60 @@ var App = { Store.init(); // Load settings and data from storage App.load().then(function () { - // Log in or show wizard + // Log in or show wizard App.upgrade(); App.start(); }); }, - + // Load settings and data from storage load: function () { return Promise.all([ - new Promise(function (callback) { - Store.get('accountsCores', function (cores) { - if (cores && cores.length) { - var accounts = App.accounts; - // Inflate accounts - for (let [i, core] in Iterator(cores)) { - var account = new Account(core); - for (let [i, core] in Iterator(core.chats)) { - let chat = new Chat(core, account); - account.chats.push(chat); - } - accounts.push(account); + new Promise(function (callback) { + Store.get('accountsCores', function (cores) { + if (cores && cores.length) { + var accounts = App.accounts; + // Inflate accounts + for (let [i, core] in Iterator(cores)) { + var account = new Account(core); + for (let [i, core] in Iterator(core.chats)) { + let chat = new Chat(core, account); + account.chats.push(chat); } - App.accounts = accounts; + accounts.push(account); } - callback(null); - }); - }), - new Promise(function (callback) { - Store.get('settings', function (val) { - App.settings = (val && Object.keys(val).length) ? val : App.defaults.App.settings; - callback(null); - }); - }), - new Promise(function (callback) { - Store.get('devsettings', function (val) { - App.devsettings = (val && Object.keys(val).length) ? val : App.defaults.App.devsettings; - callback(null); - }); - }), - new Promise(function (callback) { - Store.get('avatars', function (val) { - App.avatars = val || {}; - callback(null); - }); - }), - new Promise(function (callback) { - Store.get('caps', function (val) { - App.caps = val || {}; - callback(null); - }); - }) - ]); + App.accounts = accounts; + } + callback(null); + }); + }), + new Promise(function (callback) { + Store.get('settings', function (val) { + App.settings = (val && Object.keys(val).length) ? val : App.defaults.App.settings; + callback(null); + }); + }), + new Promise(function (callback) { + Store.get('devsettings', function (val) { + App.devsettings = (val && Object.keys(val).length) ? val : App.defaults.App.devsettings; + callback(null); + }); + }), + new Promise(function (callback) { + Store.get('avatars', function (val) { + App.avatars = val || {}; + callback(null); + }); + }), + new Promise(function (callback) { + Store.get('caps', function (val) { + App.caps = val || {}; + callback(null); + }); + }) + ]); }, - + // Perform special processes if upgrading from older version upgrade: function () { var last = localStorage.getItem('version'); @@ -277,16 +277,16 @@ var App = { } localStorage.setItem('version', App.version); }, - + // Bootstrap logins and so on start: function (last) { App.online = App.online; // If there is already a configured account if (App.accounts.length) { - screen.unlockOrientation= screen.unlockOrientation || screen.mozUnlockOrientation; - if(!App.settings.lockOrientation && screen.unlockOrientation){ - screen.unlockOrientation(); - } + screen.unlockOrientation= screen.unlockOrientation || screen.mozUnlockOrientation; + if(!App.settings.lockOrientation && screen.unlockOrientation){ + screen.unlockOrientation(); + } App.alarmSet({}); this.connect(); Menu.show('main'); @@ -307,7 +307,7 @@ var App = { Menu.show('providers', null, 500); } }, - + // Connect with every enabled account connect: function () { for (var i in this.accounts) { @@ -317,7 +317,7 @@ var App = { } } }, - + // Disconnect from every account disconnect: function () { for (var i in this.accounts) { @@ -330,20 +330,20 @@ var App = { } $('section#main').attr('data-show', 'na'); }, - + // Update an array and put it in storage smartpush: function (key, value, callback) { this[key].push(value); Tools.log('PUSHING', value, 'TO', key, this[key]); Store.put(key, this[key], callback); }, - + // Update an object and put it in storage smartupdate: function (key, callback) { /*Tools.log('SAVING', key, this[key]); Store.put(key, this[key], callback);*/ }, - + // Disconnect from every account killAll: function () { this.settings.reconnect = false; @@ -351,11 +351,11 @@ var App = { this.accounts[i].connector.disconnect(); } }, - + // Display a system notification or play a sound accordingly notify: function (core, altSound, force) { var alt = function () { - App.audio(altSound); + App.audio(altSound); }; if (force || document.hidden) { var notification= null; @@ -381,14 +381,14 @@ var App = { alt(); } }, - + // Play a sound audio: function (file) { if (App.settings.sound && !document.hidden) { $('audio[src="audio/' + file + '.ogg"]')[0].play(); } }, - + // Set an alarm for 90 seconds later so that app automatically reopens alarmSet: function (data) { if (navigator.mozAlarms) { @@ -397,7 +397,7 @@ var App = { req.onerror = function () { }; } }, - + // Bring app to foreground toForeground: function () { navigator.mozApps.getSelf().onsuccess = function (e) { @@ -406,7 +406,260 @@ var App = { app.launch('Loqui IM'); } }; + }, + + + exportData : function(){ + var accounts= null; + var chatChunks= {}; + var avatars= {}; + var avatarChunks= {}; + + Store.get('accountsCores', function(accounts){ + var jobs= []; + + Tools.log('EXPORTING DATA', accounts); + Lungo.Notification.show('upload', _('ExportData')); + + accounts.forEach(function(account){ + + account.chats.forEach(function(chat){ + + chat.chunks.forEach(function(index){ + + jobs.push(new Promise(function(done){ + + Store.recover(index, function(chunk){ + chatChunks[index.toString()]= chunk; + done(); + }); + + })); + + }); + + }); + + avatars[account.fullJid]= App.avatars[account.fullJid]; + + jobs.push(new Promise(function(done){ + Store.recover(avatars[account.fullJid].chunk, function(chunk){ + avatarChunks[avatars[account.fullJid].chunk.toString()]= chunk; + done(); + }); + })); + + if(avatars[account.fullJid].original){ + jobs.push(new Promise(function(done){ + Store.recover(avatars[account.fullJid].original, function(chunk){ + avatarChunks[avatars[account.fullJid].original.toString()]= chunk; + done(); + }); + })); + } + + }); + + Promise.all(jobs).then(function(){ + Lungo.Notification.hide(); + App.requestPassword('Backup', function(password){ + Lungo.Notification.show('lock', _('EncryptingData')); + setTimeout(function(){ + Object.keys(chatChunks).forEach(function(key){ + this[key]= CryptoJS.AES.encrypt(JSON.stringify(this[key]), password).toString(); + }.bind(chatChunks)); + + accounts.forEach(function(account, key){ + this[key]= CryptoJS.AES.encrypt(JSON.stringify(account), password).toString(); + }.bind(accounts)); + + Object.keys(avatars).forEach(function(key){ + this[key]= CryptoJS.AES.encrypt(JSON.stringify(this[key]), password).toString(); + }.bind(avatars)); + + Object.keys(avatarChunks).forEach(function(key){ + this[key]= CryptoJS.AES.encrypt(JSON.stringify(this[key]), password).toString(); + }.bind(avatarChunks)); + + Lungo.Notification.show('save', _('SavingBackup')); + var blob= new Blob([JSON.stringify({accounts : accounts, avatars : avatars, chatChunks : chatChunks, avatarChunks : avatarChunks})], { type : 'application/json' }); + + Store.SD.save('loqui/backups/'+ (new Date()).getTime() +'.backup', blob, function(){ + Lungo.Notification.success(_('Backup'), _('BackupStored'), 'save', 3); + }, function(e){ + Lungo.Notification.error(_('Backup'), _('BackupFailed'), 'info-sign', 5); + console.log('FAILED TO SAVE THE BACKUP', e, blob); + }); + + }, 100); + }); + }); + + }); + }, + + importData : function(){ + var restore= function(backupPack){ + App.requestPassword('Restore', function(password){ + var backup= JSON.parse(backupPack); + Lungo.Notification.show('unlock', _('DecryptingData')); + setTimeout(function(){ + try{ + Object.keys(backup.chatChunks).forEach(function(key){ + var chat= backup.chatChunks[key]; + + backup.chatChunks[key]= JSON.parse(CryptoJS.AES.decrypt(chat, password).toString(CryptoJS.enc.Utf8)); + }); + + backup.accounts.forEach(function(account, key){ + backup.accounts[key]= JSON.parse(CryptoJS.AES.decrypt(account, password).toString(CryptoJS.enc.Utf8)); + }); + + Object.keys(backup.avatars).forEach(function(key){ + var avatar= backup.avatars[key]; + + backup.avatars[key]= JSON.parse(CryptoJS.AES.decrypt(avatar, password).toString(CryptoJS.enc.Utf8)); + }); + + Object.keys(backup.avatarChunks).forEach(function(key){ + var avatar= backup.avatarChunks[key]; + + backup.avatarChunks[key]= JSON.parse(CryptoJS.AES.decrypt(avatar, password).toString(CryptoJS.enc.Utf8)); + }); + + console.log('BACKUP DECRYPTED', backup); + + if(confirm(_('BackupApplyRequest'))){ + console.log('REMOVING CURRENT DATA'); + Lungo.Notification.show('trash', _('RemovingCurrentData')); + + var jobs= []; + + for(var i= 0; i <= Store.size; i++){ + jobs.push(new Promise(function(done){ Store.blockDrop(i, done); })); + } + + Promise.all(jobs).then(function(){ + Lungo.Notification.show('download', _('ApplyingBackup')); + + Store.size= 0; + jobs= []; + + backup.accounts.forEach(function(account){ + + account.chats.forEach(function(chat){ + + chat.chunks.forEach(function(index, i){ + + jobs.push(new Promise(function(done){ + Store.save(backup.chatChunks[index.toString()], function(index){ + chat.chunks[i]= index; + done(); + }); + })); + + }); + + }); + + }); + + Object.keys(backup.avatars).forEach(function(index){ + var avatar= backup.avatars[index]; + + jobs.push(new Promise(function(done){ + Store.save(backup.avatarChunks[avatar.chunk.toString()], function(index){ + avatar.chunk= index; + done(); + }); + })); + + if(avatar.original){ + jobs.push(new Promise(function(done){ + Store.save(backup.avatarChunks[avatar.original.toString()], function(index){ + avatar.original= index; + done(); + }); + })); + } + + }); + + Promise.all(jobs).then(function(){ + App.accountsCores= backup.accounts; + App.avatars= backup.avatars; + + Store.update(0, Store.size, function(){ + window.location.reload(); + }); + }); + + }); + } + }catch(e){ + Lungo.Notification.error(_('DecryptionFailed'), _('DecryptionFailedLong'), 'info-sign', 4, restore.bind(null, [backupPack])); + } + }, 100); + }); + }; + + Store.SD.dir('loqui/backups', function(list){ + var files= []; + + list.forEach(function(item){ + if(item.name.substr(item.name.lastIndexOf('.')+1) == 'backup'){ + files.push({ + + from : Tools.convenientDate((new Date(parseInt( + item.name.substring( + item.name.lastIndexOf('/')+1, + item.name.lastIndexOf('.') + ) + ))).toISOString()), + + file : item, + name : item.name.substr(item.name.lastIndexOf('/')+1) + + }); + } + }); + + $('#pickBackup ul')[0].innerHTML= ''; + + files.sort(function(a, b){ + a = parseInt(a.name.substring(0, a.name.lastIndexOf('.'))); + b = parseInt(b.name.substring(0, b.name.lastIndexOf('.'))); + + return (a > b) ? 1 : 0; + }).forEach(function(item, index){ + var element= document.createElement('li'); + $('#pickBackup ul').append(element); + + element.dataset.index= index; + element.textContent= _('BackupRestoreLabel') + ' ' + item.from.join(' '); + }); + + Lungo.Router.section('pickBackup'); + + $('#pickBackup ul')[0].onclick= function(e){ + Lungo.Router.section('back'); + var index= e.target.dataset.index; + + App.disconnect(); + Tools.textUnblob(files[index].file, restore); + }; + }); + }, + + requestPassword : function(type, callback){ + $('#backupPassword p')[0].dataset.l10nId= "PasswordExplanation" + type; + + Lungo.Router.section('backupPassword'); + $('#selectPassword')[0].onclick= function(){ + Lungo.Router.section('back'); + callback($('section#backupPassword input')[0].value); + }; } - + }; diff --git a/src/scripts/loqui/chungo.js b/src/scripts/loqui/chungo.js index e03626ef..3a2e2cb4 100644 --- a/src/scripts/loqui/chungo.js +++ b/src/scripts/loqui/chungo.js @@ -245,6 +245,7 @@ var Chungo = { this._timeout = [ setTimeout(function () { this.hide(); + if(cb) cb(); }.bind(this), seconds ? seconds * 1000 : 20000), cb ]; diff --git a/src/scripts/loqui/store.js b/src/scripts/loqui/store.js index 1a92afa4..2552fdec 100644 --- a/src/scripts/loqui/store.js +++ b/src/scripts/loqui/store.js @@ -158,6 +158,30 @@ var Store = { } }); } + }, + + dir : function(path, onsuccess, onerror){ + if(this.card){ + var list= []; + var req = this.card.enumerate(path); + + req.onsuccess= function(){ + if(this.result){ + list.push(this.result); + this.continue(); + }else{ + onsuccess(list); + } + }; + + req.onerror= function(){ + if(onerror){ + onerror(this.error); + Lungo.Notification.error(_('Error'), _('NoSDAccess'), 'cloud-download', 5); + } + }; + + } } } diff --git a/src/style/loqui/index.css b/src/style/loqui/index.css index c107825d..cfa6191b 100644 --- a/src/style/loqui/index.css +++ b/src/style/loqui/index.css @@ -25,7 +25,7 @@ input[type='search'] { width: 100%; border-radius: 0; } -input[type='text'] { +input[type='text'], input[type='password'] { width: calc(100% - 2rem); height: 4.2rem; font-size: 1.5rem; @@ -1228,7 +1228,7 @@ section.profile #settings li .caption { display: none; } #status input, -#nick input { +#nick input, input[type="password"] { margin: .5rem 0 1.5rem 0; } section.profile article > div:not(#card) { @@ -1285,6 +1285,16 @@ ul.mini li img { top: .9rem; } +section#pickBackup.extended article { + top: 5.6rem; +} + +ul.fileList li { + border-bottom: 0.1rem solid #B4B2B2; + font-size: 1.6rem; + padding: 2.2rem 3rem; +} + aside .cover { display: block; position: relative;