From f3c66801b0514c78e43bd09632d973f1475f7946 Mon Sep 17 00:00:00 2001 From: huhuanming Date: Fri, 1 Nov 2024 20:17:28 +0800 Subject: [PATCH] feat: restrict the Windows Hello-related libraries from running in child processes. (#6108) --- apps/desktop/scripts/build.js | 2 +- apps/desktop/src-electron/app.ts | 68 +++++++++------- apps/desktop/src-electron/service/enum.ts | 4 + apps/desktop/src-electron/service/index.ts | 79 +++++++++++++++++++ .../src-electron/service/windowsHello.ts | 70 ++++++++++++++++ 5 files changed, 193 insertions(+), 30 deletions(-) create mode 100644 apps/desktop/src-electron/service/enum.ts create mode 100644 apps/desktop/src-electron/service/index.ts create mode 100644 apps/desktop/src-electron/service/windowsHello.ts diff --git a/apps/desktop/scripts/build.js b/apps/desktop/scripts/build.js index 7df5f9fc672..9f9665a7993 100644 --- a/apps/desktop/scripts/build.js +++ b/apps/desktop/scripts/build.js @@ -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', diff --git a/apps/desktop/src-electron/app.ts b/apps/desktop/src-electron/app.ts index 14550c396a7..cd5969db87a 100644 --- a/apps/desktop/src-electron/app.ts +++ b/apps/desktop/src-electron/app.ts @@ -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, @@ -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; @@ -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?.(); @@ -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 }); @@ -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(); diff --git a/apps/desktop/src-electron/service/enum.ts b/apps/desktop/src-electron/service/enum.ts new file mode 100644 index 00000000000..f9cde16a3c7 --- /dev/null +++ b/apps/desktop/src-electron/service/enum.ts @@ -0,0 +1,4 @@ +export enum EWindowHelloEventType { + CheckAvailabilityAsync = 'checkAvailabilityAsync', + RequestVerificationAsync = 'requestVerificationAsync', +} diff --git a/apps/desktop/src-electron/service/index.ts b/apps/desktop/src-electron/service/index.ts new file mode 100644 index 00000000000..d5835762de5 --- /dev/null +++ b/apps/desktop/src-electron/service/index.ts @@ -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([ + new Promise((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, + }); + }); diff --git a/apps/desktop/src-electron/service/windowsHello.ts b/apps/desktop/src-electron/service/windowsHello.ts new file mode 100644 index 00000000000..4fc6513f1ef --- /dev/null +++ b/apps/desktop/src-electron/service/windowsHello.ts @@ -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; + } + }, +);