Przeglądaj źródła

Added web worker for parallel decryption.

Pushkar Anand 4 lat temu
rodzic
commit
8e76f6b5bd

+ 14 - 1
next.config.js

@@ -1,3 +1,16 @@
+const WorkerPlugin = require('worker-plugin');
+
 module.exports = {
-    target: 'serverless'
+    target: 'serverless',
+    webpack: (config, { buildId, dev, isServer, defaultLoaders, webpack }) => {
+        if (!isServer) {
+          config.plugins.push(
+            new WorkerPlugin({
+              // use "self" as the global object when receiving hot updates.
+              globalObject: 'self',
+            })
+          )
+        }
+        return config
+    },
 };

+ 3 - 1
package.json

@@ -14,6 +14,7 @@
     "formik": "^2.1.5",
     "http-proxy-middleware": "^1.0.5",
     "next": "9.5.3",
+    "node-forge": "^0.10.0",
     "react": "16.13.1",
     "react-bootstrap": "^1.3.0",
     "react-dom": "16.13.1",
@@ -28,6 +29,7 @@
     "@types/yup": "^0.29.7",
     "babel-plugin-styled-components": "^1.11.1",
     "next-on-netlify": "^2.4.0",
-    "typescript": "^4.0.2"
+    "typescript": "^4.0.2",
+    "worker-plugin": "^5.0.0"
   }
 }

+ 18 - 0
src/pages/gallery/components/PreviewCard.tsx

@@ -0,0 +1,18 @@
+import React from 'react';
+import Card from 'react-bootstrap/Card';
+import { fileData } from 'services/fileService';
+
+interface IProps {
+    data: fileData,
+}
+
+export default function PreviewCard(props: IProps) {
+    const { data } = props;
+
+    return (<Card>
+        <Card.Body>
+            <div>ID: {data?.id}</div>
+            <div>MetaData: {JSON.stringify(data?.metadata)}</div>
+        </Card.Body>
+    </Card>);
+}

+ 22 - 20
src/pages/gallery/index.tsx

@@ -1,34 +1,36 @@
-import React, { useContext, useEffect } from 'react';
+import React, { useLayoutEffect, useState } from 'react';
 import { useRouter } from 'next/router';
