keeweb/desktop/scripts/ipc-handlers/hardware-crypto.js

93 lines
2.8 KiB
JavaScript

const { ipcMain } = require('electron');
const { readXoredValue, makeXoredValue } = require('../util/byte-utils');
const { reqNative } = require('../util/req-native');
const { isDev } = require('../util/app-info');
ipcMain.handle('hardwareCryptoDeleteKey', hardwareCryptoDeleteKey);
ipcMain.handle('hardwareEncrypt', hardwareEncrypt);
ipcMain.handle('hardwareDecrypt', hardwareDecrypt);
const keyTag = 'net.antelle.keeweb.encryption-key';
let testCipherParams;
let keyChecked = false;
async function hardwareCryptoDeleteKey() {
const secureEnclave = reqNative('secure-enclave');
await secureEnclave.deleteKeyPair({ keyTag });
keyChecked = false;
}
async function hardwareEncrypt(e, value) {
return await hardwareCrypto(value, true);
}
async function hardwareDecrypt(e, value, touchIdPrompt) {
return await hardwareCrypto(value, false, touchIdPrompt);
}
async function hardwareCrypto(value, encrypt, touchIdPrompt) {
if (process.platform !== 'darwin') {
throw new Error('Not supported');
}
// This is a native module, but why is it here and not in native-module-host?
// It's because native-module-host is started as a fork,
// and macOS thinks it doesn't have necessary entitlements,
// so any attempt to use Secure Enclave API fails with an error.
const secureEnclave = reqNative('secure-enclave');
const data = readXoredValue(value);
let res;
if (isDev && process.env.KEEWEB_EMULATE_HARDWARE_ENCRYPTION) {
const crypto = require('crypto');
if (!testCipherParams) {
let key, iv;
if (process.env.KEEWEB_EMULATE_HARDWARE_ENCRYPTION === 'persistent') {
key = Buffer.alloc(32, 0);
iv = Buffer.alloc(16, 0);
} else {
key = crypto.randomBytes(32);
iv = crypto.randomBytes(16);
}
testCipherParams = { key, iv };
}
const { key, iv } = testCipherParams;
const algo = 'aes-256-cbc';
let cipher;
if (encrypt) {
cipher = crypto.createCipheriv(algo, key, iv);
} else {
cipher = crypto.createDecipheriv(algo, key, iv);
}
res = Buffer.concat([cipher.update(data), cipher.final()]);
} else {
if (encrypt) {
await checkKey();
res = await secureEnclave.encrypt({ keyTag, data });
} else {
res = await secureEnclave.decrypt({ keyTag, data, touchIdPrompt });
}
}
data.fill(0);
return makeXoredValue(res);
async function checkKey() {
if (keyChecked) {
return;
}
try {
await secureEnclave.createKeyPair({ keyTag });
keyChecked = true;
} catch (e) {
if (!e.keyExists) {
throw e;
}
}
}
}