1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-16 00:03:56 +00:00

[BEEEP] [EC-141] Rust - Windows hello (#2635)

This commit is contained in:
Oscar Hinton
2022-07-25 13:24:13 +02:00
committed by GitHub
parent 05f6a30672
commit 0c1bd57cfd
25 changed files with 379 additions and 2026 deletions

View File

@@ -6,8 +6,6 @@ import { StateService } from "@bitwarden/common/abstractions/state.service";
import { BiometricMain } from "../biometric/biometric.main";
export default class BiometricDarwinMain implements BiometricMain {
isError = false;
constructor(private i18nservice: I18nService, private stateService: StateService) {}
async init() {

View File

@@ -1,5 +1,4 @@
export abstract class BiometricMain {
isError: boolean;
init: () => Promise<void>;
supportsBiometric: () => Promise<boolean>;
authenticateBiometric: () => Promise<boolean>;

View File

@@ -1,18 +1,14 @@
import { ipcMain } from "electron";
import forceFocus from "forcefocus";
import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
import { LogService } from "@bitwarden/common/abstractions/log.service";
import { StateService } from "@bitwarden/common/abstractions/state.service";
import { biometrics } from "@bitwarden/desktop-native";
import { WindowMain } from "@bitwarden/electron/window.main";
import { BiometricMain } from "src/main/biometric/biometric.main";
export default class BiometricWindowsMain implements BiometricMain {
isError = false;
private windowsSecurityCredentialsUiModule: any;
constructor(
private i18nservice: I18nService,
private windowMain: WindowMain,
@@ -21,126 +17,31 @@ export default class BiometricWindowsMain implements BiometricMain {
) {}
async init() {
this.windowsSecurityCredentialsUiModule = this.getWindowsSecurityCredentialsUiModule();
let supportsBiometric = false;
try {
supportsBiometric = await this.supportsBiometric();
} catch {
// store error state so we can let the user know on the settings page
this.isError = true;
} catch (e) {
this.logService.error(e);
}
await this.stateService.setEnableBiometric(supportsBiometric);
await this.stateService.setBiometricText("unlockWithWindowsHello");
await this.stateService.setNoAutoPromptBiometricsText("autoPromptWindowsHello");
ipcMain.on("biometric", async (event: any, message: any) => {
event.returnValue = await this.authenticateBiometric();
ipcMain.handle("biometric", async () => {
return await this.authenticateBiometric();
});
}
async supportsBiometric(): Promise<boolean> {
const availability = await this.checkAvailabilityAsync();
return this.getAllowedAvailabilities().includes(availability);
try {
return await biometrics.available();
} catch {
return false;
}
}
async authenticateBiometric(): Promise<boolean> {
const module = this.getWindowsSecurityCredentialsUiModule();
if (module == null) {
return false;
}
const verification = await this.requestVerificationAsync(
this.i18nservice.t("windowsHelloConsentMessage")
);
return verification === module.UserConsentVerificationResult.verified;
}
getWindowsSecurityCredentialsUiModule(): any {
try {
if (this.windowsSecurityCredentialsUiModule == null && this.getWindowsMajorVersion() >= 10) {
this.windowsSecurityCredentialsUiModule = require("@nodert-win10-rs4/windows.security.credentials.ui");
}
return this.windowsSecurityCredentialsUiModule;
} catch {
this.isError = true;
}
return null;
}
async checkAvailabilityAsync(): Promise<any> {
const module = this.getWindowsSecurityCredentialsUiModule();
if (module != null) {
// eslint-disable-next-line
return new Promise((resolve, reject) => {
try {
module.UserConsentVerifier.checkAvailabilityAsync((error: Error, result: any) => {
if (error) {
return resolve(null);
}
return resolve(result);
});
} catch {
this.isError = true;
return resolve(null);
}
});
}
return Promise.resolve(null);
}
async requestVerificationAsync(message: string): Promise<any> {
const module = this.getWindowsSecurityCredentialsUiModule();
if (module != null) {
return new Promise((resolve, reject) => {
try {
module.UserConsentVerifier.requestVerificationAsync(
message,
(error: Error, result: any) => {
if (error) {
return resolve(null);
}
return resolve(result);
}
);
forceFocus.focusWindow(this.windowMain.win);
} catch (error) {
this.isError = true;
return reject(error);
}
});
}
return Promise.resolve(null);
}
getAllowedAvailabilities(): any[] {
try {
const module = this.getWindowsSecurityCredentialsUiModule();
if (module != null) {
return [
module.UserConsentVerifierAvailability.available,
module.UserConsentVerifierAvailability.deviceBusy,
];
}
} catch {
/*Ignore error*/
}
return [];
}
getWindowsMajorVersion(): number {
if (process.platform !== "win32") {
return -1;
}
try {
// eslint-disable-next-line
const version = require("os").release();
return Number.parseInt(version.split(".")[0], 10);
} catch {
this.logService.error("Unable to resolve windows major version number");
}
return -1;
const hwnd = this.windowMain.win.getNativeWindowHandle();
return await biometrics.prompt(hwnd, this.i18nservice.t("windowsHelloConsentMessage"));
}
}

View File

@@ -1,5 +1,6 @@
import { ipcMain } from "electron";
import { deletePassword, getPassword, setPassword } from "keytar";
import { passwords } from "@bitwarden/desktop-native";
import { BiometricMain } from "./biometric/biometric.main";
@@ -10,7 +11,7 @@ export class DesktopCredentialStorageListener {
constructor(private serviceName: string, private biometricService: BiometricMain) {}
init() {
ipcMain.on("keytar", async (event: any, message: any) => {
ipcMain.handle("keytar", async (event: any, message: any) => {
try {
let serviceName = this.serviceName;
message.keySuffix = "_" + (message.keySuffix ?? "");
@@ -25,23 +26,35 @@ export class DesktopCredentialStorageListener {
let val: string | boolean = null;
if (authenticated && message.action && message.key) {
if (message.action === "getPassword") {
val = await getPassword(serviceName, message.key);
val = await this.getPassword(serviceName, message.key);
} else if (message.action === "hasPassword") {
const result = await getPassword(serviceName, message.key);
const result = await this.getPassword(serviceName, message.key);
val = result != null;
} else if (message.action === "setPassword" && message.value) {
await setPassword(serviceName, message.key, message.value);
await passwords.setPassword(serviceName, message.key, message.value);
} else if (message.action === "deletePassword") {
await deletePassword(serviceName, message.key);
await passwords.deletePassword(serviceName, message.key);
}
}
event.returnValue = val;
return val;
} catch {
event.returnValue = null;
return null;
}
});
}
// Gracefully handle old keytar values, and if detected updated the entry to the proper format
private async getPassword(serviceName: string, key: string) {
let val = await passwords.getPassword(serviceName, key);
try {
JSON.parse(val);
} catch (e) {
val = await passwords.getPasswordKeytar(serviceName, key);
await passwords.setPassword(serviceName, key, val);
}
return val;
}
private async authenticateBiometric(): Promise<boolean> {
if (this.biometricService) {
return await this.biometricService.authenticateBiometric();

File diff suppressed because it is too large Load Diff

View File

@@ -12,8 +12,6 @@
"url": "git+https://github.com/bitwarden/clients.git"
},
"dependencies": {
"@nodert-win10-rs4/windows.security.credentials.ui": "^0.4.4",
"forcefocus": "^1.1.0",
"keytar": "^7.9.0"
"@bitwarden/desktop-native": "file:../desktop_native"
}
}