From 2f0c1610d941454f480207af792b422ed3c688bb Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Fri, 29 Nov 2024 16:44:42 +0100 Subject: [PATCH] Implement rust fido2 for desktop mac and linux --- .../browser-platform-utils.service.ts | 11 + .../services/cli-platform-utils.service.ts | 11 + apps/desktop/desktop_native/Cargo.lock | 298 +++++++++++++++++- apps/desktop/desktop_native/core/Cargo.toml | 3 + .../core/src/fido2_client/mod.rs | 92 ++++++ apps/desktop/desktop_native/core/src/lib.rs | 4 +- apps/desktop/desktop_native/napi/index.d.ts | 3 + apps/desktop/desktop_native/napi/src/lib.rs | 10 + apps/desktop/src/main.ts | 5 + .../src/platform/main/webauthn-listener.ts | 18 ++ apps/desktop/src/platform/preload.ts | 11 + .../electron-platform-utils.service.ts | 16 +- .../app/core/web-platform-utils.service.ts | 11 + .../two-factor-auth-webauthn.component.ts | 16 + .../abstractions/platform-utils.service.ts | 6 + 15 files changed, 506 insertions(+), 9 deletions(-) create mode 100644 apps/desktop/desktop_native/core/src/fido2_client/mod.rs create mode 100644 apps/desktop/src/platform/main/webauthn-listener.ts diff --git a/apps/browser/src/platform/services/platform-utils/browser-platform-utils.service.ts b/apps/browser/src/platform/services/platform-utils/browser-platform-utils.service.ts index b47488bdd7d..3d9db084151 100644 --- a/apps/browser/src/platform/services/platform-utils/browser-platform-utils.service.ts +++ b/apps/browser/src/platform/services/platform-utils/browser-platform-utils.service.ts @@ -338,4 +338,15 @@ export abstract class BrowserPlatformUtilsService implements PlatformUtilsServic return ""; } + + supportsNativeWebauthn(): boolean { + return false; + } + performNativeWebauthnAuthentication( + challenge: string, + credentials: Array, + origin: string, + ): Promise { + throw new Error("Method not implemented."); + } } diff --git a/apps/cli/src/platform/services/cli-platform-utils.service.ts b/apps/cli/src/platform/services/cli-platform-utils.service.ts index 24bceec389c..d9f9ec87e98 100644 --- a/apps/cli/src/platform/services/cli-platform-utils.service.ts +++ b/apps/cli/src/platform/services/cli-platform-utils.service.ts @@ -138,4 +138,15 @@ export class CliPlatformUtilsService implements PlatformUtilsService { getAutofillKeyboardShortcut(): Promise { return null; } + + supportsNativeWebauthn(): boolean { + return false; + } + performNativeWebauthnAuthentication( + challenge: string, + credentials: Array, + origin: string, + ): Promise { + throw new Error("Method not implemented."); + } } diff --git a/apps/desktop/desktop_native/Cargo.lock b/apps/desktop/desktop_native/Cargo.lock index 82264617339..22513b7c50e 100644 --- a/apps/desktop/desktop_native/Cargo.lock +++ b/apps/desktop/desktop_native/Cargo.lock @@ -83,6 +83,45 @@ dependencies = [ "x11rb", ] +[[package]] +name = "asn1-rs" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5493c3bedbacf7fd7382c6346bbd66687d12bbaad3a89a2d2c303ee6cf20b048" +dependencies = [ + "asn1-rs-derive", + "asn1-rs-impl", + "displaydoc", + "nom", + "num-traits", + "rusticata-macros", + "thiserror", + "time", +] + +[[package]] +name = "asn1-rs-derive" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "965c2d33e53cb6b267e148a4cb0760bc01f4904c1cd4bb4002a085bb016d1490" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + +[[package]] +name = "asn1-rs-impl" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b18050c2cd6fe86c3a76584ef5e0baf286d038cda203eb6223df2cc413565f7" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "async-broadcast" version = "0.7.1" @@ -533,6 +572,29 @@ dependencies = [ "typenum", ] +[[package]] +name = "ctap-hid-fido2" +version = "3.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1452b85807c4da0e06a24df21886c643da501404d40a52da534ef08b8ec01779" +dependencies = [ + "aes", + "anyhow", + "base64", + "byteorder", + "cbc", + "hex", + "hidapi", + "num", + "pad", + "ring", + "serde", + "serde_cbor", + "strum", + "strum_macros", + "x509-parser", +] + [[package]] name = "ctor" version = "0.2.8" @@ -622,6 +684,12 @@ dependencies = [ "syn", ] +[[package]] +name = "data-encoding" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8566979429cf69b49a5c740c60791108e86440e8be149bbea4fe54d2c32d6e2" + [[package]] name = "der" version = "0.7.9" @@ -633,6 +701,20 @@ dependencies = [ "zeroize", ] +[[package]] +name = "der-parser" +version = "9.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5cd0a5c643689626bec213c4d8bd4d96acc8ffdb4ad4bb6bc16abf27d5f4b553" +dependencies = [ + "asn1-rs", + "displaydoc", + "nom", + "num-bigint", + "num-traits", + "rusticata-macros", +] + [[package]] name = "deranged" version = "0.3.11" @@ -666,6 +748,7 @@ dependencies = [ "byteorder", "cbc", "core-foundation", + "ctap-hid-fido2", "dirs", "ed25519", "futures", @@ -686,6 +769,8 @@ dependencies = [ "scopeguard", "security-framework", "security-framework-sys", + "serde", + "serde_json", "sha2", "ssh-encoding", "ssh-key", @@ -764,6 +849,17 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "displaydoc" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "dlib" version = "0.5.2" @@ -1143,6 +1239,12 @@ dependencies = [ "system-deps", ] +[[package]] +name = "half" +version = "1.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b43ede17f21864e81be2fa654110bf1e793774238d86ef8555c37e6519c0403" + [[package]] name = "hashbrown" version = "0.15.1" @@ -1173,6 +1275,19 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" +[[package]] +name = "hidapi" +version = "2.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03b876ecf37e86b359573c16c8366bc3eba52b689884a0fc42ba3f67203d2a8b" +dependencies = [ + "cc", + "cfg-if", + "libc", + "pkg-config", + "windows-sys 0.48.0", +] + [[package]] name = "hmac" version = "0.12.1" @@ -1489,6 +1604,30 @@ dependencies = [ "minimal-lexical", ] +[[package]] +name = "num" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35bd024e8b2ff75562e5f34e7f4905839deb4b22955ef5e73d2fea1b9813cb23" +dependencies = [ + "num-bigint", + "num-complex", + "num-integer", + "num-iter", + "num-rational", + "num-traits", +] + +[[package]] +name = "num-bigint" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" +dependencies = [ + "num-integer", + "num-traits", +] + [[package]] name = "num-bigint-dig" version = "0.8.4" @@ -1506,6 +1645,15 @@ dependencies = [ "zeroize", ] +[[package]] +name = "num-complex" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495" +dependencies = [ + "num-traits", +] + [[package]] name = "num-conv" version = "0.1.0" @@ -1532,6 +1680,17 @@ dependencies = [ "num-traits", ] +[[package]] +name = "num-rational" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824" +dependencies = [ + "num-bigint", + "num-integer", + "num-traits", +] + [[package]] name = "num-traits" version = "0.2.19" @@ -1659,6 +1818,15 @@ dependencies = [ "memchr", ] +[[package]] +name = "oid-registry" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8d8034d9489cdaf79228eb9f6a3b8d7bb32ba00d6645ebd48eef4077ceb5bd9" +dependencies = [ + "asn1-rs", +] + [[package]] name = "once_cell" version = "1.20.2" @@ -1697,6 +1865,15 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "pad" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2ad9b889f1b12e0b9ee24db044b5129150d5eada288edc800f789928dc8c0e3" +dependencies = [ + "unicode-width", +] + [[package]] name = "parking" version = "2.2.1" @@ -2025,6 +2202,21 @@ dependencies = [ "rand", ] +[[package]] +name = "ring" +version = "0.17.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d" +dependencies = [ + "cc", + "cfg-if", + "getrandom", + "libc", + "spin", + "untrusted", + "windows-sys 0.52.0", +] + [[package]] name = "rsa" version = "0.9.6" @@ -2071,6 +2263,15 @@ dependencies = [ "semver", ] +[[package]] +name = "rusticata-macros" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "faf0c4a6ece9950b9abdb62b1cfcf2a68b3b67a10ba445b3bb85be2a293d0632" +dependencies = [ + "nom", +] + [[package]] name = "rustix" version = "0.38.37" @@ -2084,6 +2285,18 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "rustversion" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e819f2bc632f285be6d7cd36e25940d45b2391dd6d9b939e79de557f7014248" + +[[package]] +name = "ryu" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" + [[package]] name = "salsa20" version = "0.10.2" @@ -2153,24 +2366,46 @@ checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" [[package]] name = "serde" -version = "1.0.214" +version = "1.0.215" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f55c3193aca71c12ad7890f1785d2b73e1b9f63a0bbc353c08ef26fe03fc56b5" +checksum = "6513c1ad0b11a9376da888e3e0baa0077f1aed55c17f50e7b2397136129fb88f" dependencies = [ "serde_derive", ] [[package]] -name = "serde_derive" -version = "1.0.214" +name = "serde_cbor" +version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de523f781f095e28fa605cdce0f8307e451cc0fd14e2eb4cd2e98a355b147766" +checksum = "2bef2ebfde456fb76bbcf9f59315333decc4fda0b2b44b420243c11e0f5ec1f5" +dependencies = [ + "half", + "serde", +] + +[[package]] +name = "serde_derive" +version = "1.0.215" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad1e866f866923f252f05c889987993144fb74e722403468a4ebd70c3cd756c0" dependencies = [ "proc-macro2", "quote", "syn", ] +[[package]] +name = "serde_json" +version = "1.0.133" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7fceb2473b9166b2294ef05efcb65a3db80803f0b03ef86a5fc88a2b85ee377" +dependencies = [ + "itoa", + "memchr", + "ryu", + "serde", +] + [[package]] name = "serde_repr" version = "0.1.19" @@ -2343,6 +2578,25 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" +[[package]] +name = "strum" +version = "0.26.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fec0f0aef304996cf250b31b5a10dee7980c85da9d759361292b8bca5a18f06" + +[[package]] +name = "strum_macros" +version = "0.26.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c6bee85a5a24955dc440386795aa378cd9cf82acd5f764469152d2270e581be" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "rustversion", + "syn", +] + [[package]] name = "subtle" version = "2.6.1" @@ -2360,6 +2614,17 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "synstructure" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "system-deps" version = "6.2.2" @@ -2628,6 +2893,12 @@ dependencies = [ "subtle", ] +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + [[package]] name = "version-compare" version = "0.2.0" @@ -3087,6 +3358,23 @@ version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec107c4503ea0b4a98ef47356329af139c0a4f7750e621cf2973cd3385ebcb3d" +[[package]] +name = "x509-parser" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcbc162f30700d6f3f82a24bf7cc62ffe7caea42c0b2cba8bf7f3ae50cf51f69" +dependencies = [ + "asn1-rs", + "data-encoding", + "der-parser", + "lazy_static", + "nom", + "oid-registry", + "rusticata-macros", + "thiserror", + "time", +] + [[package]] name = "xdg-home" version = "1.3.0" diff --git a/apps/desktop/desktop_native/core/Cargo.toml b/apps/desktop/desktop_native/core/Cargo.toml index 6f333c480e6..14a4fb152f7 100644 --- a/apps/desktop/desktop_native/core/Cargo.toml +++ b/apps/desktop/desktop_native/core/Cargo.toml @@ -60,6 +60,9 @@ rand_chacha = "=0.3.1" pkcs8 = { version = "=0.10.2", features = ["alloc", "encryption", "pem"] } rsa = "=0.9.6" ed25519 = { version = "=2.2.3", features = ["pkcs8"] } +ctap-hid-fido2 = "3.5.2" +serde = { version = "1.0.215", features = ["derive", "serde_derive"] } +serde_json = "1.0.133" [target.'cfg(windows)'.dependencies] widestring = { version = "=1.1.0", optional = true } diff --git a/apps/desktop/desktop_native/core/src/fido2_client/mod.rs b/apps/desktop/desktop_native/core/src/fido2_client/mod.rs new file mode 100644 index 00000000000..cc6a14595b3 --- /dev/null +++ b/apps/desktop/desktop_native/core/src/fido2_client/mod.rs @@ -0,0 +1,92 @@ +use base64::{prelude::BASE64_URL_SAFE_NO_PAD, Engine}; +use ctap_hid_fido2::{fidokey::GetAssertionArgsBuilder, Cfg, FidoKeyHidFactory}; +use serde::{Deserialize, Serialize}; + +#[derive(Debug)] +pub enum Fido2ClientError { + WrongPin, + NoCredentials, + NoDevice, + InvalidInput, + AssertionError, +} + +pub fn authenticate(challenge: String, credentials: Vec, rpid: String, pin: Option) -> Result { + let device = FidoKeyHidFactory::create(&Cfg::init()).map_err(|_| Fido2ClientError::NoDevice)?; + let clientdata = format!(r#"{{"type":"webauthn.get","challenge":"{}","origin":"https://{}","crossOrigin": true}}"#, challenge, rpid); + + let mut get_assertion_args = GetAssertionArgsBuilder::new(rpid.as_str(), clientdata.as_bytes()); + + let result = if let Some(pin) = pin { + get_assertion_args = get_assertion_args.pin(&pin.as_str()); + let get_assertion_args = get_assertion_args.build(); + device.get_assertion_with_args(&get_assertion_args) + } else { + let mut get_assertion_args = get_assertion_args + .without_pin_and_uv(); + for cred in credentials { + let credid_bytes = BASE64_URL_SAFE_NO_PAD.decode(cred.as_bytes()).map_err(|_| Fido2ClientError::InvalidInput)?; + get_assertion_args = get_assertion_args.credential_id(&credid_bytes); + } + let get_assertion_args = get_assertion_args.build(); + device.get_assertion_with_args(&get_assertion_args) + }; + + let assertion = match result { + Ok(assertion) => assertion, + Err(_) => { + return Err(Fido2ClientError::NoCredentials); + } + }; + + let assertion = assertion.get(0).ok_or(Fido2ClientError::AssertionError)?; + + let twofa_token = TwoFactorAuthToken{ + id: BASE64_URL_SAFE_NO_PAD.encode(assertion.credential_id.as_slice()), + raw_id: BASE64_URL_SAFE_NO_PAD.encode(assertion.credential_id.as_slice()), + type_: "public-key".to_string(), + response: WebauthnResponseData { + authenticator_data: BASE64_URL_SAFE_NO_PAD.encode( + &assertion.auth_data.as_slice() + ), + client_data_json: BASE64_URL_SAFE_NO_PAD.encode( + &clientdata.as_bytes() + ), + signature: BASE64_URL_SAFE_NO_PAD.encode( + &assertion.signature.as_slice() + ), + }, + extensions: WebauthnExtensions { + appid: Some(false), + }, + }; + let twofa_token = serde_json::to_string(&twofa_token).map_err(|_| Fido2ClientError::AssertionError)?; + + Ok(twofa_token) +} + +#[derive(Debug, Serialize, Deserialize)] +struct TwoFactorAuthToken { + id: String, + #[serde(rename = "rawId")] + raw_id: String, + #[serde(rename = "type")] + type_: String, + extensions: WebauthnExtensions, + #[serde(rename = "response")] + response: WebauthnResponseData, +} + +#[derive(Debug, Serialize, Deserialize)] +struct WebauthnExtensions { + appid: Option, +} + +#[derive(Debug, Serialize, Deserialize)] +struct WebauthnResponseData { + #[serde(rename = "authenticatorData")] + authenticator_data: String, + #[serde(rename = "clientDataJson")] + client_data_json: String, + signature: String, +} diff --git a/apps/desktop/desktop_native/core/src/lib.rs b/apps/desktop/desktop_native/core/src/lib.rs index f38e6ef97b4..0575131ba0d 100644 --- a/apps/desktop/desktop_native/core/src/lib.rs +++ b/apps/desktop/desktop_native/core/src/lib.rs @@ -12,5 +12,7 @@ pub mod process_isolation; #[cfg(feature = "sys")] pub mod powermonitor; #[cfg(feature = "sys")] - pub mod ssh_agent; + +#[cfg(feature = "sys")] +pub mod fido2_client; diff --git a/apps/desktop/desktop_native/napi/index.d.ts b/apps/desktop/desktop_native/napi/index.d.ts index 62e448d4800..904db5c4311 100644 --- a/apps/desktop/desktop_native/napi/index.d.ts +++ b/apps/desktop/desktop_native/napi/index.d.ts @@ -123,3 +123,6 @@ export declare namespace ipc { send(message: string): number } } +export declare namespace fido2_hid_client { + export function authenticate(challenge: string, credentials: Array, rpid: string, pin: string): string +} diff --git a/apps/desktop/desktop_native/napi/src/lib.rs b/apps/desktop/desktop_native/napi/src/lib.rs index 8e156eb3efa..86dba610249 100644 --- a/apps/desktop/desktop_native/napi/src/lib.rs +++ b/apps/desktop/desktop_native/napi/src/lib.rs @@ -522,3 +522,13 @@ pub mod ipc { } } } + +#[napi] +pub mod fido2_hid_client { + #[napi] + pub fn authenticate(challenge: String, credentials: Vec, rpid: String, pin: String) -> napi::Result { + let pin = if pin.is_empty() { None } else { Some(pin) }; + desktop_core::fido2_client::authenticate(challenge, credentials, rpid, pin) + .map_err(|e| napi::Error::from_reason(format!("Error authenticating: {:?}", e))) + } +} \ No newline at end of file diff --git a/apps/desktop/src/main.ts b/apps/desktop/src/main.ts index bbb5c23513e..8bd8cd60ea4 100644 --- a/apps/desktop/src/main.ts +++ b/apps/desktop/src/main.ts @@ -40,6 +40,7 @@ import { DesktopCredentialStorageListener } from "./platform/main/desktop-creden import { MainCryptoFunctionService } from "./platform/main/main-crypto-function.service"; import { MainSshAgentService } from "./platform/main/main-ssh-agent.service"; import { VersionMain } from "./platform/main/version.main"; +import { WebauthnListener } from "./platform/main/webauthn-listener"; import { DesktopSettingsService } from "./platform/services/desktop-settings.service"; import { ElectronLogMainService } from "./platform/services/electron-log.main.service"; import { ElectronStorageService } from "./platform/services/electron-storage.service"; @@ -75,6 +76,7 @@ export class Main { desktopAutofillSettingsService: DesktopAutofillSettingsService; versionMain: VersionMain; sshAgentService: MainSshAgentService; + webauthnListener: WebauthnListener; constructor() { // Set paths for portable builds @@ -254,6 +256,9 @@ export class Main { } }); + this.webauthnListener = new WebauthnListener(); + this.webauthnListener.init(); + new EphemeralValueStorageService(); new SSOLocalhostCallbackService(this.environmentService, this.messagingService); } diff --git a/apps/desktop/src/platform/main/webauthn-listener.ts b/apps/desktop/src/platform/main/webauthn-listener.ts new file mode 100644 index 00000000000..97d11330eca --- /dev/null +++ b/apps/desktop/src/platform/main/webauthn-listener.ts @@ -0,0 +1,18 @@ +import { ipcMain } from "electron"; + +import { fido2_hid_client } from "@bitwarden/desktop-napi"; + +export class WebauthnListener { + constructor() {} + + init() { + ipcMain.handle("webauthn.authenticate", async (event: any, message: any) => { + return fido2_hid_client.authenticate( + message.challenge, + message.credentials, + message.origin, + "", + ); + }); + } +} diff --git a/apps/desktop/src/platform/preload.ts b/apps/desktop/src/platform/preload.ts index 171e83bbef0..d36abd06106 100644 --- a/apps/desktop/src/platform/preload.ts +++ b/apps/desktop/src/platform/preload.ts @@ -119,6 +119,16 @@ const localhostCallbackService = { }, }; +const webauthn = { + webauthnAuthenticate: ( + challenge: string, + credentials: Array, + origin: string, + ): Promise => { + return ipcRenderer.invoke("webauthn.authenticate", { challenge, credentials, origin }); + }, +}; + export default { versions: { app: (): Promise => ipcRenderer.invoke("appVersion"), @@ -190,6 +200,7 @@ export default { crypto, ephemeralStore, localhostCallbackService, + webauthn, }; function deviceType(): DeviceType { diff --git a/apps/desktop/src/platform/services/electron-platform-utils.service.ts b/apps/desktop/src/platform/services/electron-platform-utils.service.ts index 2808b74f097..bea077da4b3 100644 --- a/apps/desktop/src/platform/services/electron-platform-utils.service.ts +++ b/apps/desktop/src/platform/services/electron-platform-utils.service.ts @@ -75,10 +75,20 @@ export class ElectronPlatformUtilsService implements PlatformUtilsService { return (await this.getApplicationVersion()).split(/[+|-]/)[0].trim(); } - // Temporarily restricted to only Windows until https://github.com/electron/electron/pull/28349 - // has been merged and an updated electron build is available. supportsWebAuthn(win: Window): boolean { - return this.getDevice() === DeviceType.WindowsDesktop; + return true; + } + + supportsNativeWebauthn(): boolean { + return true; + } + + performNativeWebauthnAuthentication( + challenge: string, + credentials: Array, + origin: string, + ): Promise { + return ipc.platform.webauthn.webauthnAuthenticate(challenge, credentials, origin); } supportsDuo(): boolean { diff --git a/apps/web/src/app/core/web-platform-utils.service.ts b/apps/web/src/app/core/web-platform-utils.service.ts index dbd0ef593d6..27783cefb6d 100644 --- a/apps/web/src/app/core/web-platform-utils.service.ts +++ b/apps/web/src/app/core/web-platform-utils.service.ts @@ -193,4 +193,15 @@ export class WebPlatformUtilsService implements PlatformUtilsService { getAutofillKeyboardShortcut(): Promise { return null; } + + supportsNativeWebauthn(): boolean { + return false; + } + performNativeWebauthnAuthentication( + challenge: string, + credentials: Array, + origin: string, + ): Promise { + throw new Error("Method not implemented."); + } } diff --git a/libs/angular/src/auth/components/two-factor-auth/two-factor-auth-webauthn.component.ts b/libs/angular/src/auth/components/two-factor-auth/two-factor-auth-webauthn.component.ts index 7e9f6486911..ef0bbe9bd2e 100644 --- a/libs/angular/src/auth/components/two-factor-auth/two-factor-auth-webauthn.component.ts +++ b/libs/angular/src/auth/components/two-factor-auth/two-factor-auth-webauthn.component.ts @@ -121,6 +121,22 @@ export class TwoFactorAuthWebAuthnComponent implements OnInit, OnDestroy { return; } + if (this.platformUtilsService.supportsNativeWebauthn()) { + const challenge = providerData.challenge; + const rpId = providerData.rpId; + const creds = providerData.allowCredentials as any as Array; + const credentials = creds.map((c: any) => { + return c.id; + }); + const resp = await this.platformUtilsService.performNativeWebauthnAuthentication( + challenge, + credentials, + rpId, + ); + this.token.emit(resp); + return; + } + this.webAuthn.init(providerData); } diff --git a/libs/common/src/platform/abstractions/platform-utils.service.ts b/libs/common/src/platform/abstractions/platform-utils.service.ts index fa0fc8f2501..c243d674fe5 100644 --- a/libs/common/src/platform/abstractions/platform-utils.service.ts +++ b/libs/common/src/platform/abstractions/platform-utils.service.ts @@ -27,6 +27,12 @@ export abstract class PlatformUtilsService { abstract getApplicationVersion(): Promise; abstract getApplicationVersionNumber(): Promise; abstract supportsWebAuthn(win: Window): boolean; + abstract supportsNativeWebauthn(): boolean; + abstract performNativeWebauthnAuthentication( + challenge: string, + credentials: Array, + origin: string, + ): Promise; abstract supportsDuo(): boolean; /** * @deprecated use `@bitwarden/components/ToastService.showToast` instead