diff options
Diffstat (limited to 'core/convenience.js')
-rw-r--r-- | core/convenience.js | 163 |
1 files changed, 104 insertions, 59 deletions
diff --git a/core/convenience.js b/core/convenience.js index 8d68b07..ecc8cfa 100644 --- a/core/convenience.js +++ b/core/convenience.js @@ -4,7 +4,7 @@ * @author Mike Hamburg * @author Dan Boneh */ - + /** @namespace JSON encapsulation */ sjcl.json = { /** Default values for encryption */ @@ -15,13 +15,13 @@ * @param {String} plaintext The data to encrypt. * @param {Object} [params] The parameters including tag, iv and salt. * @param {Object} [rp] A returned version with filled-in parameters. - * @return {String} The ciphertext. + * @return {Object} The cipher raw data. * @throws {sjcl.exception.invalid} if a parameter is invalid. */ - encrypt: function (password, plaintext, params, rp) { + _encrypt: function (password, plaintext, params, rp) { params = params || {}; rp = rp || {}; - + var j = sjcl.json, p = j._add({ iv: sjcl.random.randomWords(4,0) }, j.defaults), tmp, prp, adata; j._add(p, params); @@ -32,7 +32,7 @@ if (typeof p.iv === "string") { p.iv = sjcl.codec.base64.toBits(p.iv); } - + if (!sjcl.mode[p.mode] || !sjcl.cipher[p.cipher] || (typeof password === "string" && p.iter <= 100) || @@ -41,52 +41,73 @@ (p.iv.length < 2 || p.iv.length > 4)) { throw new sjcl.exception.invalid("json encrypt: invalid parameters"); } - + if (typeof password === "string") { tmp = sjcl.misc.cachedPbkdf2(password, p); password = tmp.key.slice(0,p.ks/32); p.salt = tmp.salt; + } else if (sjcl.ecc && password instanceof sjcl.ecc.elGamal.publicKey) { + tmp = password.kem(); + p.kemtag = tmp.tag; + password = tmp.key.slice(0,p.ks/32); } if (typeof plaintext === "string") { plaintext = sjcl.codec.utf8String.toBits(plaintext); } if (typeof adata === "string") { - adata = sjcl.codec.utf8String.toBits(adata); + p.adata = adata = sjcl.codec.utf8String.toBits(adata); } prp = new sjcl.cipher[p.cipher](password); - + /* return the json data */ j._add(rp, p); rp.key = password; - + /* do the encryption */ - p.ct = sjcl.mode[p.mode].encrypt(prp, plaintext, p.iv, adata, p.ts); - + if (p.mode === "ccm" && sjcl.arrayBuffer && sjcl.arrayBuffer.ccm && plaintext instanceof ArrayBuffer) { + p.ct = sjcl.arrayBuffer.ccm.encrypt(prp, plaintext, p.iv, adata, p.ts); + } else { + p.ct = sjcl.mode[p.mode].encrypt(prp, plaintext, p.iv, adata, p.ts); + } + //return j.encode(j._subtract(p, j.defaults)); + return p; + }, + + /** Simple encryption function. + * @param {String|bitArray} password The password or key. + * @param {String} plaintext The data to encrypt. + * @param {Object} [params] The parameters including tag, iv and salt. + * @param {Object} [rp] A returned version with filled-in parameters. + * @return {String} The ciphertext serialized data. + * @throws {sjcl.exception.invalid} if a parameter is invalid. + */ + encrypt: function (password, plaintext, params, rp) { + var j = sjcl.json, p = j._encrypt.apply(j, arguments); return j.encode(p); }, - + /** Simple decryption function. * @param {String|bitArray} password The password or key. - * @param {String} ciphertext The ciphertext to decrypt. + * @param {Object} ciphertext The cipher raw data to decrypt. * @param {Object} [params] Additional non-default parameters. * @param {Object} [rp] A returned object with filled parameters. * @return {String} The plaintext. * @throws {sjcl.exception.invalid} if a parameter is invalid. * @throws {sjcl.exception.corrupt} if the ciphertext is corrupt. */ - decrypt: function (password, ciphertext, params, rp) { + _decrypt: function (password, ciphertext, params, rp) { params = params || {}; rp = rp || {}; - - var j = sjcl.json, p = j._add(j._add(j._add({},j.defaults),j.decode(ciphertext)), params, true), ct, tmp, prp, adata=p.adata; + + var j = sjcl.json, p = j._add(j._add(j._add({},j.defaults),ciphertext), params, true), ct, tmp, prp, adata=p.adata; if (typeof p.salt === "string") { p.salt = sjcl.codec.base64.toBits(p.salt); } if (typeof p.iv === "string") { p.iv = sjcl.codec.base64.toBits(p.iv); } - + if (!sjcl.mode[p.mode] || !sjcl.cipher[p.cipher] || (typeof password === "string" && p.iter <= 100) || @@ -96,27 +117,51 @@ (p.iv.length < 2 || p.iv.length > 4)) { throw new sjcl.exception.invalid("json decrypt: invalid parameters"); } - + if (typeof password === "string") { tmp = sjcl.misc.cachedPbkdf2(password, p); password = tmp.key.slice(0,p.ks/32); p.salt = tmp.salt; + } else if (sjcl.ecc && password instanceof sjcl.ecc.elGamal.secretKey) { + password = password.unkem(sjcl.codec.base64.toBits(p.kemtag)).slice(0,p.ks/32); } if (typeof adata === "string") { adata = sjcl.codec.utf8String.toBits(adata); } prp = new sjcl.cipher[p.cipher](password); - + /* do the decryption */ - ct = sjcl.mode[p.mode].decrypt(prp, p.ct, p.iv, adata, p.ts); - + if (p.mode === "ccm" && sjcl.arrayBuffer && sjcl.arrayBuffer.ccm && p.ct instanceof ArrayBuffer) { + ct = sjcl.arrayBuffer.ccm.decrypt(prp, p.ct, p.iv, p.tag, adata, p.ts); + } else { + ct = sjcl.mode[p.mode].decrypt(prp, p.ct, p.iv, adata, p.ts); + } + /* return the json data */ j._add(rp, p); rp.key = password; - - return sjcl.codec.utf8String.fromBits(ct); + + if (params.raw === 1) { + return ct; + } else { + return sjcl.codec.utf8String.fromBits(ct); + } }, - + + /** Simple decryption function. + * @param {String|bitArray} password The password or key. + * @param {String} ciphertext The ciphertext to decrypt. + * @param {Object} [params] Additional non-default parameters. + * @param {Object} [rp] A returned object with filled parameters. + * @return {String} The plaintext. + * @throws {sjcl.exception.invalid} if a parameter is invalid. + * @throws {sjcl.exception.corrupt} if the ciphertext is corrupt. + */ + decrypt: function (password, ciphertext, params, rp) { + var j = sjcl.json; + return j._decrypt(password, j.decode(ciphertext), params, rp); + }, + /** Encode a flat structure into a JSON string. * @param {Object} obj The structure to encode. * @return {String} A JSON string. @@ -130,31 +175,31 @@ if (!i.match(/^[a-z0-9]+$/i)) { throw new sjcl.exception.invalid("json encode: invalid property name"); } - out += comma + '"' + i + '"' + ':'; + out += comma + '"' + i + '":'; comma = ','; - + switch (typeof obj[i]) { - case 'number': - case 'boolean': - out += obj[i]; - break; - - case 'string': - out += '"' + escape(obj[i]) + '"'; - break; - - case 'object': - out += '"' + sjcl.codec.base64.fromBits(obj[i],1) + '"'; - break; - - default: - throw new sjcl.exception.bug("json encode: unsupported type"); + case 'number': + case 'boolean': + out += obj[i]; + break; + + case 'string': + out += '"' + escape(obj[i]) + '"'; + break; + + case 'object': + out += '"' + sjcl.codec.base64.fromBits(obj[i],0) + '"'; + break; + + default: + throw new sjcl.exception.bug("json encode: unsupported type"); } } } return out+'}'; }, - + /** Decode a simple (flat) JSON string into a structure. The ciphertext, * adata, salt and iv will be base64-decoded. * @param {String} str The string. @@ -163,23 +208,25 @@ */ decode: function (str) { str = str.replace(/\s/g,''); - if (!str.match(/^\{.*\}$/)) { + if (!str.match(/^\{.*\}$/)) { throw new sjcl.exception.invalid("json decode: this isn't json!"); } var a = str.replace(/^\{|\}$/g, '').split(/,/), out={}, i, m; for (i=0; i<a.length; i++) { - if (!(m=a[i].match(/^(?:(["']?)([a-z][a-z0-9]*)\1):(?:(\d+)|"([a-z0-9+\/%*_.@=\-]*)")$/i))) { + if (!(m=a[i].match(/^\s*(?:(["']?)([a-z][a-z0-9]*)\1)\s*:\s*(?:(-?\d+)|"([a-z0-9+\/%*_.@=\-]*)"|(true|false))$/i))) { throw new sjcl.exception.invalid("json decode: this isn't json!"); } - if (m[3]) { + if (m[3] != null) { out[m[2]] = parseInt(m[3],10); - } else { - out[m[2]] = m[2].match(/^(ct|salt|iv)$/) ? sjcl.codec.base64.toBits(m[4]) : unescape(m[4]); + } else if (m[4] != null) { + out[m[2]] = m[2].match(/^(ct|adata|salt|iv)$/) ? sjcl.codec.base64.toBits(m[4]) : unescape(m[4]); + } else if (m[5] != null) { + out[m[2]] = m[5] === 'true'; } } return out; }, - + /** Insert all elements of src into target, modifying and returning target. * @param {Object} target The object to be modified. * @param {Object} src The object to pull data from. @@ -201,22 +248,22 @@ } return target; }, - + /** Remove all elements of minus from plus. Does not modify plus. * @private */ _subtract: function (plus, minus) { var out = {}, i; - + for (i in plus) { if (plus.hasOwnProperty(i) && plus[i] !== minus[i]) { out[i] = plus[i]; } } - + return out; }, - + /** Return only the specified elements of src. * @private */ @@ -256,24 +303,22 @@ sjcl.misc._pbkdf2Cache = {}; /** Cached PBKDF2 key derivation. * @param {String} password The password. - * @param {Object} [params] The derivation params (iteration count and optional salt). + * @param {Object} [obj] The derivation params (iteration count and optional salt). * @return {Object} The derived data in key, the salt in salt. */ sjcl.misc.cachedPbkdf2 = function (password, obj) { var cache = sjcl.misc._pbkdf2Cache, c, cp, str, salt, iter; - + obj = obj || {}; iter = obj.iter || 1000; - + /* open the cache for this password and iteration count */ cp = cache[password] = cache[password] || {}; c = cp[iter] = cp[iter] || { firstSalt: (obj.salt && obj.salt.length) ? obj.salt.slice(0) : sjcl.random.randomWords(2,0) }; - + salt = (obj.salt === undefined) ? c.firstSalt : obj.salt; - + c[salt] = c[salt] || sjcl.misc.pbkdf2(password, salt, obj.iter); return { key: c[salt].slice(0), salt:salt.slice(0) }; }; - - |