diff --git a/static/js/crypto.mjs b/static/js/crypto.mjs
new file mode 100644
index 0000000..c81a3f8
--- /dev/null
+++ b/static/js/crypto.mjs
@@ -0,0 +1,69 @@
+export default class Crypto {
+ constructor(key) {
+ if(typeof key === 'string')
+ this.key = sjcl.codec.base64.toBits(base64_key)
+ else
+ this.key = key
+
+ this.aes = new sjcl.cipher.aes(this.key)
+ }
+
+ static generate_key() {
+ return sjcl.random.randomWords(8)
+ }
+
+ static pass_to_key(pass, salt = null) {
+ if(salt === null)
+ salt = sjcl.random.randomWords(4) // 128 bits (16 bytes)
+ let key = sjcl.misc.pbkdf2(pass, salt, 10000)
+
+ return {
+ salt,
+ key,
+ }
+ }
+
+ encrypt(plaintext_data_in_bits, counter, return_encoded = true) {
+ // 8 bytes of random data, (1 word = 4 bytes) * 2
+ // with 8 bytes of byte encoded counter is used as
+ // IV to guarantee a non-repeated IV (which is a catastrophe).
+ // Assumes counter value is kept unique. Counter is taken from
+ // Postgres sequence.
+ let random_bits = sjcl.random.randomWords(2)
+ let iv_bytes = sjcl.codec.bytes.fromBits(random_bits)
+ for (let i = 0; i < 8; ++i) {
+ let mask = 0xffn << BigInt(i*8)
+ let counter_i_byte = (counter & mask) >> BigInt(i*8)
+ iv_bytes[15-i] = Number(counter_i_byte)
+ }
+ let iv = sjcl.codec.bytes.toBits(iv_bytes)
+
+ let encrypted = sjcl.mode['ccm'].encrypt(
+ this.aes,
+ plaintext_data_in_bits,
+ iv,
+ )
+
+ // Returning 16 bytes (4 words) IV + encrypted data.
+ if(return_encoded)
+ return sjcl.codec.base64.fromBits(
+ iv.concat(encrypted)
+ )
+ else
+ return iv.concat(encrypted)
+ }
+
+ decrypt(encrypted_base64_data) {
+ try {
+ let encoded = sjcl.codec.base64.toBits(encrypted_base64_data)
+ let iv = encoded.slice(0, 4) // in words (4 bytes), not bytes
+ let encrypted_data = encoded.slice(4)
+ return sjcl.mode['ccm'].decrypt(this.aes, encrypted_data, iv)
+ } catch(err) {
+ if(err.message == `ccm: tag doesn't match`)
+ throw('Decryption failed')
+ else
+ throw(err)
+ }
+ }
+}
diff --git a/static/js/key.mjs b/static/js/key.mjs
new file mode 100644
index 0000000..73bd6a3
--- /dev/null
+++ b/static/js/key.mjs
@@ -0,0 +1,113 @@
+import 'preact/devtools'
+import { h, Component } from 'preact'
+import htm from 'htm'
+import Crypto from 'crypto'
+const html = htm.bind(h)
+
+export class Keys extends Component {
+ constructor(props) {//{{{
+ super(props)
+ this.retrieveKeys()
+ }//}}}
+ render({ nodeui }) {//{{{
+ let keys = nodeui.keys.value
+ .sort((a,b)=>{
+ if(a.description < b.description) return -1
+ if(a.description > b.description) return 1
+ return 0
+ })
+ .map(key=>
+ html`<${KeyComponent} key=${`key-${key.ID}`} model=${key} />`
+ )
+
+ return html`
+
+
Keys
+
+ ${keys}
+
+
`
+ }//}}}
+
+ retrieveKeys() {//{{{
+ window._app.current.request('/key/retrieve', {})
+ .then(res=>{
+ this.props.nodeui.keys.value = res.Keys.map(keyData=>new Key(keyData))
+ })
+ .catch(window._app.current.responseError)
+ }//}}}
+}
+
+export class Key {
+ constructor(data) {//{{{
+ this.ID = data.ID
+ this.description = data.Description
+ this.unlockedKey = data.Key
+ this.key = null
+
+ let hex_key = window.sessionStorage.getItem(`key-${this.ID}`)
+ if(hex_key)
+ this.key = sjcl.codec.hex.toBits(hex_key)
+ }//}}}
+ status() {//{{{
+ if(this.key === null)
+ return 'locked'
+ return 'unlocked'
+ }//}}}
+ lock() {//{{{
+ this.key = null
+ window.sessionStorage.removeItem(`key-${this.ID}`)
+ }//}}}
+ unlock(password) {//{{{
+ let db = sjcl.codec.hex.toBits(this.unlockedKey)
+ let salt = db.slice(0, 4)
+ let pass_key = Crypto.pass_to_key(password, salt)
+ let crypto = new Crypto(pass_key.key)
+ this.key = crypto.decrypt(sjcl.codec.base64.fromBits(db.slice(4)))
+ window.sessionStorage.setItem(`key-${this.ID}`, sjcl.codec.hex.fromBits(this.key))
+ }//}}}
+}
+
+export class KeyComponent extends Component {
+ render({ model }) {//{{{
+ let status = ''
+ switch(model.status()) {
+ case 'locked':
+ status = html``
+ break
+
+ case 'unlocked':
+ status = html``
+ break
+ }
+
+ return html`
+
this.toggle()}>${status}
+
this.toggle()}>${model.description}
+ `
+ }//}}}
+ toggle() {//{{{
+ if(this.props.model.status() == 'locked')
+ this.unlock()
+ else
+ this.lock()
+ }//}}}
+ lock() {//{{{
+ this.props.model.lock()
+ this.forceUpdate()
+ }//}}}
+ unlock() {//{{{
+ let pass = prompt("Password")
+ if(!pass)
+ return
+
+ try {
+ this.props.model.unlock(pass)
+ this.forceUpdate()
+ } catch(err) {
+ alert(err)
+ }
+ }//}}}
+}
+
+// vim: foldmethod=marker
diff --git a/static/js/lib/sjcl.js b/static/js/lib/sjcl.js
new file mode 100644
index 0000000..b5a1021
--- /dev/null
+++ b/static/js/lib/sjcl.js
@@ -0,0 +1,2574 @@
+/** @fileOverview Javascript cryptography implementation.
+ *
+ * Crush to remove comments, shorten variable names and
+ * generally reduce transmission size.
+ *
+ * @author Emily Stark
+ * @author Mike Hamburg
+ * @author Dan Boneh
+ */
+
+"use strict";
+/*jslint indent: 2, bitwise: false, nomen: false, plusplus: false, white: false, regexp: false */
+/*global document, window, escape, unescape, module, require, Uint32Array */
+
+/**
+ * The Stanford Javascript Crypto Library, top-level namespace.
+ * @namespace
+ */
+var sjcl = {
+ /**
+ * Symmetric ciphers.
+ * @namespace
+ */
+ cipher: {},
+
+ /**
+ * Hash functions. Right now only SHA256 is implemented.
+ * @namespace
+ */
+ hash: {},
+
+ /**
+ * Key exchange functions. Right now only SRP is implemented.
+ * @namespace
+ */
+ keyexchange: {},
+
+ /**
+ * Cipher modes of operation.
+ * @namespace
+ */
+ mode: {},
+
+ /**
+ * Miscellaneous. HMAC and PBKDF2.
+ * @namespace
+ */
+ misc: {},
+
+ /**
+ * Bit array encoders and decoders.
+ * @namespace
+ *
+ * @description
+ * The members of this namespace are functions which translate between
+ * SJCL's bitArrays and other objects (usually strings). Because it
+ * isn't always clear which direction is encoding and which is decoding,
+ * the method names are "fromBits" and "toBits".
+ */
+ codec: {},
+
+ /**
+ * Exceptions.
+ * @namespace
+ */
+ exception: {
+ /**
+ * Ciphertext is corrupt.
+ * @constructor
+ */
+ corrupt: function(message) {
+ this.toString = function() { return "CORRUPT: "+this.message; };
+ this.message = message;
+ },
+
+ /**
+ * Invalid parameter.
+ * @constructor
+ */
+ invalid: function(message) {
+ this.toString = function() { return "INVALID: "+this.message; };
+ this.message = message;
+ },
+
+ /**
+ * Bug or missing feature in SJCL.
+ * @constructor
+ */
+ bug: function(message) {
+ this.toString = function() { return "BUG: "+this.message; };
+ this.message = message;
+ },
+
+ /**
+ * Something isn't ready.
+ * @constructor
+ */
+ notReady: function(message) {
+ this.toString = function() { return "NOT READY: "+this.message; };
+ this.message = message;
+ }
+ }
+};
+/** @fileOverview Low-level AES implementation.
+ *
+ * This file contains a low-level implementation of AES, optimized for
+ * size and for efficiency on several browsers. It is based on
+ * OpenSSL's aes_core.c, a public-domain implementation by Vincent
+ * Rijmen, Antoon Bosselaers and Paulo Barreto.
+ *
+ * An older version of this implementation is available in the public
+ * domain, but this one is (c) Emily Stark, Mike Hamburg, Dan Boneh,
+ * Stanford University 2008-2010 and BSD-licensed for liability
+ * reasons.
+ *
+ * @author Emily Stark
+ * @author Mike Hamburg
+ * @author Dan Boneh
+ */
+
+/**
+ * Schedule out an AES key for both encryption and decryption. This
+ * is a low-level class. Use a cipher mode to do bulk encryption.
+ *
+ * @constructor
+ * @param {Array} key The key as an array of 4, 6 or 8 words.
+ */
+sjcl.cipher.aes = function (key) {
+ if (!this._tables[0][0][0]) {
+ this._precompute();
+ }
+
+ var i, j, tmp,
+ encKey, decKey,
+ sbox = this._tables[0][4], decTable = this._tables[1],
+ keyLen = key.length, rcon = 1;
+
+ if (keyLen !== 4 && keyLen !== 6 && keyLen !== 8) {
+ throw new sjcl.exception.invalid("invalid aes key size");
+ }
+
+ this._key = [encKey = key.slice(0), decKey = []];
+
+ // schedule encryption keys
+ for (i = keyLen; i < 4 * keyLen + 28; i++) {
+ tmp = encKey[i-1];
+
+ // apply sbox
+ if (i%keyLen === 0 || (keyLen === 8 && i%keyLen === 4)) {
+ tmp = sbox[tmp>>>24]<<24 ^ sbox[tmp>>16&255]<<16 ^ sbox[tmp>>8&255]<<8 ^ sbox[tmp&255];
+
+ // shift rows and add rcon
+ if (i%keyLen === 0) {
+ tmp = tmp<<8 ^ tmp>>>24 ^ rcon<<24;
+ rcon = rcon<<1 ^ (rcon>>7)*283;
+ }
+ }
+
+ encKey[i] = encKey[i-keyLen] ^ tmp;
+ }
+
+ // schedule decryption keys
+ for (j = 0; i; j++, i--) {
+ tmp = encKey[j&3 ? i : i - 4];
+ if (i<=4 || j<4) {
+ decKey[j] = tmp;
+ } else {
+ decKey[j] = decTable[0][sbox[tmp>>>24 ]] ^
+ decTable[1][sbox[tmp>>16 & 255]] ^
+ decTable[2][sbox[tmp>>8 & 255]] ^
+ decTable[3][sbox[tmp & 255]];
+ }
+ }
+};
+
+sjcl.cipher.aes.prototype = {
+ // public
+ /* Something like this might appear here eventually
+ name: "AES",
+ blockSize: 4,
+ keySizes: [4,6,8],
+ */
+
+ /**
+ * Encrypt an array of 4 big-endian words.
+ * @param {Array} data The plaintext.
+ * @return {Array} The ciphertext.
+ */
+ encrypt:function (data) { return this._crypt(data,0); },
+
+ /**
+ * Decrypt an array of 4 big-endian words.
+ * @param {Array} data The ciphertext.
+ * @return {Array} The plaintext.
+ */
+ decrypt:function (data) { return this._crypt(data,1); },
+
+ /**
+ * The expanded S-box and inverse S-box tables. These will be computed
+ * on the client so that we don't have to send them down the wire.
+ *
+ * There are two tables, _tables[0] is for encryption and
+ * _tables[1] is for decryption.
+ *
+ * The first 4 sub-tables are the expanded S-box with MixColumns. The
+ * last (_tables[01][4]) is the S-box itself.
+ *
+ * @private
+ */
+ _tables: [[[],[],[],[],[]],[[],[],[],[],[]]],
+
+ /**
+ * Expand the S-box tables.
+ *
+ * @private
+ */
+ _precompute: function () {
+ var encTable = this._tables[0], decTable = this._tables[1],
+ sbox = encTable[4], sboxInv = decTable[4],
+ i, x, xInv, d=[], th=[], x2, x4, x8, s, tEnc, tDec;
+
+ // Compute double and third tables
+ for (i = 0; i < 256; i++) {
+ th[( d[i] = i<<1 ^ (i>>7)*283 )^i]=i;
+ }
+
+ for (x = xInv = 0; !sbox[x]; x ^= x2 || 1, xInv = th[xInv] || 1) {
+ // Compute sbox
+ s = xInv ^ xInv<<1 ^ xInv<<2 ^ xInv<<3 ^ xInv<<4;
+ s = s>>8 ^ s&255 ^ 99;
+ sbox[x] = s;
+ sboxInv[s] = x;
+
+ // Compute MixColumns
+ x8 = d[x4 = d[x2 = d[x]]];
+ tDec = x8*0x1010101 ^ x4*0x10001 ^ x2*0x101 ^ x*0x1010100;
+ tEnc = d[s]*0x101 ^ s*0x1010100;
+
+ for (i = 0; i < 4; i++) {
+ encTable[i][x] = tEnc = tEnc<<24 ^ tEnc>>>8;
+ decTable[i][s] = tDec = tDec<<24 ^ tDec>>>8;
+ }
+ }
+
+ // Compactify. Considerable speedup on Firefox.
+ for (i = 0; i < 5; i++) {
+ encTable[i] = encTable[i].slice(0);
+ decTable[i] = decTable[i].slice(0);
+ }
+ },
+
+ /**
+ * Encryption and decryption core.
+ * @param {Array} input Four words to be encrypted or decrypted.
+ * @param dir The direction, 0 for encrypt and 1 for decrypt.
+ * @return {Array} The four encrypted or decrypted words.
+ * @private
+ */
+ _crypt:function (input, dir) {
+ if (input.length !== 4) {
+ throw new sjcl.exception.invalid("invalid aes block size");
+ }
+
+ var key = this._key[dir],
+ // state variables a,b,c,d are loaded with pre-whitened data
+ a = input[0] ^ key[0],
+ b = input[dir ? 3 : 1] ^ key[1],
+ c = input[2] ^ key[2],
+ d = input[dir ? 1 : 3] ^ key[3],
+ a2, b2, c2,
+
+ nInnerRounds = key.length/4 - 2,
+ i,
+ kIndex = 4,
+ out = [0,0,0,0],
+ table = this._tables[dir],
+
+ // load up the tables
+ t0 = table[0],
+ t1 = table[1],
+ t2 = table[2],
+ t3 = table[3],
+ sbox = table[4];
+
+ // Inner rounds. Cribbed from OpenSSL.
+ for (i = 0; i < nInnerRounds; i++) {
+ a2 = t0[a>>>24] ^ t1[b>>16 & 255] ^ t2[c>>8 & 255] ^ t3[d & 255] ^ key[kIndex];
+ b2 = t0[b>>>24] ^ t1[c>>16 & 255] ^ t2[d>>8 & 255] ^ t3[a & 255] ^ key[kIndex + 1];
+ c2 = t0[c>>>24] ^ t1[d>>16 & 255] ^ t2[a>>8 & 255] ^ t3[b & 255] ^ key[kIndex + 2];
+ d = t0[d>>>24] ^ t1[a>>16 & 255] ^ t2[b>>8 & 255] ^ t3[c & 255] ^ key[kIndex + 3];
+ kIndex += 4;
+ a=a2; b=b2; c=c2;
+ }
+
+ // Last round.
+ for (i = 0; i < 4; i++) {
+ out[dir ? 3&-i : i] =
+ sbox[a>>>24 ]<<24 ^
+ sbox[b>>16 & 255]<<16 ^
+ sbox[c>>8 & 255]<<8 ^
+ sbox[d & 255] ^
+ key[kIndex++];
+ a2=a; a=b; b=c; c=d; d=a2;
+ }
+
+ return out;
+ }
+};
+
+/** @fileOverview Arrays of bits, encoded as arrays of Numbers.
+ *
+ * @author Emily Stark
+ * @author Mike Hamburg
+ * @author Dan Boneh
+ */
+
+/**
+ * Arrays of bits, encoded as arrays of Numbers.
+ * @namespace
+ * @description
+ *
+ * These objects are the currency accepted by SJCL's crypto functions.
+ *
+ *
+ *
+ * Most of our crypto primitives operate on arrays of 4-byte words internally,
+ * but many of them can take arguments that are not a multiple of 4 bytes.
+ * This library encodes arrays of bits (whose size need not be a multiple of 8
+ * bits) as arrays of 32-bit words. The bits are packed, big-endian, into an
+ * array of words, 32 bits at a time. Since the words are double-precision
+ * floating point numbers, they fit some extra data. We use this (in a private,
+ * possibly-changing manner) to encode the number of bits actually present
+ * in the last word of the array.
+ *
+ *
+ *
+ * Because bitwise ops clear this out-of-band data, these arrays can be passed
+ * to ciphers like AES which want arrays of words.
+ *
+ */
+sjcl.bitArray = {
+ /**
+ * Array slices in units of bits.
+ * @param {bitArray} a The array to slice.
+ * @param {Number} bstart The offset to the start of the slice, in bits.
+ * @param {Number} bend The offset to the end of the slice, in bits. If this is undefined,
+ * slice until the end of the array.
+ * @return {bitArray} The requested slice.
+ */
+ bitSlice: function (a, bstart, bend) {
+ a = sjcl.bitArray._shiftRight(a.slice(bstart/32), 32 - (bstart & 31)).slice(1);
+ return (bend === undefined) ? a : sjcl.bitArray.clamp(a, bend-bstart);
+ },
+
+ /**
+ * Extract a number packed into a bit array.
+ * @param {bitArray} a The array to slice.
+ * @param {Number} bstart The offset to the start of the slice, in bits.
+ * @param {Number} blength The length of the number to extract.
+ * @return {Number} The requested slice.
+ */
+ extract: function(a, bstart, blength) {
+ // FIXME: this Math.floor is not necessary at all, but for some reason
+ // seems to suppress a bug in the Chromium JIT.
+ var x, sh = Math.floor((-bstart-blength) & 31);
+ if ((bstart + blength - 1 ^ bstart) & -32) {
+ // it crosses a boundary
+ x = (a[bstart/32|0] << (32 - sh)) ^ (a[bstart/32+1|0] >>> sh);
+ } else {
+ // within a single word
+ x = a[bstart/32|0] >>> sh;
+ }
+ return x & ((1< 0 && len) {
+ a[l-1] = sjcl.bitArray.partial(len, a[l-1] & 0x80000000 >> (len-1), 1);
+ }
+ return a;
+ },
+
+ /**
+ * Make a partial word for a bit array.
+ * @param {Number} len The number of bits in the word.
+ * @param {Number} x The bits.
+ * @param {Number} [_end=0] Pass 1 if x has already been shifted to the high side.
+ * @return {Number} The partial word.
+ */
+ partial: function (len, x, _end) {
+ if (len === 32) { return x; }
+ return (_end ? x|0 : x << (32-len)) + len * 0x10000000000;
+ },
+
+ /**
+ * Get the number of bits used by a partial word.
+ * @param {Number} x The partial word.
+ * @return {Number} The number of bits used by the partial word.
+ */
+ getPartial: function (x) {
+ return Math.round(x/0x10000000000) || 32;
+ },
+
+ /**
+ * Compare two arrays for equality in a predictable amount of time.
+ * @param {bitArray} a The first array.
+ * @param {bitArray} b The second array.
+ * @return {boolean} true if a == b; false otherwise.
+ */
+ equal: function (a, b) {
+ if (sjcl.bitArray.bitLength(a) !== sjcl.bitArray.bitLength(b)) {
+ return false;
+ }
+ var x = 0, i;
+ for (i=0; i= 32; shift -= 32) {
+ out.push(carry);
+ carry = 0;
+ }
+ if (shift === 0) {
+ return out.concat(a);
+ }
+
+ for (i=0; i>>shift);
+ carry = a[i] << (32-shift);
+ }
+ last2 = a.length ? a[a.length-1] : 0;
+ shift2 = sjcl.bitArray.getPartial(last2);
+ out.push(sjcl.bitArray.partial(shift+shift2 & 31, (shift + shift2 > 32) ? carry : out.pop(),1));
+ return out;
+ },
+
+ /** xor a block of 4 words together.
+ * @private
+ */
+ _xor4: function(x,y) {
+ return [x[0]^y[0],x[1]^y[1],x[2]^y[2],x[3]^y[3]];
+ },
+
+ /** byteswap a word array inplace.
+ * (does not handle partial words)
+ * @param {sjcl.bitArray} a word array
+ * @return {sjcl.bitArray} byteswapped array
+ */
+ byteswapM: function(a) {
+ var i, v, m = 0xff00;
+ for (i = 0; i < a.length; ++i) {
+ v = a[i];
+ a[i] = (v >>> 24) | ((v >>> 8) & m) | ((v & m) << 8) | (v << 24);
+ }
+ return a;
+ }
+};
+/** @fileOverview Bit array codec implementations.
+ *
+ * @author Emily Stark
+ * @author Mike Hamburg
+ * @author Dan Boneh
+ */
+
+/**
+ * UTF-8 strings
+ * @namespace
+ */
+sjcl.codec.utf8String = {
+ /** Convert from a bitArray to a UTF-8 string. */
+ fromBits: function (arr) {
+ var out = "", bl = sjcl.bitArray.bitLength(arr), i, tmp;
+ for (i=0; i>> 8 >>> 8 >>> 8);
+ tmp <<= 8;
+ }
+ return decodeURIComponent(escape(out));
+ },
+
+ /** Convert from a UTF-8 string to a bitArray. */
+ toBits: function (str) {
+ str = unescape(encodeURIComponent(str));
+ var out = [], i, tmp=0;
+ for (i=0; i>>bits) >>> REMAINING);
+ if (bits < BASE) {
+ ta = arr[i] << (BASE-bits);
+ bits += REMAINING;
+ i++;
+ } else {
+ ta <<= BASE;
+ bits -= BASE;
+ }
+ }
+ while ((out.length & 7) && !_noEquals) { out += "="; }
+
+ return out;
+ },
+
+ /** Convert from a base32 string to a bitArray */
+ toBits: function(str, _hex) {
+ str = str.replace(/\s|=/g,'').toUpperCase();
+ var BITS = sjcl.codec.base32.BITS, BASE = sjcl.codec.base32.BASE, REMAINING = sjcl.codec.base32.REMAINING;
+ var out = [], i, bits=0, c = sjcl.codec.base32._chars, ta=0, x, format="base32";
+
+ if (_hex) {
+ c = sjcl.codec.base32._hexChars;
+ format = "base32hex";
+ }
+
+ for (i=0; i REMAINING) {
+ bits -= REMAINING;
+ out.push(ta ^ x>>>bits);
+ ta = x << (BITS-bits);
+ } else {
+ bits += BASE;
+ ta ^= x << (BITS-bits);
+ }
+ }
+ if (bits&56) {
+ out.push(sjcl.bitArray.partial(bits&56, ta, 1));
+ }
+ return out;
+ }
+};
+
+sjcl.codec.base32hex = {
+ fromBits: function (arr, _noEquals) { return sjcl.codec.base32.fromBits(arr,_noEquals,1); },
+ toBits: function (str) { return sjcl.codec.base32.toBits(str,1); }
+};
+/** @fileOverview Bit array codec implementations.
+ *
+ * @author Emily Stark
+ * @author Mike Hamburg
+ * @author Dan Boneh
+ */
+
+/**
+ * Base64 encoding/decoding
+ * @namespace
+ */
+sjcl.codec.base64 = {
+ /** The base64 alphabet.
+ * @private
+ */
+ _chars: "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",
+
+ /** Convert from a bitArray to a base64 string. */
+ fromBits: function (arr, _noEquals, _url) {
+ var out = "", i, bits=0, c = sjcl.codec.base64._chars, ta=0, bl = sjcl.bitArray.bitLength(arr);
+ if (_url) {
+ c = c.substr(0,62) + '-_';
+ }
+ for (i=0; out.length * 6 < bl; ) {
+ out += c.charAt((ta ^ arr[i]>>>bits) >>> 26);
+ if (bits < 6) {
+ ta = arr[i] << (6-bits);
+ bits += 26;
+ i++;
+ } else {
+ ta <<= 6;
+ bits -= 6;
+ }
+ }
+ while ((out.length & 3) && !_noEquals) { out += "="; }
+ return out;
+ },
+
+ /** Convert from a base64 string to a bitArray */
+ toBits: function(str, _url) {
+ str = str.replace(/\s|=/g,'');
+ var out = [], i, bits=0, c = sjcl.codec.base64._chars, ta=0, x;
+ if (_url) {
+ c = c.substr(0,62) + '-_';
+ }
+ for (i=0; i 26) {
+ bits -= 26;
+ out.push(ta ^ x>>>bits);
+ ta = x << (32-bits);
+ } else {
+ bits += 6;
+ ta ^= x << (32-bits);
+ }
+ }
+ if (bits&56) {
+ out.push(sjcl.bitArray.partial(bits&56, ta, 1));
+ }
+ return out;
+ }
+};
+
+sjcl.codec.base64url = {
+ fromBits: function (arr) { return sjcl.codec.base64.fromBits(arr,1,1); },
+ toBits: function (str) { return sjcl.codec.base64.toBits(str,1); }
+};
+/** @fileOverview Bit array codec implementations.
+ *
+ * @author Emily Stark
+ * @author Mike Hamburg
+ * @author Dan Boneh
+ */
+
+/**
+ * Arrays of bytes
+ * @namespace
+ */
+sjcl.codec.bytes = {
+ /** Convert from a bitArray to an array of bytes. */
+ fromBits: function (arr) {
+ var out = [], bl = sjcl.bitArray.bitLength(arr), i, tmp;
+ for (i=0; i>> 24);
+ tmp <<= 8;
+ }
+ return out;
+ },
+ /** Convert from an array of bytes to a bitArray. */
+ toBits: function (bytes) {
+ var out = [], i, tmp=0;
+ for (i=0; i 9007199254740991){
+ throw new sjcl.exception.invalid("Cannot hash more than 2^53 - 1 bits");
+ }
+
+ if (typeof Uint32Array !== 'undefined') {
+ var c = new Uint32Array(b);
+ var j = 0;
+ for (i = 512+ol - ((512+ol) & 511); i <= nl; i+= 512) {
+ this._block(c.subarray(16 * j, 16 * (j+1)));
+ j += 1;
+ }
+ b.splice(0, 16 * j);
+ } else {
+ for (i = 512+ol - ((512+ol) & 511); i <= nl; i+= 512) {
+ this._block(b.splice(0,16));
+ }
+ }
+ return this;
+ },
+
+ /**
+ * Complete hashing and output the hash value.
+ * @return {bitArray} The hash value, an array of 8 big-endian words.
+ */
+ finalize:function () {
+ var i, b = this._buffer, h = this._h;
+
+ // Round out and push the buffer
+ b = sjcl.bitArray.concat(b, [sjcl.bitArray.partial(1,1)]);
+
+ // Round out the buffer to a multiple of 16 words, less the 2 length words.
+ for (i = b.length + 2; i & 15; i++) {
+ b.push(0);
+ }
+
+ // append the length
+ b.push(Math.floor(this._length / 0x100000000));
+ b.push(this._length | 0);
+
+ while (b.length) {
+ this._block(b.splice(0,16));
+ }
+
+ this.reset();
+ return h;
+ },
+
+ /**
+ * The SHA-256 initialization vector, to be precomputed.
+ * @private
+ */
+ _init:[],
+ /*
+ _init:[0x6a09e667,0xbb67ae85,0x3c6ef372,0xa54ff53a,0x510e527f,0x9b05688c,0x1f83d9ab,0x5be0cd19],
+ */
+
+ /**
+ * The SHA-256 hash key, to be precomputed.
+ * @private
+ */
+ _key:[],
+ /*
+ _key:
+ [0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
+ 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
+ 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
+ 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,
+ 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
+ 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
+ 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
+ 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2],
+ */
+
+
+ /**
+ * Function to precompute _init and _key.
+ * @private
+ */
+ _precompute: function () {
+ var i = 0, prime = 2, factor, isPrime;
+
+ function frac(x) { return (x-Math.floor(x)) * 0x100000000 | 0; }
+
+ for (; i<64; prime++) {
+ isPrime = true;
+ for (factor=2; factor*factor <= prime; factor++) {
+ if (prime % factor === 0) {
+ isPrime = false;
+ break;
+ }
+ }
+ if (isPrime) {
+ if (i<8) {
+ this._init[i] = frac(Math.pow(prime, 1/2));
+ }
+ this._key[i] = frac(Math.pow(prime, 1/3));
+ i++;
+ }
+ }
+ },
+
+ /**
+ * Perform one cycle of SHA-256.
+ * @param {Uint32Array|bitArray} w one block of words.
+ * @private
+ */
+ _block:function (w) {
+ var i, tmp, a, b,
+ h = this._h,
+ k = this._key,
+ h0 = h[0], h1 = h[1], h2 = h[2], h3 = h[3],
+ h4 = h[4], h5 = h[5], h6 = h[6], h7 = h[7];
+
+ /* Rationale for placement of |0 :
+ * If a value can overflow is original 32 bits by a factor of more than a few
+ * million (2^23 ish), there is a possibility that it might overflow the
+ * 53-bit mantissa and lose precision.
+ *
+ * To avoid this, we clamp back to 32 bits by |'ing with 0 on any value that
+ * propagates around the loop, and on the hash state h[]. I don't believe
+ * that the clamps on h4 and on h0 are strictly necessary, but it's close
+ * (for h4 anyway), and better safe than sorry.
+ *
+ * The clamps on h[] are necessary for the output to be correct even in the
+ * common case and for short inputs.
+ */
+ for (i=0; i<64; i++) {
+ // load up the input word for this round
+ if (i<16) {
+ tmp = w[i];
+ } else {
+ a = w[(i+1 ) & 15];
+ b = w[(i+14) & 15];
+ tmp = w[i&15] = ((a>>>7 ^ a>>>18 ^ a>>>3 ^ a<<25 ^ a<<14) +
+ (b>>>17 ^ b>>>19 ^ b>>>10 ^ b<<15 ^ b<<13) +
+ w[i&15] + w[(i+9) & 15]) | 0;
+ }
+
+ tmp = (tmp + h7 + (h4>>>6 ^ h4>>>11 ^ h4>>>25 ^ h4<<26 ^ h4<<21 ^ h4<<7) + (h6 ^ h4&(h5^h6)) + k[i]); // | 0;
+
+ // shift register
+ h7 = h6; h6 = h5; h5 = h4;
+ h4 = h3 + tmp | 0;
+ h3 = h2; h2 = h1; h1 = h0;
+
+ h0 = (tmp + ((h1&h2) ^ (h3&(h1^h2))) + (h1>>>2 ^ h1>>>13 ^ h1>>>22 ^ h1<<30 ^ h1<<19 ^ h1<<10)) | 0;
+ }
+
+ h[0] = h[0]+h0 | 0;
+ h[1] = h[1]+h1 | 0;
+ h[2] = h[2]+h2 | 0;
+ h[3] = h[3]+h3 | 0;
+ h[4] = h[4]+h4 | 0;
+ h[5] = h[5]+h5 | 0;
+ h[6] = h[6]+h6 | 0;
+ h[7] = h[7]+h7 | 0;
+ }
+};
+
+
+/** @fileOverview CCM mode implementation.
+ *
+ * Special thanks to Roy Nicholson for pointing out a bug in our
+ * implementation.
+ *
+ * @author Emily Stark
+ * @author Mike Hamburg
+ * @author Dan Boneh
+ */
+
+/**
+ * CTR mode with CBC MAC.
+ * @namespace
+ */
+sjcl.mode.ccm = {
+ /** The name of the mode.
+ * @constant
+ */
+ name: "ccm",
+
+ _progressListeners: [],
+
+ listenProgress: function (cb) {
+ sjcl.mode.ccm._progressListeners.push(cb);
+ },
+
+ unListenProgress: function (cb) {
+ var index = sjcl.mode.ccm._progressListeners.indexOf(cb);
+ if (index > -1) {
+ sjcl.mode.ccm._progressListeners.splice(index, 1);
+ }
+ },
+
+ _callProgressListener: function (val) {
+ var p = sjcl.mode.ccm._progressListeners.slice(), i;
+
+ for (i = 0; i < p.length; i += 1) {
+ p[i](val);
+ }
+ },
+
+ /** Encrypt in CCM mode.
+ * @static
+ * @param {Object} prf The pseudorandom function. It must have a block size of 16 bytes.
+ * @param {bitArray} plaintext The plaintext data.
+ * @param {bitArray} iv The initialization value.
+ * @param {bitArray} [adata=[]] The authenticated data.
+ * @param {Number} [tlen=64] the desired tag length, in bits.
+ * @return {bitArray} The encrypted data, an array of bytes.
+ */
+ encrypt: function(prf, plaintext, iv, adata, tlen) {
+ var L, out = plaintext.slice(0), tag, w=sjcl.bitArray, ivl = w.bitLength(iv) / 8, ol = w.bitLength(out) / 8;
+ tlen = tlen || 64;
+ adata = adata || [];
+
+ if (ivl < 7) {
+ throw new sjcl.exception.invalid("ccm: iv must be at least 7 bytes");
+ }
+
+ // compute the length of the length
+ for (L=2; L<4 && ol >>> 8*L; L++) {}
+ if (L < 15 - ivl) { L = 15-ivl; }
+ iv = w.clamp(iv,8*(15-L));
+
+ // compute the tag
+ tag = sjcl.mode.ccm._computeTag(prf, plaintext, iv, adata, tlen, L);
+
+ // encrypt
+ out = sjcl.mode.ccm._ctrMode(prf, out, iv, tag, tlen, L);
+
+ return w.concat(out.data, out.tag);
+ },
+
+ /** Decrypt in CCM mode.
+ * @static
+ * @param {Object} prf The pseudorandom function. It must have a block size of 16 bytes.
+ * @param {bitArray} ciphertext The ciphertext data.
+ * @param {bitArray} iv The initialization value.
+ * @param {bitArray} [adata=[]] adata The authenticated data.
+ * @param {Number} [tlen=64] tlen the desired tag length, in bits.
+ * @return {bitArray} The decrypted data.
+ */
+ decrypt: function(prf, ciphertext, iv, adata, tlen) {
+ tlen = tlen || 64;
+ adata = adata || [];
+ var L,
+ w=sjcl.bitArray,
+ ivl = w.bitLength(iv) / 8,
+ ol = w.bitLength(ciphertext),
+ out = w.clamp(ciphertext, ol - tlen),
+ tag = w.bitSlice(ciphertext, ol - tlen), tag2;
+
+
+ ol = (ol - tlen) / 8;
+
+ if (ivl < 7) {
+ throw new sjcl.exception.invalid("ccm: iv must be at least 7 bytes");
+ }
+
+ // compute the length of the length
+ for (L=2; L<4 && ol >>> 8*L; L++) {}
+ if (L < 15 - ivl) { L = 15-ivl; }
+ iv = w.clamp(iv,8*(15-L));
+
+ // decrypt
+ out = sjcl.mode.ccm._ctrMode(prf, out, iv, tag, tlen, L);
+
+ // check the tag
+ tag2 = sjcl.mode.ccm._computeTag(prf, out.data, iv, adata, tlen, L);
+ if (!w.equal(out.tag, tag2)) {
+ throw new sjcl.exception.corrupt("ccm: tag doesn't match");
+ }
+
+ return out.data;
+ },
+
+ _macAdditionalData: function (prf, adata, iv, tlen, ol, L) {
+ var mac, tmp, i, macData = [], w=sjcl.bitArray, xor = w._xor4;
+
+ // mac the flags
+ mac = [w.partial(8, (adata.length ? 1<<6 : 0) | (tlen-2) << 2 | L-1)];
+
+ // mac the iv and length
+ mac = w.concat(mac, iv);
+ mac[3] |= ol;
+ mac = prf.encrypt(mac);
+
+ if (adata.length) {
+ // mac the associated data. start with its length...
+ tmp = w.bitLength(adata)/8;
+ if (tmp <= 0xFEFF) {
+ macData = [w.partial(16, tmp)];
+ } else if (tmp <= 0xFFFFFFFF) {
+ macData = w.concat([w.partial(16,0xFFFE)], [tmp]);
+ } // else ...
+
+ // mac the data itself
+ macData = w.concat(macData, adata);
+ for (i=0; i 16) {
+ throw new sjcl.exception.invalid("ccm: invalid tag length");
+ }
+
+ if (adata.length > 0xFFFFFFFF || plaintext.length > 0xFFFFFFFF) {
+ // I don't want to deal with extracting high words from doubles.
+ throw new sjcl.exception.bug("ccm: can't deal with 4GiB or more data");
+ }
+
+ mac = sjcl.mode.ccm._macAdditionalData(prf, adata, iv, tlen, w.bitLength(plaintext)/8, L);
+
+ // mac the plaintext
+ for (i=0; i n) {
+ sjcl.mode.ccm._callProgressListener(i/l);
+ n += p;
+ }
+ ctr[3]++;
+ enc = prf.encrypt(ctr);
+ data[i] ^= enc[0];
+ data[i+1] ^= enc[1];
+ data[i+2] ^= enc[2];
+ data[i+3] ^= enc[3];
+ }
+ return { tag:tag, data:w.clamp(data,bl) };
+ }
+};
+/** @fileOverview OCB 2.0 implementation
+ *
+ * @author Emily Stark
+ * @author Mike Hamburg
+ * @author Dan Boneh
+ */
+
+/**
+ * Phil Rogaway's Offset CodeBook mode, version 2.0.
+ * May be covered by US and international patents.
+ *
+ * @namespace
+ * @author Emily Stark
+ * @author Mike Hamburg
+ * @author Dan Boneh
+ */
+sjcl.mode.ocb2 = {
+ /** The name of the mode.
+ * @constant
+ */
+ name: "ocb2",
+
+ /** Encrypt in OCB mode, version 2.0.
+ * @param {Object} prp The block cipher. It must have a block size of 16 bytes.
+ * @param {bitArray} plaintext The plaintext data.
+ * @param {bitArray} iv The initialization value.
+ * @param {bitArray} [adata=[]] The authenticated data.
+ * @param {Number} [tlen=64] the desired tag length, in bits.
+ * @param {boolean} [premac=false] true if the authentication data is pre-macced with PMAC.
+ * @return The encrypted data, an array of bytes.
+ * @throws {sjcl.exception.invalid} if the IV isn't exactly 128 bits.
+ */
+ encrypt: function(prp, plaintext, iv, adata, tlen, premac) {
+ if (sjcl.bitArray.bitLength(iv) !== 128) {
+ throw new sjcl.exception.invalid("ocb iv must be 128 bits");
+ }
+ var i,
+ times2 = sjcl.mode.ocb2._times2,
+ w = sjcl.bitArray,
+ xor = w._xor4,
+ checksum = [0,0,0,0],
+ delta = times2(prp.encrypt(iv)),
+ bi, bl,
+ output = [],
+ pad;
+
+ adata = adata || [];
+ tlen = tlen || 64;
+
+ for (i=0; i+4 < plaintext.length; i+=4) {
+ /* Encrypt a non-final block */
+ bi = plaintext.slice(i,i+4);
+ checksum = xor(checksum, bi);
+ output = output.concat(xor(delta,prp.encrypt(xor(delta, bi))));
+ delta = times2(delta);
+ }
+
+ /* Chop out the final block */
+ bi = plaintext.slice(i);
+ bl = w.bitLength(bi);
+ pad = prp.encrypt(xor(delta,[0,0,0,bl]));
+ bi = w.clamp(xor(bi.concat([0,0,0]),pad), bl);
+
+ /* Checksum the final block, and finalize the checksum */
+ checksum = xor(checksum,xor(bi.concat([0,0,0]),pad));
+ checksum = prp.encrypt(xor(checksum,xor(delta,times2(delta))));
+
+ /* MAC the header */
+ if (adata.length) {
+ checksum = xor(checksum, premac ? adata : sjcl.mode.ocb2.pmac(prp, adata));
+ }
+
+ return output.concat(w.concat(bi, w.clamp(checksum, tlen)));
+ },
+
+ /** Decrypt in OCB mode.
+ * @param {Object} prp The block cipher. It must have a block size of 16 bytes.
+ * @param {bitArray} ciphertext The ciphertext data.
+ * @param {bitArray} iv The initialization value.
+ * @param {bitArray} [adata=[]] The authenticated data.
+ * @param {Number} [tlen=64] the desired tag length, in bits.
+ * @param {boolean} [premac=false] true if the authentication data is pre-macced with PMAC.
+ * @return The decrypted data, an array of bytes.
+ * @throws {sjcl.exception.invalid} if the IV isn't exactly 128 bits.
+ * @throws {sjcl.exception.corrupt} if if the message is corrupt.
+ */
+ decrypt: function(prp, ciphertext, iv, adata, tlen, premac) {
+ if (sjcl.bitArray.bitLength(iv) !== 128) {
+ throw new sjcl.exception.invalid("ocb iv must be 128 bits");
+ }
+ tlen = tlen || 64;
+ var i,
+ times2 = sjcl.mode.ocb2._times2,
+ w = sjcl.bitArray,
+ xor = w._xor4,
+ checksum = [0,0,0,0],
+ delta = times2(prp.encrypt(iv)),
+ bi, bl,
+ len = sjcl.bitArray.bitLength(ciphertext) - tlen,
+ output = [],
+ pad;
+
+ adata = adata || [];
+
+ for (i=0; i+4 < len/32; i+=4) {
+ /* Decrypt a non-final block */
+ bi = xor(delta, prp.decrypt(xor(delta, ciphertext.slice(i,i+4))));
+ checksum = xor(checksum, bi);
+ output = output.concat(bi);
+ delta = times2(delta);
+ }
+
+ /* Chop out and decrypt the final block */
+ bl = len-i*32;
+ pad = prp.encrypt(xor(delta,[0,0,0,bl]));
+ bi = xor(pad, w.clamp(ciphertext.slice(i),bl).concat([0,0,0]));
+
+ /* Checksum the final block, and finalize the checksum */
+ checksum = xor(checksum, bi);
+ checksum = prp.encrypt(xor(checksum, xor(delta, times2(delta))));
+
+ /* MAC the header */
+ if (adata.length) {
+ checksum = xor(checksum, premac ? adata : sjcl.mode.ocb2.pmac(prp, adata));
+ }
+
+ if (!w.equal(w.clamp(checksum, tlen), w.bitSlice(ciphertext, len))) {
+ throw new sjcl.exception.corrupt("ocb: tag doesn't match");
+ }
+
+ return output.concat(w.clamp(bi,bl));
+ },
+
+ /** PMAC authentication for OCB associated data.
+ * @param {Object} prp The block cipher. It must have a block size of 16 bytes.
+ * @param {bitArray} adata The authenticated data.
+ */
+ pmac: function(prp, adata) {
+ var i,
+ times2 = sjcl.mode.ocb2._times2,
+ w = sjcl.bitArray,
+ xor = w._xor4,
+ checksum = [0,0,0,0],
+ delta = prp.encrypt([0,0,0,0]),
+ bi;
+
+ delta = xor(delta,times2(times2(delta)));
+
+ for (i=0; i+4>>31,
+ x[1]<<1 ^ x[2]>>>31,
+ x[2]<<1 ^ x[3]>>>31,
+ x[3]<<1 ^ (x[0]>>>31)*0x87];
+ }
+};
+/** @fileOverview GCM mode implementation.
+ *
+ * @author Juho Vähä-Herttua
+ */
+
+/**
+ * Galois/Counter mode.
+ * @namespace
+ */
+sjcl.mode.gcm = {
+ /**
+ * The name of the mode.
+ * @constant
+ */
+ name: "gcm",
+
+ /** Encrypt in GCM mode.
+ * @static
+ * @param {Object} prf The pseudorandom function. It must have a block size of 16 bytes.
+ * @param {bitArray} plaintext The plaintext data.
+ * @param {bitArray} iv The initialization value.
+ * @param {bitArray} [adata=[]] The authenticated data.
+ * @param {Number} [tlen=128] The desired tag length, in bits.
+ * @return {bitArray} The encrypted data, an array of bytes.
+ */
+ encrypt: function (prf, plaintext, iv, adata, tlen) {
+ var out, data = plaintext.slice(0), w=sjcl.bitArray;
+ tlen = tlen || 128;
+ adata = adata || [];
+
+ // encrypt and tag
+ out = sjcl.mode.gcm._ctrMode(true, prf, data, adata, iv, tlen);
+
+ return w.concat(out.data, out.tag);
+ },
+
+ /** Decrypt in GCM mode.
+ * @static
+ * @param {Object} prf The pseudorandom function. It must have a block size of 16 bytes.
+ * @param {bitArray} ciphertext The ciphertext data.
+ * @param {bitArray} iv The initialization value.
+ * @param {bitArray} [adata=[]] The authenticated data.
+ * @param {Number} [tlen=128] The desired tag length, in bits.
+ * @return {bitArray} The decrypted data.
+ */
+ decrypt: function (prf, ciphertext, iv, adata, tlen) {
+ var out, data = ciphertext.slice(0), tag, w=sjcl.bitArray, l=w.bitLength(data);
+ tlen = tlen || 128;
+ adata = adata || [];
+
+ // Slice tag out of data
+ if (tlen <= l) {
+ tag = w.bitSlice(data, l-tlen);
+ data = w.bitSlice(data, 0, l-tlen);
+ } else {
+ tag = data;
+ data = [];
+ }
+
+ // decrypt and tag
+ out = sjcl.mode.gcm._ctrMode(false, prf, data, adata, iv, tlen);
+
+ if (!w.equal(out.tag, tag)) {
+ throw new sjcl.exception.corrupt("gcm: tag doesn't match");
+ }
+ return out.data;
+ },
+
+ /* Compute the galois multiplication of X and Y
+ * @private
+ */
+ _galoisMultiply: function (x, y) {
+ var i, j, xi, Zi, Vi, lsb_Vi, w=sjcl.bitArray, xor=w._xor4;
+
+ Zi = [0,0,0,0];
+ Vi = y.slice(0);
+
+ // Block size is 128 bits, run 128 times to get Z_128
+ for (i=0; i<128; i++) {
+ xi = (x[Math.floor(i/32)] & (1 << (31-i%32))) !== 0;
+ if (xi) {
+ // Z_i+1 = Z_i ^ V_i
+ Zi = xor(Zi, Vi);
+ }
+
+ // Store the value of LSB(V_i)
+ lsb_Vi = (Vi[3] & 1) !== 0;
+
+ // V_i+1 = V_i >> 1
+ for (j=3; j>0; j--) {
+ Vi[j] = (Vi[j] >>> 1) | ((Vi[j-1]&1) << 31);
+ }
+ Vi[0] = Vi[0] >>> 1;
+
+ // If LSB(V_i) is 1, V_i+1 = (V_i >> 1) ^ R
+ if (lsb_Vi) {
+ Vi[0] = Vi[0] ^ (0xe1 << 24);
+ }
+ }
+ return Zi;
+ },
+
+ _ghash: function(H, Y0, data) {
+ var Yi, i, l = data.length;
+
+ Yi = Y0.slice(0);
+ for (i=0; i bs) {
+ key = Hash.hash(key);
+ }
+
+ for (i=0; iUse sjcl.random as a singleton for this class!
+ *
+ * This random number generator is a derivative of Ferguson and Schneier's
+ * generator Fortuna. It collects entropy from various events into several
+ * pools, implemented by streaming SHA-256 instances. It differs from
+ * ordinary Fortuna in a few ways, though.
+ *
+ *
+ *
+ * Most importantly, it has an entropy estimator. This is present because
+ * there is a strong conflict here between making the generator available
+ * as soon as possible, and making sure that it doesn't "run on empty".
+ * In Fortuna, there is a saved state file, and the system is likely to have
+ * time to warm up.
+ *
+ *
+ *
+ * Second, because users are unlikely to stay on the page for very long,
+ * and to speed startup time, the number of pools increases logarithmically:
+ * a new pool is created when the previous one is actually used for a reseed.
+ * This gives the same asymptotic guarantees as Fortuna, but gives more
+ * entropy to early reseeds.
+ *
+ *
+ *
+ * The entire mechanism here feels pretty klunky. Furthermore, there are
+ * several improvements that should be made, including support for
+ * dedicated cryptographic functions that may be present in some browsers;
+ * state files in local storage; cookies containing randomness; etc. So
+ * look for improvements in future versions.
+ *
+ * @constructor
+ */
+sjcl.prng = function(defaultParanoia) {
+
+ /* private */
+ this._pools = [new sjcl.hash.sha256()];
+ this._poolEntropy = [0];
+ this._reseedCount = 0;
+ this._robins = {};
+ this._eventId = 0;
+
+ this._collectorIds = {};
+ this._collectorIdNext = 0;
+
+ this._strength = 0;
+ this._poolStrength = 0;
+ this._nextReseed = 0;
+ this._key = [0,0,0,0,0,0,0,0];
+ this._counter = [0,0,0,0];
+ this._cipher = undefined;
+ this._defaultParanoia = defaultParanoia;
+
+ /* event listener stuff */
+ this._collectorsStarted = false;
+ this._callbacks = {progress: {}, seeded: {}};
+ this._callbackI = 0;
+
+ /* constants */
+ this._NOT_READY = 0;
+ this._READY = 1;
+ this._REQUIRES_RESEED = 2;
+
+ this._MAX_WORDS_PER_BURST = 65536;
+ this._PARANOIA_LEVELS = [0,48,64,96,128,192,256,384,512,768,1024];
+ this._MILLISECONDS_PER_RESEED = 30000;
+ this._BITS_PER_RESEED = 80;
+};
+
+sjcl.prng.prototype = {
+ /** Generate several random words, and return them in an array.
+ * A word consists of 32 bits (4 bytes)
+ * @param {Number} nwords The number of words to generate.
+ */
+ randomWords: function (nwords, paranoia) {
+ var out = [], i, readiness = this.isReady(paranoia), g;
+
+ if (readiness === this._NOT_READY) {
+ throw new sjcl.exception.notReady("generator isn't seeded");
+ } else if (readiness & this._REQUIRES_RESEED) {
+ this._reseedFromPools(!(readiness & this._READY));
+ }
+
+ for (i=0; i0) {
+ estimatedEntropy++;
+ tmp = tmp >>> 1;
+ }
+ }
+ }
+ this._pools[robin].update([id,this._eventId++,2,estimatedEntropy,t,data.length].concat(data));
+ }
+ break;
+
+ case "string":
+ if (estimatedEntropy === undefined) {
+ /* English text has just over 1 bit per character of entropy.
+ * But this might be HTML or something, and have far less
+ * entropy than English... Oh well, let's just say one bit.
+ */
+ estimatedEntropy = data.length;
+ }
+ this._pools[robin].update([id,this._eventId++,3,estimatedEntropy,t,data.length]);
+ this._pools[robin].update(data);
+ break;
+
+ default:
+ err=1;
+ }
+ if (err) {
+ throw new sjcl.exception.bug("random: addEntropy only supports number, array of numbers or string");
+ }
+
+ /* record the new strength */
+ this._poolEntropy[robin] += estimatedEntropy;
+ this._poolStrength += estimatedEntropy;
+
+ /* fire off events */
+ if (oldReady === this._NOT_READY) {
+ if (this.isReady() !== this._NOT_READY) {
+ this._fireEvent("seeded", Math.max(this._strength, this._poolStrength));
+ }
+ this._fireEvent("progress", this.getProgress());
+ }
+ },
+
+ /** Is the generator ready? */
+ isReady: function (paranoia) {
+ var entropyRequired = this._PARANOIA_LEVELS[ (paranoia !== undefined) ? paranoia : this._defaultParanoia ];
+
+ if (this._strength && this._strength >= entropyRequired) {
+ return (this._poolEntropy[0] > this._BITS_PER_RESEED && (new Date()).valueOf() > this._nextReseed) ?
+ this._REQUIRES_RESEED | this._READY :
+ this._READY;
+ } else {
+ return (this._poolStrength >= entropyRequired) ?
+ this._REQUIRES_RESEED | this._NOT_READY :
+ this._NOT_READY;
+ }
+ },
+
+ /** Get the generator's progress toward readiness, as a fraction */
+ getProgress: function (paranoia) {
+ var entropyRequired = this._PARANOIA_LEVELS[ paranoia ? paranoia : this._defaultParanoia ];
+
+ if (this._strength >= entropyRequired) {
+ return 1.0;
+ } else {
+ return (this._poolStrength > entropyRequired) ?
+ 1.0 :
+ this._poolStrength / entropyRequired;
+ }
+ },
+
+ /** start the built-in entropy collectors */
+ startCollectors: function () {
+ if (this._collectorsStarted) { return; }
+
+ this._eventListener = {
+ loadTimeCollector: this._bind(this._loadTimeCollector),
+ mouseCollector: this._bind(this._mouseCollector),
+ keyboardCollector: this._bind(this._keyboardCollector),
+ accelerometerCollector: this._bind(this._accelerometerCollector),
+ touchCollector: this._bind(this._touchCollector)
+ };
+
+ if (window.addEventListener) {
+ window.addEventListener("load", this._eventListener.loadTimeCollector, false);
+ window.addEventListener("mousemove", this._eventListener.mouseCollector, false);
+ window.addEventListener("keypress", this._eventListener.keyboardCollector, false);
+ window.addEventListener("devicemotion", this._eventListener.accelerometerCollector, false);
+ window.addEventListener("touchmove", this._eventListener.touchCollector, false);
+ } else if (document.attachEvent) {
+ document.attachEvent("onload", this._eventListener.loadTimeCollector);
+ document.attachEvent("onmousemove", this._eventListener.mouseCollector);
+ document.attachEvent("keypress", this._eventListener.keyboardCollector);
+ } else {
+ throw new sjcl.exception.bug("can't attach event");
+ }
+
+ this._collectorsStarted = true;
+ },
+
+ /** stop the built-in entropy collectors */
+ stopCollectors: function () {
+ if (!this._collectorsStarted) { return; }
+
+ if (window.removeEventListener) {
+ window.removeEventListener("load", this._eventListener.loadTimeCollector, false);
+ window.removeEventListener("mousemove", this._eventListener.mouseCollector, false);
+ window.removeEventListener("keypress", this._eventListener.keyboardCollector, false);
+ window.removeEventListener("devicemotion", this._eventListener.accelerometerCollector, false);
+ window.removeEventListener("touchmove", this._eventListener.touchCollector, false);
+ } else if (document.detachEvent) {
+ document.detachEvent("onload", this._eventListener.loadTimeCollector);
+ document.detachEvent("onmousemove", this._eventListener.mouseCollector);
+ document.detachEvent("keypress", this._eventListener.keyboardCollector);
+ }
+
+ this._collectorsStarted = false;
+ },
+
+ /* use a cookie to store entropy.
+ useCookie: function (all_cookies) {
+ throw new sjcl.exception.bug("random: useCookie is unimplemented");
+ },*/
+
+ /** add an event listener for progress or seeded-ness. */
+ addEventListener: function (name, callback) {
+ this._callbacks[name][this._callbackI++] = callback;
+ },
+
+ /** remove an event listener for progress or seeded-ness */
+ removeEventListener: function (name, cb) {
+ var i, j, cbs=this._callbacks[name], jsTemp=[];
+
+ /* I'm not sure if this is necessary; in C++, iterating over a
+ * collection and modifying it at the same time is a no-no.
+ */
+
+ for (j in cbs) {
+ if (cbs.hasOwnProperty(j) && cbs[j] === cb) {
+ jsTemp.push(j);
+ }
+ }
+
+ for (i=0; i= 1 << this._pools.length) {
+ this._pools.push(new sjcl.hash.sha256());
+ this._poolEntropy.push(0);
+ }
+
+ /* how strong was this reseed? */
+ this._poolStrength -= strength;
+ if (strength > this._strength) {
+ this._strength = strength;
+ }
+
+ this._reseedCount ++;
+ this._reseed(reseedData);
+ },
+
+ _keyboardCollector: function () {
+ this._addCurrentTimeToEntropy(1);
+ },
+
+ _mouseCollector: function (ev) {
+ var x, y;
+
+ try {
+ x = ev.x || ev.clientX || ev.offsetX || 0;
+ y = ev.y || ev.clientY || ev.offsetY || 0;
+ } catch (err) {
+ // Event originated from a secure element. No mouse position available.
+ x = 0;
+ y = 0;
+ }
+
+ if (x != 0 && y!= 0) {
+ this.addEntropy([x,y], 2, "mouse");
+ }
+
+ this._addCurrentTimeToEntropy(0);
+ },
+
+ _touchCollector: function(ev) {
+ var touch = ev.touches[0] || ev.changedTouches[0];
+ var x = touch.pageX || touch.clientX,
+ y = touch.pageY || touch.clientY;
+
+ this.addEntropy([x,y],1,"touch");
+
+ this._addCurrentTimeToEntropy(0);
+ },
+
+ _loadTimeCollector: function () {
+ this._addCurrentTimeToEntropy(2);
+ },
+
+ _addCurrentTimeToEntropy: function (estimatedEntropy) {
+ if (typeof window !== 'undefined' && window.performance && typeof window.performance.now === "function") {
+ //how much entropy do we want to add here?
+ this.addEntropy(window.performance.now(), estimatedEntropy, "loadtime");
+ } else {
+ this.addEntropy((new Date()).valueOf(), estimatedEntropy, "loadtime");
+ }
+ },
+ _accelerometerCollector: function (ev) {
+ var ac = ev.accelerationIncludingGravity.x||ev.accelerationIncludingGravity.y||ev.accelerationIncludingGravity.z;
+ if(window.orientation){
+ var or = window.orientation;
+ if (typeof or === "number") {
+ this.addEntropy(or, 1, "accelerometer");
+ }
+ }
+ if (ac) {
+ this.addEntropy(ac, 2, "accelerometer");
+ }
+ this._addCurrentTimeToEntropy(0);
+ },
+
+ _fireEvent: function (name, arg) {
+ var j, cbs=sjcl.random._callbacks[name], cbsTemp=[];
+ /* TODO: there is a race condition between removing collectors and firing them */
+
+ /* I'm not sure if this is necessary; in C++, iterating over a
+ * collection and modifying it at the same time is a no-no.
+ */
+
+ for (j in cbs) {
+ if (cbs.hasOwnProperty(j)) {
+ cbsTemp.push(cbs[j]);
+ }
+ }
+
+ for (j=0; j 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") {
+ 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 */
+ 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 {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) {
+ params = params || {};
+ rp = rp || {};
+
+ 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) ||
+ (p.ts !== 64 && p.ts !== 96 && p.ts !== 128) ||
+ (p.ks !== 128 && p.ks !== 192 && p.ks !== 256) ||
+ (!p.iv) ||
+ (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 */
+ 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;
+
+ 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.
+ * @throws {sjcl.exception.invalid} if obj has a non-alphanumeric property.
+ * @throws {sjcl.exception.bug} if a parameter has an unsupported type.
+ */
+ encode: function (obj) {
+ var i, out='{', comma='';
+ for (i in obj) {
+ if (obj.hasOwnProperty(i)) {
+ if (!i.match(/^[a-z0-9]+$/i)) {
+ throw new sjcl.exception.invalid("json encode: invalid property name");
+ }
+ 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],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.
+ * @return {Object} The decoded structure.
+ * @throws {sjcl.exception.invalid} if str isn't (simple) JSON.
+ */
+ decode: function (str) {
+ str = str.replace(/\s/g,'');
+ 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{
if(evt.state && evt.state.hasOwnProperty('nodeID'))
this.goToNode(evt.state.nodeID, true)
@@ -35,8 +37,8 @@ export class NodeUI extends Component {
).reverse())
let children = node.Children.sort((a,b)=>{
- if(a.Name.toLowerCase() > b.Name.toLowerCase()) return 1;
- if(a.Name.toLowerCase() < b.Name.toLowerCase()) return -1;
+ if(a.Name.toLowerCase() > b.Name.toLowerCase()) return 1
+ if(a.Name.toLowerCase() < b.Name.toLowerCase()) return -1
return 0
}).map(child=>html`
this.goToNode(child.ID)}>${child.Name}
@@ -44,23 +46,46 @@ export class NodeUI extends Component {
let modified = ''
if(this.props.app.nodeModified.value)
- modified = 'modified';
+ modified = 'modified'
- let upload = '';
- if(this.upload.value)
- upload = html`<${UploadUI} nodeui=${this} />`
- let menu = '';
+ // Page to display
+ let page = ''
+ switch(this.page.value) {
+ case 'node':
+ if(node.ID > 0)
+ page = html`
+ ${children.length > 0 ? html`