keeweb/app/scripts/util/kdbxweb-init.js

149 lines
6.3 KiB
JavaScript
Raw Normal View History

2017-01-30 21:26:31 +01:00
const kdbxweb = require('kdbxweb');
2017-02-04 14:17:33 +01:00
const Logger = require('../util/logger');
const logger = new Logger('argon2');
2017-01-30 21:26:31 +01:00
const KdbxwebInit = {
init() {
2017-02-04 14:17:33 +01:00
kdbxweb.CryptoEngine.argon2 = (...args) => this.argon2(...args);
2017-01-30 21:26:31 +01:00
},
argon2(password, salt, memory, iterations, length, parallelism, type, version) {
2017-04-15 20:14:57 +02:00
const args = { password, salt, memory, iterations, length, parallelism, type, version };
return this.loadRuntime().then(runtime => {
2017-02-04 14:17:33 +01:00
const ts = logger.ts();
2017-04-15 20:14:57 +02:00
return runtime.hash(args).then(hash => {
logger.debug('Hash computed', logger.ts(ts));
return hash;
});
2017-02-04 14:17:33 +01:00
});
},
2017-04-15 20:14:57 +02:00
loadRuntime() {
2017-02-04 14:17:33 +01:00
if (this.runtimeModule) {
return Promise.resolve(this.runtimeModule);
2017-01-30 21:26:31 +01:00
}
2017-02-04 14:17:33 +01:00
if (!global.WebAssembly) {
return this.loadAsmJsFallbackRuntime();
}
return new Promise((resolve, reject) => {
const loadTimeout = setTimeout(() => reject('timeout'), 1000);
try {
const ts = logger.ts();
const argon2LoaderCode = require('argon2');
const wasmBinaryBase64 = require('argon2-wasm');
2017-04-15 20:14:57 +02:00
const moduleDecl = '{' +
'wasmJSMethod: "native-wasm",' +
'wasmBinary: Uint8Array.from(atob("' + wasmBinaryBase64 + '"), c => c.charCodeAt(0)),' +
'print(...args) { postMessage({op:"log",args}) },' +
'printErr(...args) { postMessage({op:"log",args}) },' +
'postRun:' + this.workerPostRun.toString() + ',' +
'calcHash:' + this.calcHash.toString() + ',' +
'}';
const script = argon2LoaderCode.replace('var Module', 'var Module=' + moduleDecl);
const blob = new Blob([script], {type: 'application/javascript'});
const objectUrl = URL.createObjectURL(blob);
const worker = new Worker(objectUrl);
const onMessage = e => {
switch (e.data.op) {
case 'log':
logger.debug(...e.data.args);
break;
case 'postRun':
logger.debug('WebAssembly runtime loaded', logger.ts(ts));
URL.revokeObjectURL(objectUrl);
clearTimeout(loadTimeout);
worker.removeEventListener('message', onMessage);
this.runtimeModule = {
hash(args) {
return new Promise((resolve, reject) => {
worker.postMessage(args);
const onHashMessage = e => {
worker.removeEventListener('message', onHashMessage);
if (!e.data || e.data.error || !e.data.hash) {
const ex = e.data && e.data.error || 'unexpected error';
logger.error('Worker error', ex);
reject(ex);
}
resolve(e.data.hash);
};
worker.addEventListener('message', onHashMessage);
});
}
};
resolve(this.runtimeModule);
break;
default:
logger.error('Unknown message', e.data);
URL.revokeObjectURL(objectUrl);
reject('Load error');
2017-02-04 14:17:33 +01:00
}
};
2017-04-15 20:14:57 +02:00
worker.addEventListener('message', onMessage);
2017-02-04 14:17:33 +01:00
} catch (err) {
reject(err);
}
}).catch(err => {
logger.warn('WebAssembly error', err);
return this.loadAsmJsFallbackRuntime();
});
},
2017-04-15 20:14:57 +02:00
workerPostRun() {
self.postMessage({ op: 'postRun' });
self.onmessage = e => {
try {
const hash = self.Module.calcHash(self.Module, e.data);
self.postMessage({ hash });
} catch (e) {
self.postMessage({ error: e.toString() });
}
};
},
loadAsmJsFallbackRuntime() {
2017-02-04 14:17:33 +01:00
logger.debug('Loading asm.js fallback runtime');
return new Promise(resolve => {
const ts = logger.ts();
global.Module = undefined;
const argon2Code = require('argon2-asm');
global.eval(argon2Code); // eslint-disable-line
2017-04-15 20:14:57 +02:00
this.runtimeModule = {
hash: args => Promise.resolve().then(() => this.calcHash(global.Module, args))
};
2017-02-04 14:17:33 +01:00
logger.debug('Fallback runtime loaded', logger.ts(ts));
resolve(this.runtimeModule);
});
2017-04-15 20:14:57 +02:00
},
calcHash(Module, args) {
let { password, salt } = args;
const { memory, iterations, length, parallelism, type, version } = args;
const passwordLen = password.byteLength;
password = Module.allocate(new Uint8Array(password), 'i8', Module.ALLOC_NORMAL);
const saltLen = salt.byteLength;
salt = Module.allocate(new Uint8Array(salt), 'i8', Module.ALLOC_NORMAL);
const hash = Module.allocate(new Array(length), 'i8', Module.ALLOC_NORMAL);
const encodedLen = 512;
const encoded = Module.allocate(new Array(encodedLen), 'i8', Module.ALLOC_NORMAL);
const res = Module._argon2_hash(iterations, memory, parallelism,
password, passwordLen, salt, saltLen,
hash, length, encoded, encodedLen, type, version);
if (res) {
throw new Error('Argon2 error ' + res);
}
const hashArr = new Uint8Array(length);
for (let i = 0; i < length; i++) {
hashArr[i] = Module.HEAP8[hash + i];
}
Module._free(password);
Module._free(salt);
Module._free(hash);
Module._free(encoded);
return hashArr;
2017-01-30 21:26:31 +01:00
}
};
module.exports = KdbxwebInit;