-import Container from 'components/Container';
-import Card from 'react-bootstrap/Card';
-import Button from 'react-bootstrap/Button';
-import { clearData } from 'utils/storage/localStorage';
-import { clearKeys, getKey, SESSION_KEYS } from 'utils/storage/sessionStorage';
+import Spinner from 'react-bootstrap/Spinner';
+import { getKey, SESSION_KEYS } from 'utils/storage/sessionStorage';
+import { fileData, getFiles } from 'services/fileService';
+import { getData, LS_KEYS } from 'utils/storage/localStorage';
+import PreviewCard from './components/PreviewCard';
+import { getActualKey } from 'utils/common/key';
 
 export default function Gallery() {
     const router = useRouter();
+    const [loading, setLoading] = useState(false);
+    const [data, setData] = useState<fileData[]>();
 
-    useEffect(() => {
+    useLayoutEffect(() => {
         const key = getKey(SESSION_KEYS.ENCRYPTION_KEY);
+        const token = getData(LS_KEYS.USER).token;
         if (!key) {
             router.push("/");
         }
+        const main = async () => {
+            setLoading(true);
+            const encryptionKey = await getActualKey();
+            const resp = await getFiles("0", token, "100", encryptionKey);
+            setLoading(false);
+            setData(resp);
+        };
+        main();
     }, []);
 
-    const logout = () => {
-        clearKeys();
-        clearData();
-        router.push('/');
+    if (!data || loading) {
+        return <Spinner animation="border" variant="primary"/>;
     }
 
-    return (<Container>
-        <Card className="text-center">
-            <Card.Body>
-                Imagine a very nice and secure gallery of your memories here.<br/>
-                <br/>
-                <Button block onClick={logout}>Logout</Button>
-            </Card.Body>
-        </Card>
-    </Container>);
+    return (data || []).map(item => <PreviewCard key={item.id} data={item} />);
 }

+ 33 - 0
src/services/fileService.ts

@@ -0,0 +1,33 @@
+import { getEndpoint } from "utils/common/apiUtil";
+import HTTPService from "./HTTPService";
+
+const ENDPOINT = getEndpoint();
+
+export interface fileData {
+    id: number;
+    metadata: {
+        currentTimestamp: number,
+    },
+};
+
+const getFileDataUsingWorker = (data: any, key: string) => {
+    return new Promise((resolve) => {
+        const worker = new Worker('worker/decrypt.worker.js', { type: 'module' });
+        const onWorkerMessage = (event) => resolve(event.data);
+        worker.addEventListener('message', onWorkerMessage);
+        worker.postMessage({ data, key });
+    });
+}
+
+export const getFiles = async (sinceTimestamp: string, token: string, limit: string, key: string) => {
+    const resp = await HTTPService.get(`${ENDPOINT}/encrypted-files/diff`, {
+        sinceTimestamp, token, limit,
+    });
+
+    const promises: Promise<fileData>[] = resp.data.diff.map((data) => getFileDataUsingWorker(data, key));
+    console.time('Metadata Parsing');
+    const decrypted = await Promise.all(promises);
+    console.timeEnd('Metadata Parsing');
+
+    return decrypted;
+}

+ 2 - 3
src/services/userService.ts

@@ -1,9 +1,8 @@
 import HTTPService from './HTTPService';
 import { keyAttributes } from 'types';
+import { getEndpoint } from 'utils/common/apiUtil';
 
-const dev = process.env.NODE_ENV === 'development';
-const API_ENDPOINT = process.env.NEXT_PUBLIC_ENTE_ENDPOINT || "https://api.staging.ente.io";
-const ENDPOINT = !dev ? API_ENDPOINT : '/api'
+const ENDPOINT = getEndpoint();
 
 export const getOtt = (email: string) => {
     return HTTPService.get(`${ENDPOINT}/users/ott`, { email })

+ 1697 - 0
src/utils/aescrypt/aescrypt.js

@@ -0,0 +1,1697 @@
+
+
+/*--------------------------------------------------------------------------------------------
+
+    MIT license.
+
+    Copyright 2017 Aaron Flin
+
+    Permission is hereby granted, free of charge, to any person obtaining a copy
+    of this software and associated documentation files (the "Software"), to
+    deal in the Software without restriction, including without limitation the
+    rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+    sell copies of the Software, and to permit persons to whom the Software is
+    furnished to do so, subject to the following conditions:
+
+    The above copyright notice and this permission notice shall be included in
+    all copies or substantial portions of the Software.
+
+    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+    IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+    FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+    AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+    LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+    FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+    IN THE SOFTWARE.
+
+
+----------------------------------------------------------------------------------------------*/
+import forge from 'node-forge';
+
+//start collecting random data for forge RNG
+var d = new Date();
+
+if (typeof document !== 'undefined') {
+    forge.random.collect(d.getTime(), 32);
+    //in normal version, we can get the mouse movements and add that as random data.
+    document.onmousemove = function (e) {
+        forge.random.collectInt(e.clientX, 16);
+        forge.random.collectInt(e.clientY, 16);
+    };
+}
+
+
+
+export var aescrypt = (function () {
+
+    var b2h = forge.util.bytesToHex;
+
+    function a2h(a) {
+        return forge.util.bytesToHex(arrayToString(a));
+    }
+
+    var toType = function (obj) {
+        return ({}).toString.call(obj).match(/\s([a-zA-Z0-9]+)/)[1].toLowerCase();
+    }
+
+    /* copy string or uint8array into a uint8array 
+        start is where in output array to start copying
+        max is max bytes to copy from input string/array
+    	
+        does not check if a is big enough to take s
+    */
+
+    function copyToArray(s, a, start, max) {
+        if (max === undefined) max = s.length;
+        if (start === undefined) start = 0;
+        var t = toType(s);
+
+        if (t == "arraybuffer") {
+            s = new Uint8Array(s);
+            t = 'uint8array';
+        }
+
+        switch (t) {
+            case 'array':
+            case 'uint8array':
+                //for (var i=0; i<max;i++)
+                //      a[i+start]=s[i];
+                // not much of an improvement using .set(), if any at all
+                if (max >= s.length) {
+                    a.set(s, start);
+                    return s.length;
+                } else if (t == 'array') {
+                    a.set(s.slice(0, max), start);
+                } else {
+                    a.set(s.subarray(0, max), start);
+                }
+                return max;
+            case 'string':
+                for (var i = 0; i < max; i++)
+                    a[i + start] = s.charCodeAt(i);
+                return i;
+            default:
+                return 0;
+        }
+    }
+
+
+    /* convert a uint8array or arraybuffer to a string */
+    function arrayToString(array) {
+        var ret = "";
+        if (toType(array) == 'arraybuffer')
+            array = new Uint8Array(array);
+        return String.fromCharCode.apply(null, array);
+    }
+
+    function toU8(data, nostrings) {
+        switch (toType(data)) {
+            case 'uint8array':
+                return data;
+            case 'arraybuffer':
+                return (new Uint8Array(data));
+            case 'string':
+                if (nostrings) return;
+            //no break
+            case 'array':
+                // is this the best way to do this, given encfile could be large?
+                var x = new Uint8Array(data.length);
+                copyToArray(data, x, 0);
+                return x;
+            default:
+                break;
+        }
+    }
+
+    function isblank(a, start, len) {
+        var end = start + len;
+        for (var i = start; i < end; i++)
+            if (a[i] != 0) return false;
+        return true;
+    }
+
+    function to16(str) {
+        var out, i, len, c, c2;
+        var char2, char3;
+
+        out = "";
+        len = str.length;
+        i = 0;
+        while (i < len) {
+            c = str.charCodeAt(i++);
+            c2 = c >>> 8;
+            c = c & 0xFF;
+            out += String.fromCharCode(c);
+            out += String.fromCharCode(c2);
+        }
+        return out;
+    }
+
+    function ivpasstokey(iv, pass) {
+        var hashbuf = new Uint8Array(32);
+        var hashstr, hashstr2;
+        var p = to16(pass);
+        copyToArray(iv, hashbuf, 0);
+        hashstr = arrayToString(hashbuf);
+        hashstr2 = hashstr;
+        // do this outside the loop, its expensive, and many times more expensive in chrome than firefox
+        var md = forge.md.sha256.create();
+        for (var i = 0; i < 8192; i++) {
+            //start() is not expensive, but allows us to start over with same object
+            md.start();
+            md.update(hashstr + p);
+            hashstr = md.digest().data;
+        }
+        return hashstr;
+    }
+
+    function xor16(a, b) {
+        var x = new Uint8Array(16);
+        for (i = 0; i < 16; i++)
+            x[i] = a.charCodeAt(i) ^ b.charCodeAt(i);
+        return arrayToString(x);
+    }
+
+    function makeext(x) {
+        var lenbytes = new String;
+        var t = new String;
+        var len = 0;
+        if (typeof x == 'string') {
+            len = x.length + 1;
+            t = x + String.fromCharCode(0);
+        } else {
+            //assume an array of strings
+            for (var i = 0; i < x.length; i++) {
+                len += x[i].length + 1;
+                t += x[i] + String.fromCharCode(0);
+            }
+        }
+
+        return String.fromCharCode(len >> 8) + String.fromCharCode(len & 0xFF) + t;
+    }
+
+    function cipherblock(key, iv, block) {
+        var cipher = forge.cipher.createCipher('AES-CBC', key);
+        cipher.start({ iv: iv });
+        cipher.update(forge.util.createBuffer(block));
+        cipher.finish();
+        var mod = block.length % 16;
+        //console.log(mod);
+        if (mod != 0)
+            return cipher.output.data;
+        // don't pad file that otherwise fits exactly into 16 byte blocks.
+        // forge needs the extra room for encoding a padding number in the plaintext
+        // aescrypt format has the padding number outside the encrypted text.
+        else
+            return cipher.output.data.slice(0, -16);
+
+    }
+
+    /* decipher some crypttext.  skipend fixes the truncation problem that prompted much of the commented out code below */
+
+    function decipherblock(key, iv, block, skipend) {
+        var decipher = forge.cipher.createDecipher('AES-CBC', key);
+        decipher.start({ iv: iv });
+        decipher.update(forge.util.createBuffer(block));
+        //too many negatives for a normal human brain (well, at least mine)
+        // skip end of file padding if skipend = true;
+        if (skipend !== true) decipher.finish();
+        return decipher.output.data;
+    }
+
+    /* converting a large file to a string chokes, we need to do this a bit at a time */
+    /* if its big, pass a Uint8Array instead of string                                */
+    var chunksize = 16 * 1024;
+    function hmacblock(key, block) {
+        var hmac = forge.hmac.create();
+        hmac.start('sha256', key);
+        if (toType(block) == 'uint8array') {
+            var l = block.length;
+            //console.log(l);
+            for (var i = 0; i < l; i += chunksize) {
+                var end = ((i + chunksize) < l) ? i + chunksize : l;
+                var s = arrayToString(block.subarray(i, end));
+                //console.log("updating with:"+s);
+                //console.log(s.length);
+                hmac.update(s);
+            }
+        } else {
+            hmac.update(block);
+        }
+        return hmac.digest().data;
+    }
+
+    //same thing, but use existing hmac, and don't finish
+    function hmacchunkblock(hmac, block) {
+        if (toType(block) == 'uint8array') {
+            var l = block.length, s;
+            //console.log(l);
+            for (var i = 0; i < l; i += chunksize) {
+                //var end = ( (i+chunksize)< l) ? i+chunksize: l;
+                //var s=arrayToString(block.subarray(i,end));
+                //console.log("updating with:"+s);
+                //console.log(s.length);
+                s = arrayToString(block.subarray(i, i + chunksize));
+                hmac.update(s);
+            }
+        } else {
+            hmac.update(block);
+        }
+    }
+
+
+    /* get a key and fileiv from a 96 byte credential block */
+    /* cb is an Uint8Array containing the credential block and pass is used to decrypt it */
+
+    function getkey(cb, pass) {
+        var keyiv, bufi = 0, enckeyblk, hdat, hashkey, hdatcomp, keyblock, fileiv, key, i;
+        keyiv = arrayToString(cb.subarray(bufi, bufi + 16));
+        bufi += 16;
+        enckeyblk = arrayToString(cb.subarray(bufi, bufi + 48));
+        bufi += 48;
+        hdat = arrayToString(cb.subarray(bufi, bufi + 32));
+        bufi += 32;
+        hashkey = ivpasstokey(keyiv, pass);
+        hdatcomp = hmacblock(hashkey, enckeyblk);
+        //console.log ("keyiv=" + forge.util.bytesToHex(keyiv));console.log ("enckeyblk=" + forge.util.bytesToHex(enckeyblk));console.log ("hdat=" + forge.util.bytesToHex(hdat));console.log ("hashkey=" + forge.util.bytesToHex(hashkey));console.log("hdatcomp=" + forge.util.bytesToHex(hdatcomp));
+        if (hdat != hdatcomp) {
+            //return error
+            //console.log("hmac does not match. Bad password or file corruption");
+            return { key: "", fileiv: "", error: "hmac does not match. Bad password or file corruption" };
+        }
+        i = 0;
+        keyblock = decipherblock(hashkey, keyiv, enckeyblk, true);
+        if (keyblock.length != 48) {
+            console.log("This shouldn't happen anymore. Please report this.");
+        }
+        fileiv = keyblock.slice(0, 16);
+        key = keyblock.slice(16, 48);
+        //console.log("keyblock.length="+keyblock.length);console.log ("key=" + forge.util.bytesToHex(key));console.log ("fileiv=" + forge.util.bytesToHex(fileiv));
+        return { key: key, fileiv: fileiv, error: "" };
+    }
+
+
+    /*  make the 96 byte credential block ( keyiv, encrypted fileiv+key, hmac (fileiv+key) )   */
+    /*  pass required, will generate key and fileiv if not provided                        */
+    function newcredentialblock(pass, key, fileiv) {
+        var keyiv, hashkey, keyblock, enckeyblk, tblock;
+        var buffer = new Uint8Array(96);
+        var d = new Date();
+        forge.random.collectInt(d.getTime(), 32);
+
+        //encrypt the iv-key combination (those used to encrypt file data)
+        // and chop off the padding
+
+        keyiv = forge.random.getBytesSync(16);
+        hashkey = ivpasstokey(keyiv, pass);
+        if (fileiv === undefined) fileiv = forge.random.getBytesSync(16);
+        if (key === undefined) key = forge.random.getBytesSync(32);
+        keyblock = fileiv + key;
+
+        enckeyblk = cipherblock(hashkey, keyiv, keyblock).slice(0, 48);
+
+        //this appears to be no longer a problem when using decipherblock(,,,true)
+        //i.e. dont finish(), and skip padding removal which we don't have
+        tblock = decipherblock(hashkey, keyiv, enckeyblk, true);
+        if (tblock != keyblock) {
+            //console.log("failed to find keys and ivs that forge liked...");
+            //console.log("keyblock=" + forge.util.bytesToHex(keyblock));
+            return "";
+        }
+
+        //console.log("keyiv="+forge.util.bytesToHex(keyiv));console.log("hashkey=" + forge.util.bytesToHex(hashkey));console.log("keyblock=" + forge.util.bytesToHex(keyblock));console.log("fileiv=" + forge.util.bytesToHex(fileiv));console.log("key=" + forge.util.bytesToHex(key));console.log("aes-cbc enckeyblock  ="+forge.util.bytesToHex(enckeyblk));console.log("encrypted iv+key length=" + enckeyblk.length);
+
+        // 16 + 48 + 32 bytes = 96 for entire record.
+        copyToArray((keyiv + enckeyblk + hmacblock(hashkey, enckeyblk)), buffer, 0)
+        return { buffer: buffer, key: key, fileiv: fileiv };
+    }
+
+
+    /*  get the key from main keyblock or in credential blocks      *
+     *  takes a Unit8Array                                          *
+     *  if inextcbonly==true, we will only look for the pass in the    *
+     *  extended cb block                                           *
+     *  if offset is set, we start looking for extensions at that   *
+     *  location instead of 5 bytes in                              */
+
+    var getanykey = function (file, pass, inextcbonly, offset, deletekey) {
+        var file, bufi, error, credext, credsize, newblock,
+            key, encdata, data, i = 0, cb;
+
+        //console.log("file="+ forge.util.bytesToHex(arrayToString(file)));
+        //find our extension, skip past rest. Assume from start of file, but take offset if provided
+        bufi = (offset === undefined) ? 5 : offset;
+
+        while (bufi < file.length && (file[bufi] != 0 || file[bufi + 1] != 0)) {
+            var sig, size = (file[bufi] << 8) + file[bufi + 1];
+            if (size > 17) {
+                sig = arrayToString(file.subarray(bufi + 2, bufi + 17));
+                if (sig == "enckeyblk v 0.1") {
+                    credext = bufi + 18; //2 size bytes plus ("enckeyblk v 0.1").length + 1 (0x00 at end)
+                    credsize = size - 16;
+                    //no need to go further, and we might not have a 0x0000 at the end anyway
+                    if (inextcbonly) break;
+                }
+            }
+            bufi += size + 2;
+            if (bufi > file.length) {
+                //return error
+                //console.log("error finding end of extensions");
+                return { key: "", fileiv: "", error: "error finding end of extensions" };
+            }
+        }
+        // if true skip and only look in extended credential block for key.  Skip checking of main block for this password
+        if (inextcbonly !== true) {
+            bufi += 2;
+            //get keyiv, encrypted key and file iv and hmac of encrypted key and file iv (credential block)
+            cb = file.subarray(bufi, bufi + 96);
+            bufi += 96;
+            //console.log("looking in main block");
+            key = getkey(cb, pass);
+            if (key.error == "")
+                return { key: key.key, fileiv: key.fileiv, error: "", masterkey: true };
+
+        }
+        var isarray = false;
+        var indexarray;
+        if (credext !== undefined) {
+            // find non empty blocks and check for key
+            // also check for request to delete key
+            // if deletekey is an array, it should be an array of keyslot positions to delete
+            // otherwise if ===true, it should be deleted if pass matches
+            var j = 0;
+            if (deletekey && typeof deletekey == 'array') {
+                isarray = true;
+                indexarray = [];
+            }
+            for (var i = 0; i < credsize; i += 96) {
+                var start = i + credext;
+                if (!isblank(file, start, 96)) {
+                    cb = file.subarray(start, start + 96);
+                    //console.log("cb="+forge.util.bytesToHex(arrayToString(cb)));
+                    // if we are deleting keys at certain positions
+                    if (isarray) {
+                        for (var k = 0; k < deletekey.length; k++) {
+                            if (deletekey[k] == j) {
+                                copyToArray(new Uint8Array(96), file, start);
+                                indexarray.push(j);
+                                break;
+                            }
+                        }
+                        //dont check for key.  if using array and inextcbonly===true, password can be blank
+                        continue;
+                    }
+                    //console.log("checking keyslot "+j);
+                    key = getkey(cb, pass);
+                    if (key.error == "") {
+                        if (deletekey === true) {
+                            copyToArray(new Uint8Array(96), file, start);
+                        }
+                        return { key: key.key, fileiv: key.fileiv, error: "", index: j, masterkey: false };
+                    }
+                }
+                //else console.log("empty keyslot: "+j);
+                j++;
+            }
+        }
+        if (isarray)
+            return { key: "", fileiv: '', error: '', index: indexarray };
+        else
+            return { key: "", fileiv: '', error: "Key not found using this password" };
+    }
+
+    // an array of uint8arrays, with functions to add more and extract bytes
+    // argument file and argument input in put(input) must be uint8arrays
+    // no checking or error messages.
+    function newbytebuf(file) {
+        var buffer = [];
+
+        if (file !== undefined) {
+            file = toU8(file);
+
+            if (toType(file) == 'uint8array') buffer = [file];
+
+        }
+
+        function getarrayslength(arrays) {
+            var len = 0;
+            for (var i = 0; i < arrays.length; i++)
+                len += arrays[i].length;
+            return len;
+        }
+
+        function joinarray(arrays) {
+            var len = getarrayslength(arrays), ret = new Uint8Array(len);
+
+            if (len == 0) return ret;
+
+            len = 0;
+            for (var i = 0; i < arrays.length; i++) {
+                ret.set(arrays[i], len);
+                len += arrays[i].length;
+            }
+
+            return ret;
+        }
+
+
+        return {
+            // read from this buffer (amount gotten with .get() )
+            read: 0,
+            // written to this buffer (amount pushed with put() )
+            written: getarrayslength(buffer),
+            eof: false,
+            buffers: buffer,
+            length: 0,
+            done: function () { this.eof = true; return this; },
+            // only use uint8arrays for input
+            put: function (input) {
+                if (this.eof) {
+                    //don't accept more data after eof==true
+                    //console.log("already done, can't take more data");
+                    this.error = "already done, can't take more data";
+                    return this;
+                }
+
+                input = toU8(input);
+
+                if (input !== undefined) {
+                    this.written += input.length;
+                    this.length = this.written - this.read;
+                    this.buffers.push(input);
+                }
+                else {
+                    this.error = "invalid input data in put()";
+                    //console.log("invalid input data in put()");
+                }
+                return this;
+            },
+            getlength: function () {
+                this.length = getarrayslength(this.buffers);
+                return this.length;
+            },
+            // optimized if data is still in underlying arraybuffer, but we won't go crazy.  If
+            // the unget data covers more than one buffer, then probably not worth it.
+            // if data is an int, we are putting back data that we just got, in order with no gaps.
+            // However, if that isn't possible, x should be the actual data so we have a fallback.
+            // if data is an uint8array, we just unshift the new data
+            unget: function (data, x) {
+                if ((typeof data == 'string' || typeof data == 'number') &&
+                    Math.round(data) == data) {
+                    if (data <= this.slice && this.buffers.length) {
+                        this.slice -= data.length;
+                        //reslice the first buffer
+                        var wholebuf = new Uint8Array(this.buffers[0].buffer);
+                        this.buffers[0] = wholebuf.subarray(this.slice);
+                        this.read -= data;
+                        this.length = this.written - this.read;
+                        return this;
+                    } else {
+                        //overlap, we'll unshift the data instead
+                        data = x;
+                    }
+                }
+                data = toU8(data);
+                this.read -= data.length;
+                this.buffers.unshift(data);
+                this.length = this.written - this.read;
+                return this;
+            },
+            // size=-1:  get all data as one array
+            // size>0:   shift size data from buffer.  if buffer smaller than size, return undefined
+            // size==undefined:  get a convenient amount of data out of the buffer;  if no data left, return undefined
+            get: function (size) {
+                var filebuf = this.buffers;
+                if (!filebuf || !filebuf.length) return;
+                // get the first block of data if undefined, or if that was the size requested
+                if (size === undefined || size == filebuf[0].length) {
+                    this.slice = 0;
+                    this.read += size;
+                    this.length = this.written - this.read;
+                    return (filebuf.shift());
+                }
+                //return everything if -1
+                //if no data in buffer, return undefined;
+                if (size == -1 || size == 'all') {
+                    var ret = joinarray(this.buffers);
+                    this.buffers = [];
+                    this.slice = 0;
+                    this.read += ret.length;
+                    this.length = this.written - this.read;
+                    return ret;
+                }
+
+                var endbuf = 0, tail;
+                var i = 0, len = 0, ret, jarray = [];
+
+                // is there enough data in the buffer for this request
+                for (i = 0; i < filebuf.length; i++) {
+                    //tail is how much more we need from the next array
+                    tail = size - len;
+                    len += filebuf[i].length;
+                    endbuf = i;
+                    if (len >= size) break;
+                }
+                if (len < size)
+                    //return undefined, just like shift above
+                    return;
+
+                if (endbuf == 0) {
+                    // the easy case
+                    ret = filebuf[0].subarray(0, size);
+                    filebuf[0] = filebuf[0].subarray(size);
+                    this.slice += size;
+                    this.read += size;
+                    this.length = this.written - this.read;
+                    return ret;
+                }
+
+                for (i = 0; i < endbuf; i++)
+                    jarray.push(filebuf.shift());
+
+                jarray.push(filebuf[0].subarray(0, tail));
+
+                filebuf[0] = filebuf[0].subarray(tail);
+                this.slice = tail;
+                ret = joinarray(jarray);
+                this.read += ret.length;
+                this.length = this.written - this.read;
+                return ret;
+            },
+
+            // Just like get, but don't advance or update read
+            // size=-1:  get all data as one array
+            // size>0:   shift size data from buffer.  if buffer smaller than size, return undefined
+            // size==undefined:  get a convenient amount of data out of the buffer;  if no data left, return undefined
+            preview: function (size) {
+                var filebuf = this.buffers;
+                this.length = this.written - this.read;
+                if (!filebuf || !filebuf.length) return;
+                // get the first block of data if undefined, or if that was the size requested
+                if (size === undefined || size == filebuf[0].length) {
+                    this.bufslice = 0;
+                    return (filebuf[0]);
+                }
+                //return everything if -1
+                //if no data in buffer, return undefined;
+                if (size == -1)
+                    return joinarray(this.buffers);
+
+                var endbuf = 0, tail;
+                var i = 0, len = 0, ret, jarray = [];
+
+                // is there enough data in the buffer for this request
+                for (i = 0; i < filebuf.length; i++) {
+                    //tail is how much more we need from the last array
+                    //from which we will grab data
+                    tail = size - len;
+                    len += filebuf[i].length;
+                    endbuf = i;
+                    if (len >= size) break;
+                }
+                if (len < size)
+                    //return undefined, just like shift above
+                    return;
+
+                if (endbuf == 0)
+                    return filebuf[0].subarray(0, size);
+
+                for (i = 0; i < endbuf; i++)
+                    jarray.push(filebuf[i]);
+
+                jarray.push(filebuf[i].subarray(0, tail));
+
+                return joinarray(jarray);
+            }
+        }
+    }
+
+    /*  parsefile (file)
+        takes an arraybuffer or uint8array of encrypted file and returns position of items in head
+        and start of the body
+        also checks format of file and returns errors;
+    */
+
+    var parsefile = function (file) {
+        var bufi, credblock, credext, credextsize, head, body;
+
+        //make sure we have a uint8array
+        file = toU8(file);
+
+        if (file === undefined)
+            return { data: file, error: "bad input" };
+
+        //check first 4 bytes
+        if (!(file[0] == 0x41 && file[1] == 0x45 && file[2] == 0x53)) {
+            //return error
+            //console.log("bad magic");
+            return { data: file, error: "bad magic" }
+        }
+
+        if (file[3] != 2) {
+            //return error
+            //console.log("wrong file version, only supports v2");
+            return { data: file, error: "wrong file version, only supports v2" }
+        }
+
+        //find our extension, skip past rest
+        bufi = 5;
+        while (file[bufi] != 0 || file[bufi + 1] != 0) {
+            var sig, size = (file[bufi] << 8) + file[bufi + 1];
+            if (size > 17) {
+                sig = arrayToString(file.subarray(bufi + 2, bufi + 17));
+                if (sig == "enckeyblk v 0.1") {
+                    credext = bufi + 18;
+                    credextsize = size - 16;
+                }
+                bufi += size + 2;
+            }
+            if (bufi > file.length) {
+                //return error
+                //console.log("error finding end of extensions");
+                return { data: file, error: "error finding end of extensions" };
+            }
+        }
+
+        bufi += 2;
+        credblock = bufi;
+        return { data: file, credblock: credblock, credext: credext, credextsize: credextsize, datastart: credblock + 96, error: '' };
+    }
+    /** Delete passwords from the file, Not part of the aescrypt 02 format standard */
+
+    /**
+     * Delete passwords from aescrypt.js extended aescrypt 02 formatted file.
+     *
+     * Behavior differs from other functions.  If error, still returns the encoded file back unaltered.
+     *
+     * @param encfile the bytes to encrypt (either encoded as String, one byte per
+     *          character, or as an ArrayBuffer or a Uint8Array).
+     *
+     * @param pass password String that was used for encryption 
+     *
+     * @param delblockarray array of ints between 0-15 specifying password-encrypted key slots to delete
+     *                  or array of strings containing passwords of passwords-encrypted key to delete.
+     *
+     * @param requirepass whether to use password to confirm ownership of file
+     *	              If set to false, blocks will be erased without confirming that pass will decrypt file
+     */
+
+    var delpass = function (encfile, pass, delblockarray, requirepass) {
+        var file, error, newblock, emptyblock,
+            key = "", encdata, data, i = 0, index = [], fileinfo
+        var blankarray = new Uint8Array(96);
+
+
+        //sanity check
+        if (toType(delblockarray) == 'array') {
+            var isnum = false;
+            var isstring = false;
+            for (var i = 0; i < delblockarray.length; i++) {
+                var x = delblockarray[i];
+                if (Math.round(x) == x) {
+                    if (x > -1 && x < 682)
+                        isnum = true;
+                    //in case someone has a password of all digits (baaaaaad), lets hope the number is less than 683
+                    else
+                        isstring = true;
+                }
+                //we'll skip empty strings below
+                else if (x != '')
+                    isstring = true;
+            }
+            // we will only handle an array of all strings or all numbers
+            if ((isnum && isstring) || (!isnum && !isstring))
+                return { data: file, error: "delblockarray must be an array of all numbers between 0 and 682 inclusive, or an array of all password strings" }
+        } else {
+            return { data: file, error: "delblockarray is not an array" };
+        }
+
+        //{data:file, credblock: credblock, credext: credext, credextsize: credextsize, datastart: credblock+96, error: ''}
+        //end of extensions tag (0x0000) is at credblock-2;
+        //begin of extended credblock extension, including size bytes is at credext-18;
+
+        fileinfo = parsefile(encfile);
+        if (fileinfo.error != '') return fileinfo;
+
+        file = fileinfo.data;
+        if (file === undefined)
+            return { data: file, error: "bad input" };
+
+        if (fileinfo.credext === undefined) {
+            //Nothing to do here
+            return { data: file, error: "" };
+        }
+
+        // default is to require password
+        if (requirepass !== false) {
+            key = getanykey(file, pass, false, fileinfo.credext - 18);
+            if (key.error != "")
+                return { data: file, error: key.error };
+        }
+
+        if (isstring) {
+            //find blocks matching passwords and delete it;
+            var x = [];
+            for (var i = 0; i < delblockarray.length; i++) {
+                var key = getanykey(file, delblockarray[i], true, fileinfo.credext - 18, true);//second true means delete password
+                if (key.error == '') {
+                    x.push(key.index);
+                }
+            }
+            return { data: file, error: "", index: x };
+        }
+
+        // doesn't actually check for password, only deletes.
+        var key = getanykey(file, '', true, fileinfo.credext - 18, delblockarray);
+
+        return { data: file, error: "", index: key.index };
+    }
+
+
+
+    /** Add another password to the file, Not part of the aescrypt 02 format standard */
+
+    /**
+     * Add Password to aescrypt.js extended aescrypt 02 formatted file
+     *
+     * Behavior differs from aes[en|de]crypt.  On error, return unaltered encrypted file
+     *
+     * @param encfile the bytes to encrypt (either encoded as String, one byte per
+     *          character, or as an ArrayBuffer or a Uint8Array).
+     *
+     * @param pass password String that was used for encryption 
+     *
+     * @param newpass password String to add to this file. 
+     */
+
+    var addpass = function (encfile, pass, newpass) {
+        var file, error, newblock, emptyblock, fileinfo,
+            key, i = 0, j = 0, index;
+
+        fileinfo = parsefile(encfile);
+        if (fileinfo.error != '') return fileinfo;
+
+        //{data:file, credblock: credblock, credext: credext, credextsize: credextsize, datastart: credblock+96, error: ''}
+        //end of extensions tag (0x0000) is at credblock-2;
+        //begin of extended credblock extension, including size bytes is at credext-18;
+
+        file = fileinfo.data;
+
+        // get a (the) key using this password.  Since we know the start of the extended credential extension, skip to that position
+        key = getanykey(file, pass, false, fileinfo.credext - 18);
+        if (key.error != "")
+            return { data: file, error: key.error };
+
+        newblock = newcredentialblock(newpass, key.key, key.fileiv);
+        if (newblock == "") return { data: file, error: "unable to make new password entry for file" };
+
+        if (fileinfo.credext === undefined) {
+            //we have to write a new file with room for extended credential block with 16 key slots (18 + (16*96))==1554
+            var endofext = fileinfo.credblock - 2;
+            var newfile = new Uint8Array(file.length + 1554);
+            // copy all of head up to but not including end of extension tag (0x0000)
+            copyToArray(file, newfile, 0, endofext);
+            //create a new credential block using newpass
+            //create entry for extended credential extension
+            copyToArray((String.fromCharCode(6) + String.fromCharCode(16) + "enckeyblk v 0.1" + String.fromCharCode(0)),
+                newfile, endofext);
+            //copy in the new credential block
+            copyToArray(newblock.buffer, newfile, endofext + 18);
+            //copy rest of data from original file starting with the end of extensions tag 0x0000
+            copyToArray(file.subarray(endofext), newfile, endofext + 1554);
+            return { data: newfile, index: 0, error: "" };
+        }
+
+
+        //find first empty block
+        j = 0;
+        for (var i = 0; i < fileinfo.credextsize; i += 96) {
+            var start = i + fileinfo.credext;
+            if (isblank(file, start, 96)) {
+                emptyblock = start;
+                index = j;
+                break;
+            }
+            j++;
+        }
+        // TODO: extend it again if full.
+        if (emptyblock === undefined)
+            return { data: file, error: "error: all keyslots are in use" };
+
+        copyToArray(newblock.buffer, file, emptyblock);
+        return { data: file, index: index, error: "" };
+    }
+
+    /** Decrypt data in the aescrypt 02 format (ver 1 and 0 not supported) */
+
+    /**
+     * Decrypt encfile using password where encfile is in aescrypt 02 format
+     * Return an ArrayBuffer of the plaintext file or a binary string
+     * 
+     *
+     * @param encfile the bytes to encrypt (either encoded as String, one byte per
+     *          character, or as an ArrayBuffer or Typed Array).
+     *
+     * @param pass password String that was used for encryption 
+     *
+     * @param returnstring boolean where if true function returns a binary string
+     *
+     * @param cb, callback with results
+     */
+
+    var aesdecrypt = function (encfile, pass, returnstring, cb) {
+        var emptydata = new Uint8Array(0);
+        //var usemod=true;
+
+        //take a uint8array, arraybuffer or string for file
+        var file = toU8(encfile);
+        if (file === undefined)
+            return { data: emptydata, error: "bad input" };
+
+
+        /*         testing: 
+        {
+        //var filebuf=newbytebuf(file);		
+        //or 
+        var filebuf=newbytebuf(file).done();
+
+        var key=decryptparsehead( filebuf, pass );
+        var decryptor=decryptstart(key);
+        // end of string is set with .done() above, so only need one pass in decryptpayload();
+        //var decrypted=decryptpayload(filebuf,decryptor);
+        //	while (decrypted>0) decrypted=decryptpayload(filebuf,decryptor);
+        //filebuf.done();
+        var decrypted=decryptpayload(filebuf,decryptor);
+        var error=decryptfinish(filebuf,decryptor);
+        }
+
+        console.log("test over");
+        */
+
+        var key = decrypthead(file, pass);
+        if (key.error != '') return { data: emptydata, error: key.error };
+
+        if (crypto && crypto.subtle && typeof cb == 'function') {
+            var cr = crypto.subtle;
+
+            function checkhmac(dec) {
+                cr.importKey(
+                    'raw',
+                    toU8(key.key),
+                    { name: "HMAC", hash: { name: "SHA-256" } },
+                    false,
+                    ["sign", "verify"]
+                ).then(function (k) {
+                    cr.verify(
+                        { name: "HMAC" },
+                        k,
+                        file.subarray(-32),
+                        file.subarray(key.datastart, -33)
+                    ).then(function (valid) {
+                        if (valid) cb({ data: dec, error: '' });
+                        else cb({ data: dec, error: "hmac does not match. Likely file corruption or tampering." })
+                    }).catch(function (e) {
+                        //console.log(e);
+                        cb({ data: emptydata, error: e.message });
+                    });;
+                }).catch(function (e) {
+                    //console.log(e);
+                    cb({ data: emptydata, error: e.message });
+                });
+
+            }
+            cr.importKey(
+                'raw',
+                toU8(key.key),
+                { name: "AES-CBC" },
+                false,
+                ["encrypt", "decrypt"]
+            ).then(function (k) {
+                var modbyte = file[file.length - 33];
+                var subfile = file.subarray(key.datastart, -33);
+                //console.log(modbyte);
+                if (modbyte == 0) {
+                    //we can cheat our way out of the fact that the aescrypt file format fails to put padding on
+                    //a size%16==0 sized file with forge api, but not with webcrypto api
+                    //so just use forge instead.
+                    cb(aesdecrypt(encfile, pass, returnstring));
+                    return;
+                }
+
+                cr.decrypt(
+                    { name: "AES-CBC", iv: toU8(key.fileiv) },
+                    k,
+                    subfile
+                ).then(function (decrypted) {
+                    decrypted = (new Uint8Array(decrypted)).subarray(0, (modbyte - 16));
+                    checkhmac(decrypted);
+                }).catch(function (e) {
+                    //for (var x in e)
+                    //	console.log(x+'='+e[x]);
+                    // in case we choke somewhere.  Orignally was for above mentioned aescrypt padding problem.
+                    cb(aesdecrypt(encfile, pass, returnstring)); //try with forge
+                })
+                    ;
+            }).catch(function (e) {
+                //console.log(e);
+                cb({ data: emptydata, error: e.message });
+            });
+            return false;
+        }
+
+        var decryptor = decryptstart(key);
+
+        var decrypted = decryptpayload(file, decryptor, key.datastart);
+
+        if (decrypted == -1) return { data: new Uint8Array(0), error: "file corrupted (invalid length)" };
+
+        var error = decryptfinish(file, decryptor, true);
+        if (returnstring)
+            return { data: decryptor.decipher.output.data, error: error };
+        else {
+            var a = new Uint8Array(decryptor.decipher.output.data.length);
+            copyToArray(decryptor.decipher.output.data, a);
+            return { data: a, error: error };
+        }
+    }
+
+    // do head in one go
+    function decrypthead(encfile, pass) {
+        var file, fileinfo, key;
+
+        //{data:file, credblock: credblock, credext: credext, credextsize: credextsize, datastart: credblock+96, error: ''}
+        //end of extensions tag (0x0000) is at credblock-2;
+        //begin of extended credblock extension, including size bytes is at credext-18;
+
+        fileinfo = parsefile(encfile);
+        if (fileinfo.error != '') return fileinfo;
+
+        file = fileinfo.data;
+        if (file === undefined)
+            return { data: file, error: "bad input" };
+        if (fileinfo.credext) {
+            key = getanykey(file, pass, false, fileinfo.credext - 18);
+        } else if (fileinfo.credblock) {
+            key = getkey(file.subarray(fileinfo.credblock, fileinfo.credblock + 96), pass);
+        }
+        else return ({ error: "Could not parse encrypted file" });
+        key.datastart = fileinfo.datastart;
+
+        return key;
+
+    }
+
+    // parse head of file and get key
+    // can be done in stages as more data is put into filebuf
+    // cant use parsefile() if we really want to be able to handle incoming a byte at a time
+    function decryptparsehead(filebuf, pass, progress) {
+        if (progress === undefined) progress = { stage: 0 };
+        //check first 5 bytes
+        if (progress.stage == 0) {
+            //need 5 bytes for stage 1;
+            var file = filebuf.get(5);
+            if (file == undefined) return progress;
+
+            if (!(file[0] == 0x41 && file[1] == 0x45 && file[2] == 0x53)) {
+                //return error
+                //console.log("bad magic");
+                return { error: "bad magic" }
+            }
+
+            if (file[3] != 2) {
+                //return error
+                //console.log("wrong file version, only supports v2");
+                return { error: "wrong file version, only supports v2" }
+            }
+            progress.stage = 1;
+        }
+        if (progress.stage == 1) {
+            file = filebuf.preview(2);
+            if (file === undefined) return progress;
+            while (file[0] != 0 || file[1] != 0) {
+                var sig = '';
+                var extsize = (file[0] << 8) + file[1] + 2;
+                var lengthbytes = file;
+                file = filebuf.get(extsize);
+                if (file === undefined) return progress;
+                // look for extended credential block
+                if (file.length > 17) {
+                    sig = arrayToString(file.subarray(2, 17));
+                    if (sig == "enckeyblk v 0.1") {
+                        var k = getanykey(file, pass, true, 0);
+                        if (k.error == "") progress.key = k;
+                    }
+                }
+                file = filebuf.preview(2);
+                if (file === undefined) return progress;
+            }
+            file = filebuf.get(2); //skip past the final 0x0000
+            progress.stage = 2;
+        }
+        if (progress.stage == 2) {
+            // this is our main credential block containing 
+            // keyiv, encrypted key and file iv and hmac of ( encrypted key + file iv)
+            file = filebuf.get(96);
+            if (file === undefined) return progress;
+            if (progress.key) {
+                progress.key.stage = 3;
+                return progress.key
+            } else {
+                var key = getkey(file, pass);
+                key.stage = 3;
+                return key;
+            }
+            //this is the end of the header section
+            //encrypted data is next
+        }
+    }
+
+
+    // set up decipher, hmac and an object to hold them an output array
+    function decryptstart(key) {
+        var decipher = forge.cipher.createDecipher('AES-CBC', key.key);
+        var hmac = forge.hmac.create();
+        decipher.start({ iv: key.fileiv });
+        hmac.start('sha256', key.key);
+        return { decipher: decipher, hmac: hmac, data: newbytebuf() };
+    }
+
+    // decrypt 64 bytes at a time
+    // either the buffer will need to contain the whole file, or we will
+    // probably need to feed buffer 128 bytes  at a time until the end of file, or it will all break down
+    // if position is defined, we assume file is a uint8array and data starts at pos
+    function decryptpayload(filebuf, decryptor, pos) {
+
+        var block;
+        var len = filebuf.length;
+        // 49 bytes is the minimum payload size (16 bytes encrypted data + 1 mod byte + 32 byte hmac)
+        // if we are at the end of file, just encrypt what we have left.
+        // if filebuf is an uint8array, pos will/should be defined
+        if (filebuf.eof || pos !== undefined) {
+            //console.log("got eof, doing rest of file, len="+len);
+
+            //this should only be 33 or more.
+            if (len < 33) return -1;
+            if (len == 33) return 0;
+            //get all but the last 33 bytes
+            len -= 33;
+            if (pos !== undefined)
+                block = filebuf.subarray(pos, -33);
+            else
+                block = filebuf.get(len);
+            //console.log("starting decrypt");
+            decryptor.decipher.update(forge.util.createBuffer(block));
+            //decryptor.hmac.update(arrayToString(block));
+            //console.log("starting hmac calc");
+            hmacchunkblock(decryptor.hmac, block);
+            //console.log("done with decryption");
+            return (len);
+
+            // if no end of file, then leave at least 33 bytes after this round
+        } else if (len > 97) {
+            //console.log("no eof, leaving at least 33 in buf");
+            len = len - 33;
+            len = len - len % 64
+            //console.log("decrypting "+len+" bytes")			
+            block = filebuf.get(len);
+            //now minimum in buffer is 33
+            decryptor.decipher.update(forge.util.createBuffer(block));
+            //decryptor.hmac.update(arrayToString(block));
+            hmacchunkblock(decryptor.hmac, block);
+            return (len);
+        }
+        return 0;
+    }
+
+    // if pos == true, filebuf is uint8array
+    function decryptfinish(filebuf, decryptor, pos) {
+        var hdat, hdatcomp, modbyte;
+
+        if (pos !== true) {
+            modbyte = filebuf.get(1);
+            if (modbyte === undefined) return "file corrupted (no modbyte)";
+            modbyte = modbyte[0];
+            hdat = arrayToString(filebuf.get(-1));
+        }
+        else {
+            pos = filebuf.length - 33;
+            modbyte = filebuf[pos];
+            hdat = arrayToString(filebuf.subarray(pos + 1));
+        }
+
+        if (!hdat || hdat.length != 32) return "file corrupted (invalid hmac block)";
+
+        hdatcomp = decryptor.hmac.digest().data
+        if (hdat != hdatcomp) {
+            return "hmac does not match. Likely file corruption or tampering.";
+        }
+        if (modbyte != 0)
+            decryptor.decipher.output.data = decryptor.decipher.output.data.slice(
+                0, (modbyte - 16)
+            );
+        return "";
+    }
+
+
+
+
+    /** Encrypt data in the aescrypt 02 format */
+
+    /**
+     * Encrypts filecontents with pass and returns an ArrayBuffer containing a file encrypted in the aescrypt format 02.
+     *
+     * @param filecontents the bytes to encrypt (either encoded as String, one byte per
+     *	  character, or as an ArrayBuffer or Typed Array).
+     *
+     * @param pass password String used for encryption 
+     *
+     * @param returnstring boolean if true return a binary string instead of a uint8arrray
+     *
+     * @param slotn number of slots for extra passwords (default 16)
+     *
+     * @param cb callback to receive encrypted data
+     */
+
+    var aesencrypt = function (filecontents, pass, returnstring, slotn, cb) {
+
+        var emptydata = new Uint8Array(0);
+
+        if (crypto && crypto.subtle && typeof cb == 'function') {
+            var cr = crypto.subtle;
+            var sd = encryptstart(pass, slotn, true);
+            console.log("WEBCRYPTO");
+            function cryptofinish(enc, hdat, head, mod) {
+                var output, bufi,
+                    modbyte = String.fromCharCode(mod);
+
+                output = newbytebuf();
+                output.put(head);
+                output.put(enc);
+                output.put(modbyte);
+                output.put(hdat);
+
+                output = output.get(-1);
+                //console.log(output.length);
+                return { data: output, error: "" };
+            }
+
+            function makehmac(enc) {
+                cr.importKey(
+                    'raw',
+                    toU8(sd.key),
+                    { name: "HMAC", hash: { name: "SHA-256" } },
+                    false,
+                    ["sign", "verify"]
+                ).then(function (k) {
+                    //console.log("key imported for hmac");
+                    var mod = filecontents.length % 16;
+                    if (mod == 0) enc = enc.subarray(0, -16);
+                    cr.sign(
+                        { name: "HMAC" },
+                        k,
+                        enc
+                    ).then(function (sig) {
+                        //console.log("finishing file");
+                        cb(cryptofinish(enc, sig, sd.head, mod));
+                    })/*.catch(function(e) {
+						console.log(e);
+						cb({data:emptydata,error:e.message});
+					});*/;
+                }).catch(function (e) {
+                    //for (var x in e)
+                    //	console.log(x+'='+e[x]);
+                    cb({ data: emptydata, error: e.message });
+                });
+
+            }
+
+            cr.importKey(
+                'raw',
+                toU8(sd.key),
+                { name: "AES-CBC" },
+                false,
+                ["encrypt", "decrypt"]
+            ).then(function (k) {
+                cr.encrypt(
+                    { name: "AES-CBC", iv: toU8(sd.fileiv) },
+                    k,
+                    toU8(filecontents)
+                ).then(function (encrypted) {
+                    //console.log(encrypted.byteLength);
+                    makehmac(new Uint8Array(encrypted));
+                }).catch(function (e) {
+                    //for (var x in e)
+                    //	console.log(x+'='+e[x]);
+                    cb(aesencrypt(filecontents, pass, returnstring, slotn)); //try with forge
+                })
+                    ;
+            }).catch(function (e) {
+                //console.log(e);
+                cb({ data: emptydata, error: e.message });
+            });
+            return false;
+        }
+        // the forge version
+        var startdata = encryptstart(pass, slotn);
+        var mod = encryptupdate(startdata.cipher, startdata.hmac, filecontents);
+        mod %= 16;
+        return encryptfinish(startdata.cipher, startdata.hmac, startdata.head, mod, returnstring);
+    }
+
+    //* set up file header and return keys and iv
+    function encryptstart(pass, extrakeyslots, noforge) {
+        var headstart = new Uint8Array(5);
+        var endext = new Uint8Array(2);//0x0000 for no more extensions
+        var extensions = [];
+        var bufarray = new ArrayBuffer(96);
+        var bufi = 0;
+        var buffer = new Uint8Array(bufarray);
+        var cred = '';
+        var keyiv, extlen = 0, output,
+            i = 0, //blank=new String;
+            blank;
+        var emptydata = new ArrayBuffer(0);
+        var cipher, hmac, extrapasses = [];
+        //console.log(toType(pass));
+        if (toType(pass) == 'array') {
+            extrapasses = pass;
+            pass = extrapasses.shift();
+            //console.log(pass);
+            //console.log(extrapasses);
+        }
+
+
+        // TODO: support for other than 16, and for extending a block that is too small to fit another 
+        //       password (in addpass()).  Right now, a code review of other projects would be required
+        //       since they might rely on file header size being constant (dunno, hence need for review).
+        // if (extrakeyslots===undefined) 
+        extrakeyslots = 16;
+        // max size of extension is 65536
+        if (extrakeyslots > 682) extrakeyslots = 682;
+        copyToArray("AES", headstart, 0);
+        headstart[3] = 2;
+
+        //headstart[4]=0; //aready 0;
+
+        // make credentials
+        //this is now mostly solved and should be successful on every attempt
+        var i = 0;
+        while (cred == '' && i++ < 9) {
+            //if(i>1) console.log("FAILED making new credential block");
+            cred = newcredentialblock(pass);
+        }
+        if (cred == '') {
+            //console.log("tried 8 time, bailing...");
+            return { head: emptydata, error: "could not make a new set of credentials" };
+        }
+
+        /* **** add extensions here **** */
+
+        extensions.push(makeext(["CREATED_BY", "aescrypt.js 0.1"]));
+
+        //add blank 128 byte extension
+
+        blank = new Uint8Array(130);
+        blank.set([128], 1);
+        extensions.push(blank);
+
+        //add extended credential block
+        //and add extra passwords, if available;
+        if (extrakeyslots > 0) {
+            var csize = 16 + (96 * extrakeyslots);
+            var h = String.fromCharCode(csize >>> 8) + String.fromCharCode(csize & 0xFF) + "enckeyblk v 0.1" + String.fromCharCode(0);
+            blank = new Uint8Array(csize + 2);
+            copyToArray(h, blank);
+            var len = (extrapasses.length < 16) ? extrapasses.length : 16;
+            for (var i = 0; i < len; i++) {
+                var newpass = extrapasses[i],
+                    newblock = newcredentialblock(newpass, cred.key, cred.fileiv),
+                    start = 18 + (i * 96);
+
+                copyToArray(newblock.buffer, blank, start);
+            }
+            extensions.push(blank);
+        }
+
+        /*
+        blank=String.fromCharCode(0)+String.fromCharCode(128);
+        for (var i=0;i<128;i++)
+            blank+=String.fromCharCode(0);
+        extensions.push(blank);	
+    	
+        //add an area for 16 96-byte blocks for extra keyiv-enckeyblk-hmac combos ("credential blocks")
+        blank=String.fromCharCode(06)+String.fromCharCode(16)+"enckeyblk v 0.1"+String.fromCharCode(0);
+        for (var i=0;i<1536;i++)
+            blank+=String.fromCharCode(0);
+        extensions.push(blank);
+        */
+
+
+        /* **** end add extensions  **** */
+
+        //the last extension  - 0x0000 to mark end of extensions
+        extensions.push(endext);
+
+        // get length of all extensions
+        for (var i = 0; i < extensions.length; i++)
+            extlen += extensions[i].length;
+
+        output = new Uint8Array(
+            headstart.length +
+            extlen +
+            cred.buffer.length
+        );
+
+        bufi = 0;
+        bufi += copyToArray(headstart, output, bufi);
+        for (var i = 0; i < extensions.length; i++)
+            bufi += copyToArray(extensions[i], output, bufi);
+        bufi += copyToArray(cred.buffer, output, bufi);
+
+        if (!noforge) {
+            cipher = forge.cipher.createCipher('AES-CBC', cred.key);
+            cipher.start({ iv: cred.fileiv });
+
+            hmac = forge.hmac.create();
+            hmac.start('sha256', cred.key);
+            return { head: output, error: "", cipher: cipher, hmac: hmac };
+        }
+
+        return { head: output, error: "", fileiv: cred.fileiv, key: cred.key };
+    }
+
+    function encryptupdate(cipher, hmac, data, newiv) {
+        var encdata;
+        // newiv is a misnomer since cipher.start({iv:newiv}) doesn't work here (not sure why)
+        if (newiv !== false && newiv !== undefined) {
+            //cipher.start({iv:newiv});
+            cipher.output.data = newiv;
+            cipher.update(forge.util.createBuffer(data));
+            cipher.output.data = cipher.output.data.slice(16);
+        }
+        else
+            cipher.update(forge.util.createBuffer(data));
+
+        hmac.update(cipher.output.data);
+
+        return (data.length);
+    }
+
+    function encryptfinish(cipher, hmac, head, mod, returnstring) {
+        var output, len, bufi, hdat,
+            modbyte = String.fromCharCode(mod),
+            hlen = cipher.output.data.length;
+
+        cipher.finish();
+        if (mod != 0)
+            len = cipher.output.data.length;
+
+        // don't pad file that otherwise fits exactly into 16 byte blocks.
+        // forge needs the extra room for encoding a padding number in the plaintext (Section 10.3 of [RFC2315], step 2)
+        // aescrypt format has the padding number outside the encrypted text.
+        else
+            len = cipher.output.data.length - 16;
+
+        //how much extra data left to hmac, negative number or zero
+        hlen -= len;
+
+        //update our hmac with extra data
+        if (hlen < 0)
+            hmac.update(cipher.output.data.slice(hlen));
+
+        hdat = hmac.digest().data;
+
+
+        if (returnstring) {
+            // do not use with chunking
+            return {
+                data: ("").concat(arrayToString(head), cipher.output.data.slice(0, len), modbyte, hdat),
+                error: ""
+            }
+        } else {
+            output = new Uint8Array(
+                head.length +
+                len +
+                modbyte.length +
+                hdat.length
+            );
+            // if getChunk is used below, head will be "" and cipher.output.data
+            // will have been moved off and also be "", or a tail portion of it
+            bufi = 0;
+            bufi += copyToArray(head, output, bufi);
+            bufi += copyToArray(cipher.output.data, output, bufi, len);
+            bufi += copyToArray(modbyte, output, bufi);
+            bufi += copyToArray(hdat, output, bufi);
+            return { data: output, error: "" };
+        }
+    }
+
+
+    var chunkencrypt = function (pass) {
+        return {
+            start: function (pass) {
+                var encstart = encryptstart(pass);
+                this.hmac = encstart.hmac;
+                this.cipher = encstart.cipher;
+                this.head = arrayToString(encstart.head);
+                this.newiv = false;
+                this.length = 0;
+                //there really should be no errors
+                this.error = encstart.error;
+            },
+            update: function (data) {
+                this.length += encryptupdate(this.cipher, this.hmac, data, this.newiv);
+                this.newiv = false;
+                return this;
+            },
+            getChunk: function (size) {
+                //return size must be a multiple of size
+
+                var ret, retsize;
+
+                if (this.leftover) {
+                    this.cipher.output.data = this.leftover + this.cipher.output.data;
+                    delete this.leftover;
+                }
+
+                retsize = parseInt((this.head.length + this.cipher.output.data.length) / size) * size;
+                //console.log("retsize="+retsize+ " or "+size+ ' * ' + parseInt( (this.head.length+this.cipher.output.data.length)/size ) );
+
+                if (retsize == 0) return;
+
+                if (retsize == this.head.length) {
+                    ret = this.head;
+                } else if (retsize < this.head.length) {
+                    ret = this.head.slice(0, retsize);
+                    this.leftover = this.head.slice(retsize);
+                } else {
+                    // concat head to cipher output to make desired size
+                    if (this.head.length) {
+                        retsize -= this.head.length;
+                        ret = this.head + this.cipher.output.data.slice(0, retsize);
+                    } else {
+                        ret = this.cipher.output.data.slice(0, retsize);
+                    }
+
+                    this.leftover = this.cipher.output.data.slice(retsize); //just add this on next time.
+                    this.newiv = this.cipher.output.data.slice(-16); //put 16 bytes back in mix for next cbc mode computation
+                    this.cipher.output.data = "";
+                }
+
+                this.head = '';
+                var reta = new Uint8Array(ret.length);
+                copyToArray(ret, reta);
+                return { data: reta, error: "" };
+
+
+                /*-------------------------- old way------------------------------------------
+                ret = this.head + 
+                        this.cipher.output.data;
+            	
+                // only update if we at least as much data as requsted
+                if (size && size > ret.length)
+                    return;		
+                    	
+                var reta=new Uint8Array(ret.length);
+
+                if( ret.length>0)
+                    copyToArray(ret,reta);
+                else 
+                    return;
+            	
+                // cbc mode needs last 16 bytes to compute next 16
+                // and we'll make adjustments in next update()
+                if (this.cipher.output.data.length) {
+                    this.newiv=this.cipher.output.data.slice(-16);
+                    this.cipher.output.data="";
+                }
+
+                this.head="";
+                            	
+                return {data: reta, error: ""};
+                ----------------------------*/
+            },
+            finish: function () {
+                if (this.leftover) {
+                    this.cipher.output.data = this.leftover + this.cipher.output.data;
+                    delete this.leftover;
+                }
+                var f = encryptfinish(this.cipher, this.hmac, this.head, (this.length % 16));
+                return f;
+            },
+        }
+    };
+
+    /*
+    var aesdecrypt=function(encfile,pass,returnstring) {
+        var emptydata=new ArrayBuffer(0);
+        //var usemod=true;
+
+        //take a uint8array, arraybuffer or string for file
+        file=toU8(encfile);
+        if (file===undefined)
+            return {data:emptydata, error:"bad input"};
+        var filebuf=newbytebuf(file).done();
+        var key=decryptparsehead( filebuf, pass );
+        if (key.error!='') return {data:emptydata, error: key.error};
+        var decryptor=decryptstart(key);
+    	
+        // end of string is set with .done() above, so only need one pass in decryptpayload();
+        var decrypted=decryptpayload(filebuf,decryptor);
+        if (decrypted == -1) return {data:new Uint8Array(0),error:"file corrupted"};
+    	
+        var error=decryptfinish(filebuf,decryptor);
+        if(returnstring)
+            return {data:decryptor.decipher.output.data, error: error};
+        else {
+            var a=new Uint8Array(decryptor.decipher.output.data.length);
+            copyToArray(decryptor.decipher.output.data,a);
+            return {data: a, error: error};
+        }
+    }
+*/
+    //data is not required in start;
+    var chunkdecrypt = function () {
+        return {
+            start: function (pass) {
+                if (!pass || pass == "") {
+                    this.error = "password missing";
+                    return this;
+                }
+                this.pass = pass;
+                this.progress = { stage: 0 };
+                this.filebuf = newbytebuf();
+                this.output = newbytebuf();
+                this.error = "";
+                this.lasterror = "";
+                delete this.decryptor;
+            },
+            update: function (data) {
+                // go no further on unrecoverable error. error message should be in this.error;
+                this.newoutput = false;
+                if (this.progress.stage == -1)
+                    return this;
+
+                // push our data into the buffer
+                this.filebuf.put(data);
+                if (this.progress.stage < 3)
+                    this.progress = decryptparsehead(this.filebuf, this.pass, this.progress);
+
+                if (this.progress.error) {
+                    this.pass = '';
+                    this.error = this.progress.error;
+                    this.progress.stage = -1;
+                    return this;
+                }
+
+                if (this.progress.stage == 3) {
+                    // we should have our key in this.progress.key
+                    // this stage requires no data from filebuf
+
+                    //dont need password lying around;
+                    delete this.pass;
+                    this.decryptor = decryptstart(this.progress);
+                    this.progress.stage = 4;
+                    //dont need key anylonger
+                    delete this.key;
+                }
+                // kinda unnecessary since stage 3 requires no data
+                // but might want to put error checking into decryptstart some day.
+                if (this.progress.stage == 4) {
+                    // decrypt whatever is in the buffer in multiples of 64 bytes, leaving at least 33 at end
+                    var decrypted = decryptpayload(this.filebuf, this.decryptor);
+                    if (decrypted > 0) {
+                        this.output.put(this.decryptor.decipher.output.data);
+                        this.decryptor.decipher.output.data = "";
+                        this.newoutput = true;
+                    } else if (decrypted == -1) {
+                        this.error = "file corrupted (invalid length)";
+                        this.progress.stage = -1;
+                    }
+                }
+                return this;
+            },
+            getChunk: function (size) {
+                var ret;
+                // get all of the buffer if size undefined
+                if (size === undefined) size = -1;
+                if (size == 0 || !this.output)
+                    return;
+
+                ret = this.output.get(size);
+                // return undefined if no data				
+                if (!ret || ret.length == 0) return;
+
+                if (this.output.eof && this.output.length == 0)
+                    delete this.output;
+
+                return { data: ret, error: this.error };
+            },
+            finish: function () {
+                // mark eof
+                this.filebuf.done();
+                // finish off whatever is in the buffer
+                var decrypted = decryptpayload(this.filebuf, this.decryptor);
+                if (decrypted > 0) {
+                    //check hmac and truncate
+                    this.error = decryptfinish(this.filebuf, this.decryptor);
+                    //copy output
+                    this.output.put(this.decryptor.decipher.output.data);
+                    //empty forge buffer
+                    this.decryptor.decipher.output.data = "";
+                    this.newoutput = true;
+                    this.output.eof = true;
+                } else if (decrypted == -1) {
+                    this.error = "file corrupted (invalid length)";
+                } else if (decrypted == 0) {
+                    delete this.output;
+                }
+
+                this.progress.stage = -1;
+
+                delete this.filebuf;
+                delete this.decryptor;
+                var lastchunk = this.getChunk();
+                if (lastchunk && lastchunk.data)
+                    lastchunk = lastchunk.data;
+                return { data: lastchunk, error: this.error };
+            },
+        }
+    };
+
+
+    return ({
+        chunkEncrypt: chunkencrypt,
+        chunkDecrypt: chunkdecrypt,
+        encrypt: aesencrypt,
+        decrypt: aesdecrypt,
+        addPassword: addpass,
+        delPassword: delpass,
+        util: {
+            copyToArray: copyToArray,
+            arrayToString: arrayToString,
+            toType: toType,
+            newbytebuf: newbytebuf
+        }
+    });
+})();

+ 21 - 0
src/utils/aescrypt/index.ts

@@ -0,0 +1,21 @@
+import { resolve } from 'path';
+import { aescrypt } from './aescrypt';
+
+const decrypt = (file: Uint8Array, password: String, binaryResponse: Boolean = false) => {
+    return new Promise((resolve, reject) => {
+        try {
+            aescrypt.decrypt(file, password, !binaryResponse, ({ data, error}) => {
+                if (error) {
+                    reject(error);
+                }
+                resolve(data);
+            });
+        } catch (e) {
+            reject(e);
+        }
+    });
+}
+
+export default {
+    decrypt,
+}

+ 6 - 0
src/utils/common/apiUtil.ts

@@ -0,0 +1,6 @@
+export const getEndpoint = () => {
+    const dev = process.env.NODE_ENV === 'development';
+    const apiEndpoint = process.env.NEXT_PUBLIC_ENTE_ENDPOINT || "https://api.staging.ente.io";
+    const endpoint = !dev ? apiEndpoint : '/api';
+    return endpoint;
+}

+ 9 - 0
src/utils/common/key.ts

@@ -0,0 +1,9 @@
+import { decrypt } from "utils/crypto/aes";
+import { getData, LS_KEYS } from "utils/storage/localStorage";
+import { getKey, SESSION_KEYS } from "utils/storage/sessionStorage";
+
+export const getActualKey = async () =>  {
+    const key = getKey(SESSION_KEYS.ENCRYPTION_KEY).encryptionKey;
+    const session = getData(LS_KEYS.SESSION);
+    return await decrypt(key, session.sessionKey, session.sessionIV);
+}

+ 2 - 2
src/utils/crypto/aes.ts

@@ -13,7 +13,7 @@ export async function encrypt(data: string, key: string, iv: string) {
         false, ['encrypt', 'decrypt']
     );
 
-    const result = await window.crypto.subtle.encrypt(
+    const result = await crypto.subtle.encrypt(
         {
             name: "AES-CBC",
             iv: base64ToUint8(iv),
@@ -38,7 +38,7 @@ export async function decrypt(data: string, key: string, iv: string) {
         false, ['encrypt', 'decrypt']
     );
 
-    const result = await window.crypto.subtle.decrypt(
+    const result = await crypto.subtle.decrypt(
         {
             name: "AES-CBC",
             iv: base64ToUint8(iv),

+ 22 - 0
src/worker/decrypt.worker.js

@@ -0,0 +1,22 @@
+import { decrypt } from "utils/crypto/aes";
+import { base64ToUint8 } from "utils/crypto/common";
+import aescrypt from 'utils/aescrypt';
+
+function decryptFile(event) {
+    const main = async () => {
+        const data = event.data.data;
+        const key = event.data.key;
+        const password = await decrypt(data.encryptedPassword, key, data.encryptedPasswordIV);
+        const metadata = await aescrypt.decrypt(base64ToUint8(data.encryptedMetadata), atob(password));
+        self.postMessage({
+            id: data.id,
+            ownerId: data.ownerId,
+            updationTime: data.updationTime,
+            password,
+            metadata: JSON.parse(metadata),
+        });
+    }
+    main();
+}
+
+self.addEventListener('message', decryptFile);

+ 2 - 1
tsconfig.json

@@ -4,7 +4,8 @@
     "lib": [
       "dom",
       "dom.iterable",
-      "esnext"
+      "esnext",
+      "webworker"
     ],
     "allowJs": true,
     "skipLibCheck": true,

+ 13 - 1
yarn.lock

@@ -3473,7 +3473,7 @@ loader-utils@2.0.0, loader-utils@^2.0.0:
     emojis-list "^3.0.0"
     json5 "^2.1.2"
 
-loader-utils@^1.2.3:
+loader-utils@^1.1.0, loader-utils@^1.2.3:
   version "1.4.0"
   resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-1.4.0.tgz#c579b5e34cb34b1a74edc6c1fb36bfa371d5a613"
   integrity sha512-qH0WSMBtn/oHuwjy/NucEgbx5dbxxnxup9s4PVXJUDHZBQY+s0NWA9rJf53RBnQZxfch7euUui7hpoAPvALZdA==
@@ -3893,6 +3893,11 @@ node-fetch@2.6.0:
   resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.0.tgz#e633456386d4aa55863f676a7ab0daa8fdecb0fd"
   integrity sha512-8dG4H5ujfvFiqDmVu9fQ5bOHUC15JMjMY/Zumv26oOvvVJjM67KF8koCWIabKQ1GJIa9r2mMZscBq/TbdOcmNA==
 
+node-forge@^0.10.0:
+  version "0.10.0"
+  resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-0.10.0.tgz#32dea2afb3e9926f02ee5ce8794902691a676bf3"
+  integrity sha512-PPmu8eEeG9saEUvI97fm4OYxXVB6bFvyNTyiUOBichBpFG8A1Ljw3bY62+5oOjDEMHRnd0Y7HQ+x7uzxOzC6JA==
+
 node-gyp-build@^4.2.2:
   version "4.2.3"
   resolved "https://registry.yarnpkg.com/node-gyp-build/-/node-gyp-build-4.2.3.tgz#ce6277f853835f718829efb47db20f3e4d9c4739"
@@ -5630,6 +5635,13 @@ worker-farm@^1.7.0:
   dependencies:
     errno "~0.1.7"
 
+worker-plugin@^5.0.0:
+  version "5.0.0"
+  resolved "https://registry.yarnpkg.com/worker-plugin/-/worker-plugin-5.0.0.tgz#113b5fe1f4a5d6a957cecd29915bedafd70bb537"
+  integrity sha512-AXMUstURCxDD6yGam2r4E34aJg6kW85IiaeX72hi+I1cxyaMUtrvVY6sbfpGKAj5e7f68Acl62BjQF5aOOx2IQ==
+  dependencies:
+    loader-utils "^1.1.0"
+
 wrappy@1:
   version "1.0.2"
   resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f"