Skip to content

Commit

Permalink
feat: restrict the Windows Hello-related libraries from running in ch…
Browse files Browse the repository at this point in the history
…ild processes. (#6108)
  • Loading branch information
huhuanming authored Nov 1, 2024
1 parent 8e67746 commit f3c6680
Show file tree
Hide file tree
Showing 5 changed files with 193 additions and 30 deletions.
2 changes: 1 addition & 1 deletion apps/desktop/scripts/build.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ const gitRevision = childProcess

const hrstart = process.hrtime();
build({
entryPoints: ['app.ts', 'preload.ts'].map((f) =>
entryPoints: ['app.ts', 'preload.ts', 'service/windowsHello.ts'].map((f) =>
path.join(electronSource, f),
),
platform: 'node',
Expand Down
68 changes: 39 additions & 29 deletions apps/desktop/src-electron/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,6 @@ import {
import contextMenu from 'electron-context-menu';
import isDev from 'electron-is-dev';
import logger from 'electron-log/main';
import windowsSecurityCredentialsUiModule, {
UserConsentVerificationResult,
UserConsentVerifierAvailability,
} from 'electron-windows-security';

import {
ONEKEY_APP_DEEP_LINK_NAME,
Expand All @@ -48,6 +44,11 @@ import * as store from './libs/store';
import { parseContentPList } from './libs/utils';
import initProcess, { restartBridge } from './process';
import { resourcesPath, staticPath } from './resoucePath';
import {
checkAvailabilityAsync,
requestVerificationAsync,
startServices,
} from './service';

logger.initialize();
logger.transports.file.maxSize = 1024 * 1024 * 10;
Expand Down Expand Up @@ -549,18 +550,17 @@ function createMainWindow() {

ipcMain.on(ipcMessageKeys.TOUCH_ID_CAN_PROMPT, async (event) => {
if (isWin) {
const result = await new Promise((resolve) => {
windowsSecurityCredentialsUiModule.UserConsentVerifier.checkAvailabilityAsync(
(error, status) => {
if (error) {
resolve(false);
} else {
resolve(status === UserConsentVerifierAvailability.available);
}
},
logger.info('[TOUCH_ID_CAN_PROMPT] Windows checkAvailabilityAsync');
try {
const result = await checkAvailabilityAsync();
event.returnValue = result;
} catch (error) {
logger.info(
'[TOUCH_ID_CAN_PROMPT] Windows checkAvailabilityAsync',
error,
);
});
event.returnValue = result;
event.returnValue = false;
}
return;
}
const result = systemPreferences?.canPromptTouchID?.();
Expand Down Expand Up @@ -607,23 +607,32 @@ function createMainWindow() {

ipcMain.on(ipcMessageKeys.TOUCH_ID_PROMPT, async (event, msg: string) => {
if (isWin) {
windowsSecurityCredentialsUiModule.UserConsentVerifier.requestVerificationAsync(
msg,
(error, status) => {
if (error) {
event.reply(ipcMessageKeys.TOUCH_ID_PROMPT_RES, {
success: false,
error: error.message,
});
} else {
event.reply(ipcMessageKeys.TOUCH_ID_PROMPT_RES, {
success: status === UserConsentVerificationResult.verified,
});
}
},
logger.info(
'[TOUCH_ID_PROMPT] Windows requestVerificationAsync',
isAppReady,
);
try {
const { success, error } = await requestVerificationAsync(msg);
event.reply(ipcMessageKeys.TOUCH_ID_PROMPT_RES, { success });
if (error) {
logger.info(
'[TOUCH_ID_PROMPT] Windows requestVerificationAsync error',
error,
);
}
} catch (e: any) {
logger.info(
'[TOUCH_ID_PROMPT] Windows requestVerificationAsync error',
e,
);
event.reply(ipcMessageKeys.TOUCH_ID_PROMPT_RES, {
success: false,
error: e.message,
});
}
return;
}

try {
await systemPreferences.promptTouchID(msg);
event.reply(ipcMessageKeys.TOUCH_ID_PROMPT_RES, { success: true });
Expand Down Expand Up @@ -914,6 +923,7 @@ if (!singleInstance && !process.mas) {
app.on('ready', async () => {
const locale = await initLocale();
logger.info('locale >>>> ', locale);
startServices();
if (!mainWindow) {
mainWindow = createMainWindow();
initMenu();
Expand Down
4 changes: 4 additions & 0 deletions apps/desktop/src-electron/service/enum.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export enum EWindowHelloEventType {
CheckAvailabilityAsync = 'checkAvailabilityAsync',
RequestVerificationAsync = 'requestVerificationAsync',
}
79 changes: 79 additions & 0 deletions apps/desktop/src-electron/service/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import path from 'path';

import { utilityProcess } from 'electron/main';
import Logger from 'electron-log/main';

import { EWindowHelloEventType } from './enum';

import type { UtilityProcess } from 'electron/main';

let windowsHelloChild: UtilityProcess | null = null;
let windowsHelloCallbacks: {
type: string;
callback: (e: any) => void;
timestamp: number;
}[] = [];
export const startServices = () => {
windowsHelloChild = utilityProcess.fork(
// After build, the directory is 'dist' and WindowsHello file is located in 'dist/service'
path.join(__dirname, './service/windowsHello.js'),
);
windowsHelloChild.on('message', (e: { type: string; result: boolean }) => {
Logger.info('parent process--onMessage', e);
const callbacks = windowsHelloCallbacks.filter(
(callbackItem) => callbackItem.type === e.type,
);
if (callbacks.length) {
callbacks.forEach((callbackItem) => {
// Callbacks older than 1 minute will not be executed
if (Date.now() - callbackItem.timestamp < 60 * 1000) {
callbackItem.callback(e.result);
}
});
windowsHelloCallbacks = windowsHelloCallbacks.filter(
(callbackItem) => !callbacks.includes(callbackItem),
);
}
});
};

let cacheWindowsHelloSupported: boolean | null = null;
export const checkAvailabilityAsync = async () => {
if (cacheWindowsHelloSupported === null) {
cacheWindowsHelloSupported = await Promise.race<boolean>([
new Promise<boolean>((resolve) => {
windowsHelloCallbacks.push({
type: EWindowHelloEventType.CheckAvailabilityAsync,
callback: resolve,
timestamp: Date.now(),
});
windowsHelloChild?.postMessage({
type: EWindowHelloEventType.CheckAvailabilityAsync,
});
}),
new Promise((resolve) =>
setTimeout(() => {
cacheWindowsHelloSupported = false;
resolve(cacheWindowsHelloSupported);
}, 500),
),
]);
}
return cacheWindowsHelloSupported;
};

export const requestVerificationAsync = (message: string) =>
new Promise<{
success: boolean;
error?: string;
}>((resolve) => {
windowsHelloCallbacks.push({
type: EWindowHelloEventType.RequestVerificationAsync,
callback: resolve,
timestamp: Date.now(),
});
windowsHelloChild?.postMessage({
type: EWindowHelloEventType.RequestVerificationAsync,
params: message,
});
});
70 changes: 70 additions & 0 deletions apps/desktop/src-electron/service/windowsHello.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import windowsSecurityCredentialsUiModule, {
UserConsentVerificationResult,
UserConsentVerifierAvailability,
} from 'electron-windows-security';

import { EWindowHelloEventType } from './enum';

function checkWindowsHelloAvailability(callback: (result: boolean) => void) {
try {
windowsSecurityCredentialsUiModule.UserConsentVerifier.checkAvailabilityAsync(
(error, status) => {
if (error) {
callback(false);
} else {
callback(status === UserConsentVerifierAvailability.available);
}
},
);
} catch (error) {
return false;
}
}

function requestVerificationAsync(
message: string,
callback: (params: { success: boolean; error?: string }) => void,
) {
windowsSecurityCredentialsUiModule.UserConsentVerifier.requestVerificationAsync(
message,
(error, status) => {
if (error) {
callback({
success: false,
error: error.message,
});
} else {
callback({
success: status === UserConsentVerificationResult.verified,
});
}
},
);
}

// Child process
process.parentPort.on(
'message',
(e: { data: { type: string; params: unknown } }) => {
switch (e.data.type) {
case 'checkAvailabilityAsync':
checkWindowsHelloAvailability((result) => {
process.parentPort.postMessage({
type: EWindowHelloEventType.CheckAvailabilityAsync,
result,
});
});
break;
case 'requestVerificationAsync':
requestVerificationAsync(e.data.params as string, (result) => {
process.parentPort.postMessage({
type: EWindowHelloEventType.RequestVerificationAsync,
result,
});
});
break;
default:
break;
}
},
);

0 comments on commit f3c6680

Please sign in to comment.