mirror of https://github.com/keeweb/keeweb.git
223 lines
9.6 KiB
JavaScript
223 lines
9.6 KiB
JavaScript
import kdbxweb from 'kdbxweb';
|
|
import { Logger } from 'util/logger';
|
|
import { Features } from 'util/features';
|
|
|
|
const logger = new Logger('argon2');
|
|
|
|
const KdbxwebInit = {
|
|
init() {
|
|
kdbxweb.CryptoEngine.argon2 = (...args) => this.argon2(...args);
|
|
},
|
|
|
|
argon2(password, salt, memory, iterations, length, parallelism, type, version) {
|
|
const args = { password, salt, memory, iterations, length, parallelism, type, version };
|
|
return this.loadRuntime(memory).then(runtime => {
|
|
const ts = logger.ts();
|
|
return runtime.hash(args).then(hash => {
|
|
logger.debug('Hash computed', logger.ts(ts));
|
|
return hash;
|
|
});
|
|
});
|
|
},
|
|
|
|
loadRuntime(requiredMemory) {
|
|
if (this.runtimeModule) {
|
|
return Promise.resolve(this.runtimeModule);
|
|
}
|
|
if (!global.WebAssembly) {
|
|
return Promise.reject('WebAssembly is not supported');
|
|
}
|
|
return new Promise((resolve, reject) => {
|
|
const loadTimeout = setTimeout(() => reject('timeout'), 5000);
|
|
try {
|
|
const ts = logger.ts();
|
|
const argon2LoaderCode = require('argon2').default;
|
|
const wasmBinaryBase64 = require('argon2-wasm');
|
|
|
|
const KB = 1024 * 1024;
|
|
const MB = 1024 * KB;
|
|
const GB = 1024 * MB;
|
|
const WASM_PAGE_SIZE = 64 * 1024;
|
|
const totalMemory = (2 * GB - 64 * KB) / 1024 / WASM_PAGE_SIZE;
|
|
const initialMemory = Math.min(
|
|
Math.max(Math.ceil((requiredMemory * 1024) / WASM_PAGE_SIZE), 256) + 256,
|
|
totalMemory
|
|
);
|
|
|
|
if (Features.canUseWasmInWebWorker) {
|
|
const memoryDecl = `var wasmMemory=new WebAssembly.Memory({initial:${initialMemory},maximum:${totalMemory}});`;
|
|
const moduleDecl =
|
|
'var Module={' +
|
|
'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() +
|
|
',' +
|
|
'wasmMemory:wasmMemory,' +
|
|
'buffer:wasmMemory.buffer,' +
|
|
'TOTAL_MEMORY:' +
|
|
initialMemory * WASM_PAGE_SIZE +
|
|
'}';
|
|
const script = argon2LoaderCode.replace(
|
|
/^var Module.*?}/,
|
|
memoryDecl + 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 (web worker)',
|
|
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
|
|
);
|
|
worker.terminate();
|
|
KdbxwebInit.runtimeModule = null;
|
|
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);
|
|
} else {
|
|
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');
|
|
}
|
|
};
|
|
worker.addEventListener('message', onMessage);
|
|
} else {
|
|
// Chrome and Electron crash if we use WASM in WebWorker
|
|
// see https://github.com/keeweb/keeweb/issues/1263
|
|
const wasmMemory = new WebAssembly.Memory({
|
|
initial: initialMemory,
|
|
maximum: totalMemory
|
|
});
|
|
global.Module = {
|
|
wasmJSMethod: 'native-wasm',
|
|
wasmBinary: Uint8Array.from(atob(wasmBinaryBase64), c => c.charCodeAt(0)),
|
|
print(...args) {
|
|
logger.debug(...args);
|
|
},
|
|
printErr(...args) {
|
|
logger.debug(...args);
|
|
},
|
|
postRun: () => {
|
|
logger.debug('WebAssembly runtime loaded (main thread)', logger.ts(ts));
|
|
clearTimeout(loadTimeout);
|
|
resolve({
|
|
hash: args => {
|
|
const hash = this.calcHash(global.Module, args);
|
|
global.Module.unloadRuntime();
|
|
global.Module = undefined;
|
|
return Promise.resolve(hash);
|
|
}
|
|
});
|
|
},
|
|
wasmMemory,
|
|
buffer: wasmMemory.buffer,
|
|
TOTAL_MEMORY: initialMemory * WASM_PAGE_SIZE
|
|
};
|
|
// eslint-disable-next-line no-eval
|
|
eval(argon2LoaderCode);
|
|
}
|
|
} catch (err) {
|
|
reject(err);
|
|
}
|
|
}).catch(err => {
|
|
logger.warn('WebAssembly error', err);
|
|
throw new Error('WebAssembly error');
|
|
});
|
|
},
|
|
|
|
// eslint-disable-next-line object-shorthand
|
|
workerPostRun: function() {
|
|
self.postMessage({ op: 'postRun' });
|
|
self.onmessage = e => {
|
|
try {
|
|
/* eslint-disable-next-line no-undef */
|
|
const hash = Module.calcHash(Module, e.data);
|
|
self.postMessage({ hash });
|
|
} catch (e) {
|
|
self.postMessage({ error: e.toString() });
|
|
}
|
|
};
|
|
},
|
|
|
|
// eslint-disable-next-line object-shorthand
|
|
calcHash: function(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;
|
|
}
|
|
};
|
|
|
|
export { KdbxwebInit };
|