mirror of
https://github.com/bitwarden/browser
synced 2026-02-06 11:43:51 +00:00
Support PRF login on desktop for security keys
This commit is contained in:
@@ -51,8 +51,10 @@ import { MasterPasswordApiService } from "@bitwarden/common/auth/abstractions/ma
|
||||
import { SsoLoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/sso-login.service.abstraction";
|
||||
import { TokenService } from "@bitwarden/common/auth/abstractions/token.service";
|
||||
import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction";
|
||||
import { NavigatorCredentialsService } from "@bitwarden/common/auth/abstractions/webauthn/navigator-credentials.service";
|
||||
import { AuthRequestAnsweringService } from "@bitwarden/common/auth/services/auth-request-answering/auth-request-answering.service";
|
||||
import { PendingAuthRequestsStateService } from "@bitwarden/common/auth/services/auth-request-answering/pending-auth-requests.state";
|
||||
import { DefaultNavigatorCredentialsService } from "@bitwarden/common/auth/services/webauthn-login/default-navigator-credentials.service";
|
||||
import {
|
||||
AutofillSettingsService,
|
||||
AutofillSettingsServiceAbstraction,
|
||||
@@ -717,6 +719,11 @@ const safeProviders: SafeProvider[] = [
|
||||
useClass: ExtensionNewDeviceVerificationComponentService,
|
||||
deps: [],
|
||||
}),
|
||||
safeProvider({
|
||||
provide: NavigatorCredentialsService,
|
||||
useClass: DefaultNavigatorCredentialsService,
|
||||
deps: [WINDOW, PlatformUtilsService],
|
||||
}),
|
||||
safeProvider({
|
||||
provide: SessionTimeoutSettingsComponentService,
|
||||
useClass: BrowserSessionTimeoutSettingsComponentService,
|
||||
|
||||
356
apps/desktop/desktop_native/Cargo.lock
generated
356
apps/desktop/desktop_native/Cargo.lock
generated
@@ -193,6 +193,45 @@ dependencies = [
|
||||
"nom",
|
||||
]
|
||||
|
||||
[[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 1.0.69",
|
||||
"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.2"
|
||||
@@ -608,7 +647,7 @@ dependencies = [
|
||||
"async-trait",
|
||||
"base64",
|
||||
"cbc",
|
||||
"dirs",
|
||||
"dirs 6.0.0",
|
||||
"hex",
|
||||
"oo7",
|
||||
"pbkdf2",
|
||||
@@ -692,7 +731,7 @@ checksum = "fe6d2e5af09e8c8ad56c969f2157a3d4238cebc7c55f0a517728c38f7b200f81"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"termcolor",
|
||||
"unicode-width",
|
||||
"unicode-width 0.2.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -779,6 +818,29 @@ dependencies = [
|
||||
"typenum",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ctap-hid-fido2"
|
||||
version = "3.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ee6473a333d82796d5b23529fde19386de33820ad19d368402f8e9de780e1723"
|
||||
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.9"
|
||||
@@ -900,6 +962,12 @@ dependencies = [
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "data-encoding"
|
||||
version = "2.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2a2330da5de22e8a3cb63252ce2abb30116bf5265e89c0e01bc17015ce30a476"
|
||||
|
||||
[[package]]
|
||||
name = "der"
|
||||
version = "0.7.10"
|
||||
@@ -911,6 +979,29 @@ 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.5.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ececcb659e7ba858fb4f10388c250a7252eb0a27373f1a72b8748afdd248e587"
|
||||
dependencies = [
|
||||
"powerfmt",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "desktop_core"
|
||||
version = "0.0.0"
|
||||
@@ -927,7 +1018,7 @@ dependencies = [
|
||||
"chacha20poly1305",
|
||||
"core-foundation",
|
||||
"desktop_objc",
|
||||
"dirs",
|
||||
"dirs 6.0.0",
|
||||
"ed25519",
|
||||
"futures",
|
||||
"homedir",
|
||||
@@ -975,6 +1066,7 @@ dependencies = [
|
||||
"base64",
|
||||
"chromium_importer",
|
||||
"desktop_core",
|
||||
"fido2_client",
|
||||
"hex",
|
||||
"napi",
|
||||
"napi-build",
|
||||
@@ -1029,13 +1121,34 @@ dependencies = [
|
||||
"subtle",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dirs"
|
||||
version = "5.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "44c45a9d03d6676652bcb5e724c7e988de1acad23a711b5217ab9cbecbec2225"
|
||||
dependencies = [
|
||||
"dirs-sys 0.4.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dirs"
|
||||
version = "6.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c3e8aa94d75141228480295a7d0e7feb620b1a5ad9f12bc40be62411e38cce4e"
|
||||
dependencies = [
|
||||
"dirs-sys",
|
||||
"dirs-sys 0.5.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dirs-sys"
|
||||
version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "520f05a5cbd335fae5a99ff7a6ab8627577660ee5cfd6a94a6a929b52ff0321c"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"option-ext",
|
||||
"redox_users 0.4.6",
|
||||
"windows-sys 0.48.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1046,7 +1159,7 @@ checksum = "e01a3366d27ee9890022452ee61b2b63a67e6f13f58900b651ff5665f0bb1fab"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"option-ext",
|
||||
"redox_users",
|
||||
"redox_users 0.5.0",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
@@ -1143,6 +1256,12 @@ dependencies = [
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "either"
|
||||
version = "1.15.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719"
|
||||
|
||||
[[package]]
|
||||
name = "elliptic-curve"
|
||||
version = "0.13.8"
|
||||
@@ -1288,6 +1407,18 @@ version = "0.2.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d"
|
||||
|
||||
[[package]]
|
||||
name = "fido2_client"
|
||||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"base64",
|
||||
"ctap-hid-fido2",
|
||||
"pinentry",
|
||||
"secrecy",
|
||||
"serde",
|
||||
"sha2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fixedbitset"
|
||||
version = "0.4.2"
|
||||
@@ -1520,6 +1651,12 @@ dependencies = [
|
||||
"subtle",
|
||||
]
|
||||
|
||||
[[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.3"
|
||||
@@ -1556,6 +1693,18 @@ version = "0.4.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
|
||||
|
||||
[[package]]
|
||||
name = "hidapi"
|
||||
version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "798154e4b6570af74899d71155fb0072d5b17e6aa12f39c8ef22c60fb8ec99e7"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"libc",
|
||||
"pkg-config",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hkdf"
|
||||
version = "0.12.4"
|
||||
@@ -2157,6 +2306,12 @@ dependencies = [
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-conv"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9"
|
||||
|
||||
[[package]]
|
||||
name = "num-integer"
|
||||
version = "0.1.46"
|
||||
@@ -2290,6 +2445,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.21.3"
|
||||
@@ -2397,6 +2561,15 @@ dependencies = [
|
||||
"sha2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pad"
|
||||
version = "0.1.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d2ad9b889f1b12e0b9ee24db044b5129150d5eada288edc800f789928dc8c0e3"
|
||||
dependencies = [
|
||||
"unicode-width 0.1.14",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "parking"
|
||||
version = "2.2.1"
|
||||
@@ -2499,6 +2672,20 @@ version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
|
||||
|
||||
[[package]]
|
||||
name = "pinentry"
|
||||
version = "0.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "72268b7db3a2075ea65d4b93b755d086e99196e327837e690db6559b393a8d69"
|
||||
dependencies = [
|
||||
"log",
|
||||
"nom",
|
||||
"percent-encoding",
|
||||
"secrecy",
|
||||
"which",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "piper"
|
||||
version = "0.2.4"
|
||||
@@ -2607,6 +2794,12 @@ dependencies = [
|
||||
"zerovec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "powerfmt"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391"
|
||||
|
||||
[[package]]
|
||||
name = "ppv-lite86"
|
||||
version = "0.2.21"
|
||||
@@ -2777,6 +2970,17 @@ dependencies = [
|
||||
"bitflags",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "redox_users"
|
||||
version = "0.4.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43"
|
||||
dependencies = [
|
||||
"getrandom 0.2.16",
|
||||
"libredox",
|
||||
"thiserror 1.0.69",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "redox_users"
|
||||
version = "0.5.0"
|
||||
@@ -2827,6 +3031,20 @@ dependencies = [
|
||||
"subtle",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ring"
|
||||
version = "0.17.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e75ec5e92c4d8aede845126adc388046234541629e76029599ed35a003c7ed24"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"cfg-if",
|
||||
"getrandom 0.2.16",
|
||||
"libc",
|
||||
"untrusted",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rsa"
|
||||
version = "0.9.6"
|
||||
@@ -2887,6 +3105,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.44"
|
||||
@@ -3031,6 +3258,15 @@ dependencies = [
|
||||
"windows 0.61.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "secrecy"
|
||||
version = "0.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9bd1c54ea06cfd2f6b63219704de0b9b4f72dcc2b8fdef820be6cd799780e91e"
|
||||
dependencies = [
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "security-framework"
|
||||
version = "3.5.0"
|
||||
@@ -3072,6 +3308,16 @@ dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_cbor"
|
||||
version = "0.11.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2bef2ebfde456fb76bbcf9f59315333decc4fda0b2b44b420243c11e0f5ec1f5"
|
||||
dependencies = [
|
||||
"half",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.209"
|
||||
@@ -3315,6 +3561,25 @@ version = "0.11.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
|
||||
|
||||
[[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"
|
||||
@@ -3443,6 +3708,37 @@ dependencies = [
|
||||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "time"
|
||||
version = "0.3.44"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "91e7d9e3bb61134e77bde20dd4825b97c010155709965fedf0f49bb138e52a9d"
|
||||
dependencies = [
|
||||
"deranged",
|
||||
"itoa",
|
||||
"num-conv",
|
||||
"powerfmt",
|
||||
"serde",
|
||||
"time-core",
|
||||
"time-macros",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "time-core"
|
||||
version = "0.1.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "40868e7c1d2f0b8d73e4a8c7f0ff63af4f6d19be117e90bd73eb1d62cf831c6b"
|
||||
|
||||
[[package]]
|
||||
name = "time-macros"
|
||||
version = "0.2.24"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "30cfb0125f12d9c277f35663a0a33f8c30190f4e4574868a330595412d34ebf3"
|
||||
dependencies = [
|
||||
"num-conv",
|
||||
"time-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tinystr"
|
||||
version = "0.8.1"
|
||||
@@ -3693,6 +3989,12 @@ version = "1.12.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-width"
|
||||
version = "0.1.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-width"
|
||||
version = "0.2.0"
|
||||
@@ -3839,6 +4141,12 @@ dependencies = [
|
||||
"subtle",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "untrusted"
|
||||
version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1"
|
||||
|
||||
[[package]]
|
||||
name = "url"
|
||||
version = "2.5.4"
|
||||
@@ -4007,6 +4315,18 @@ dependencies = [
|
||||
"nom",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "which"
|
||||
version = "4.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8ad25fe5717e59ada8ea33511bbbf7420b11031730a24c65e82428766c307006"
|
||||
dependencies = [
|
||||
"dirs 5.0.1",
|
||||
"either",
|
||||
"once_cell",
|
||||
"rustix 0.38.44",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "widestring"
|
||||
version = "1.2.0"
|
||||
@@ -4242,6 +4562,15 @@ dependencies = [
|
||||
"windows-targets 0.42.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.48.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
|
||||
dependencies = [
|
||||
"windows-targets 0.48.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.52.0"
|
||||
@@ -4600,6 +4929,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 1.0.69",
|
||||
"time",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "yoke"
|
||||
version = "0.8.0"
|
||||
|
||||
@@ -5,6 +5,7 @@ members = [
|
||||
"bitwarden_chromium_import_helper",
|
||||
"chromium_importer",
|
||||
"core",
|
||||
"fido2_client",
|
||||
"macos_provider",
|
||||
"napi",
|
||||
"process_isolation",
|
||||
|
||||
14
apps/desktop/desktop_native/fido2_client/Cargo.toml
Normal file
14
apps/desktop/desktop_native/fido2_client/Cargo.toml
Normal file
@@ -0,0 +1,14 @@
|
||||
[package]
|
||||
name = "fido2_client"
|
||||
edition = { workspace = true }
|
||||
license = { workspace = true }
|
||||
version = { workspace = true }
|
||||
publish = { workspace = true }
|
||||
|
||||
[dependencies]
|
||||
base64 = { workspace = true }
|
||||
ctap-hid-fido2 = "3.5.1"
|
||||
pinentry = "0.5.0"
|
||||
serde = { workspace = true, features = ["derive"] }
|
||||
secrecy = "0.8.0"
|
||||
sha2 = { workspace = true }
|
||||
145
apps/desktop/desktop_native/fido2_client/src/ctap_hid_fido2.rs
Normal file
145
apps/desktop/desktop_native/fido2_client/src/ctap_hid_fido2.rs
Normal file
@@ -0,0 +1,145 @@
|
||||
use base64::{prelude::BASE64_URL_SAFE_NO_PAD, Engine};
|
||||
use ctap_hid_fido2::{
|
||||
fidokey::{AssertionExtension, GetAssertionArgsBuilder},
|
||||
Cfg, FidoKeyHidFactory,
|
||||
};
|
||||
use pinentry::PassphraseInput;
|
||||
use secrecy::ExposeSecret;
|
||||
|
||||
use crate::{
|
||||
prf_to_hmac, AuthenticatorAssertionResponse, Fido2ClientError, PublicKeyCredential,
|
||||
PublicKeyCredentialRequestOptions,
|
||||
};
|
||||
|
||||
fn get_pin() -> Option<String> {
|
||||
if let Some(mut input) = PassphraseInput::with_default_binary() {
|
||||
input
|
||||
.with_description("Enter your FIDO2 Authenticator PIN:")
|
||||
.with_prompt("PIN:")
|
||||
.interact()
|
||||
.ok()
|
||||
.map(|p| p.expose_secret().to_owned())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn available() -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn make_assertion(
|
||||
options: PublicKeyCredentialRequestOptions,
|
||||
client_data_json: String,
|
||||
credential: Option<&[u8]>,
|
||||
) -> Result<GetAssertionArgsBuilder, Fido2ClientError> {
|
||||
let mut get_assertion_args =
|
||||
GetAssertionArgsBuilder::new(options.rp_id.as_str(), client_data_json.as_bytes())
|
||||
.extensions(&[AssertionExtension::HmacSecret(Some(prf_to_hmac(
|
||||
&options.prf_eval_first,
|
||||
)))]);
|
||||
|
||||
if let Some(cred) = credential {
|
||||
get_assertion_args = get_assertion_args.credential_id(cred);
|
||||
}
|
||||
Ok(get_assertion_args)
|
||||
}
|
||||
|
||||
pub fn get(
|
||||
options: PublicKeyCredentialRequestOptions,
|
||||
) -> Result<PublicKeyCredential, Fido2ClientError> {
|
||||
let device = FidoKeyHidFactory::create(&Cfg::init()).map_err(|_| Fido2ClientError::NoDevice)?;
|
||||
|
||||
let client_data_json = format!(
|
||||
r#"{{"type":"webauthn.get","challenge":"{}","origin":"https://{}","crossOrigin": true}}"#,
|
||||
BASE64_URL_SAFE_NO_PAD.encode(&options.challenge),
|
||||
options.rp_id
|
||||
);
|
||||
|
||||
let mut get_assertion_args = make_assertion(
|
||||
options.clone(),
|
||||
client_data_json.clone(),
|
||||
options.allow_credentials.get(0).map(|v| v.as_slice()),
|
||||
)?;
|
||||
|
||||
let pin: String;
|
||||
if options.user_verification == crate::UserVerification::Required
|
||||
|| options.user_verification == crate::UserVerification::Preferred
|
||||
{
|
||||
pin = get_pin().ok_or(Fido2ClientError::WrongPin)?;
|
||||
get_assertion_args = get_assertion_args.pin(pin.as_str());
|
||||
}
|
||||
|
||||
let mut assertions = device
|
||||
.get_assertion_with_args(&get_assertion_args.build())
|
||||
.map_err(|_e| Fido2ClientError::AssertionError)?;
|
||||
|
||||
let assertion = if assertions.len() > 1 {
|
||||
let first_assertion = &assertions[0];
|
||||
let mut get_assertion_args = make_assertion(
|
||||
options.clone(),
|
||||
client_data_json.clone(),
|
||||
Some(&first_assertion.credential_id),
|
||||
)?;
|
||||
let pin: String;
|
||||
if options.user_verification == crate::UserVerification::Required
|
||||
|| options.user_verification == crate::UserVerification::Preferred
|
||||
{
|
||||
pin = get_pin().ok_or(Fido2ClientError::WrongPin)?;
|
||||
get_assertion_args = get_assertion_args.pin(pin.as_str());
|
||||
}
|
||||
|
||||
assertions = device
|
||||
.get_assertion_with_args(&get_assertion_args.build())
|
||||
.map_err(|_e| Fido2ClientError::AssertionError)?;
|
||||
assertions.get(0).ok_or(Fido2ClientError::AssertionError)?
|
||||
} else {
|
||||
assertions.get(0).ok_or(Fido2ClientError::AssertionError)?
|
||||
};
|
||||
|
||||
let prf_extension = assertion
|
||||
.extensions
|
||||
.iter()
|
||||
.find_map(|ext| {
|
||||
if let AssertionExtension::HmacSecret(results) = ext {
|
||||
Some(*results)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.flatten();
|
||||
|
||||
Ok(PublicKeyCredential {
|
||||
authenticator_attachment: "cross-platform".to_string(),
|
||||
id: BASE64_URL_SAFE_NO_PAD.encode(&assertion.credential_id),
|
||||
raw_id: assertion.credential_id.clone(),
|
||||
response: AuthenticatorAssertionResponse {
|
||||
authenticator_data: assertion.auth_data.clone(),
|
||||
client_data_json: client_data_json.as_bytes().to_vec(),
|
||||
signature: assertion.signature.clone(),
|
||||
user_handle: assertion.user.id.clone(),
|
||||
},
|
||||
r#type: "public-key".to_string(),
|
||||
prf: prf_extension,
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::{ctap_hid_fido2::get, PublicKeyCredentialRequestOptions};
|
||||
|
||||
#[test]
|
||||
#[ignore]
|
||||
fn assertion() {
|
||||
get(PublicKeyCredentialRequestOptions {
|
||||
challenge: vec![],
|
||||
timeout: 0,
|
||||
rp_id: "vault.usdev.bitwarden.pw".to_string(),
|
||||
user_verification: crate::UserVerification::Required,
|
||||
allow_credentials: vec![],
|
||||
prf_eval_first: [0u8; 32],
|
||||
prf_eval_second: None,
|
||||
})
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
78
apps/desktop/desktop_native/fido2_client/src/lib.rs
Normal file
78
apps/desktop/desktop_native/fido2_client/src/lib.rs
Normal file
@@ -0,0 +1,78 @@
|
||||
#[cfg(all(target_os = "linux", target_env = "gnu"))]
|
||||
mod ctap_hid_fido2;
|
||||
#[cfg(all(target_os = "linux", target_env = "gnu"))]
|
||||
use ctap_hid_fido2::*;
|
||||
|
||||
#[cfg(not(all(target_os = "linux", target_env = "gnu")))]
|
||||
mod unimplemented;
|
||||
#[cfg(not(all(target_os = "linux", target_env = "gnu")))]
|
||||
use unimplemented::*;
|
||||
|
||||
#[cfg(all(target_os = "linux", target_env = "gnu"))]
|
||||
/// Depending on the platform API, the platform MAY do this for you, or may require you to do it manually.
|
||||
fn prf_to_hmac(prf_salt: &[u8]) -> [u8; 32] {
|
||||
use sha2::Digest;
|
||||
sha2::Sha256::digest(&[b"WebAuthn PRF".as_slice(), &[0], prf_salt].concat()).into()
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
pub enum UserVerification {
|
||||
Discouraged,
|
||||
Preferred,
|
||||
Required,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct PrfConfig {
|
||||
pub first: Vec<u8>,
|
||||
pub second: Option<Vec<u8>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct PublicKeyCredentialRequestOptions {
|
||||
pub challenge: Vec<u8>,
|
||||
pub timeout: u64,
|
||||
pub rp_id: String,
|
||||
pub user_verification: UserVerification,
|
||||
pub allow_credentials: Vec<Vec<u8>>,
|
||||
pub prf: Option<PrfConfig>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct AuthenticatorAssertionResponse {
|
||||
pub authenticator_data: Vec<u8>,
|
||||
pub client_data_json: Vec<u8>,
|
||||
pub signature: Vec<u8>,
|
||||
pub user_handle: Vec<u8>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct PublicKeyCredential {
|
||||
pub authenticator_attachment: String,
|
||||
pub id: String,
|
||||
pub raw_id: Vec<u8>,
|
||||
pub response: AuthenticatorAssertionResponse,
|
||||
pub r#type: String,
|
||||
pub prf: Option<[u8; 32]>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Fido2ClientError {
|
||||
WrongPin,
|
||||
NoCredentials,
|
||||
NoDevice,
|
||||
InvalidInput,
|
||||
AssertionError,
|
||||
}
|
||||
|
||||
pub mod fido2_client {
|
||||
pub fn get(
|
||||
assertion_options: super::PublicKeyCredentialRequestOptions,
|
||||
) -> Result<super::PublicKeyCredential, super::Fido2ClientError> {
|
||||
super::get(assertion_options)
|
||||
}
|
||||
|
||||
pub fn available() -> bool {
|
||||
super::available()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
use crate::{Fido2ClientError, PublicKeyCredential, PublicKeyCredentialRequestOptions};
|
||||
|
||||
pub fn get(
|
||||
_options: PublicKeyCredentialRequestOptions,
|
||||
) -> Result<PublicKeyCredential, Fido2ClientError> {
|
||||
todo!("Fido2Client is unimplemented on this platform");
|
||||
}
|
||||
|
||||
pub fn available() -> bool {
|
||||
false
|
||||
}
|
||||
@@ -19,6 +19,7 @@ autotype = { path = "../autotype" }
|
||||
base64 = { workspace = true }
|
||||
chromium_importer = { path = "../chromium_importer" }
|
||||
desktop_core = { path = "../core" }
|
||||
fido2_client = { path = "../fido2_client" }
|
||||
hex = { workspace = true }
|
||||
napi = { workspace = true, features = ["async"] }
|
||||
napi-derive = { workspace = true }
|
||||
|
||||
35
apps/desktop/desktop_native/napi/index.d.ts
vendored
35
apps/desktop/desktop_native/napi/index.d.ts
vendored
@@ -254,3 +254,38 @@ export declare namespace autotype {
|
||||
export function getForegroundWindowTitle(): string
|
||||
export function typeInput(input: Array<number>, keyboardShortcut: Array<string>): void
|
||||
}
|
||||
export declare namespace navigator_credentials {
|
||||
export const enum UserVerification {
|
||||
Preferred = 'Preferred',
|
||||
Required = 'Required',
|
||||
Discouraged = 'Discouraged'
|
||||
}
|
||||
export interface PrfConfig {
|
||||
first: Uint8Array
|
||||
second?: Uint8Array
|
||||
}
|
||||
export interface PublicKeyCredentialRequestOptions {
|
||||
challenge: Uint8Array
|
||||
timeout: number
|
||||
rpId: string
|
||||
userVerification: UserVerification
|
||||
allowCredentials: Array<Uint8Array>
|
||||
prf?: PrfConfig
|
||||
}
|
||||
export interface AuthenticatorAssertionResponse {
|
||||
authenticatorData: Uint8Array
|
||||
clientDataJson: Uint8Array
|
||||
signature: Uint8Array
|
||||
userHandle: Uint8Array
|
||||
}
|
||||
export interface PublicKeyCredential {
|
||||
authenticatorAttachment: string
|
||||
id: string
|
||||
rawId: Uint8Array
|
||||
response: AuthenticatorAssertionResponse
|
||||
type: string
|
||||
prf?: Uint8Array
|
||||
}
|
||||
export function get(assertionOptions: PublicKeyCredentialRequestOptions): PublicKeyCredential
|
||||
export function available(): boolean
|
||||
}
|
||||
|
||||
@@ -1199,3 +1199,125 @@ pub mod autotype {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[napi]
|
||||
pub mod navigator_credentials {
|
||||
use napi::bindgen_prelude::Uint8Array;
|
||||
|
||||
#[napi(string_enum)]
|
||||
pub enum UserVerification {
|
||||
Preferred,
|
||||
Required,
|
||||
Discouraged,
|
||||
}
|
||||
|
||||
impl Into<fido2_client::UserVerification> for UserVerification {
|
||||
fn into(self) -> fido2_client::UserVerification {
|
||||
match self {
|
||||
UserVerification::Preferred => fido2_client::UserVerification::Preferred,
|
||||
UserVerification::Required => fido2_client::UserVerification::Required,
|
||||
UserVerification::Discouraged => fido2_client::UserVerification::Discouraged,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[napi(object)]
|
||||
pub struct PrfConfig {
|
||||
pub first: Uint8Array,
|
||||
pub second: Option<Uint8Array>,
|
||||
}
|
||||
|
||||
impl Into<fido2_client::PrfConfig> for PrfConfig {
|
||||
fn into(self) -> fido2_client::PrfConfig {
|
||||
fido2_client::PrfConfig {
|
||||
first: self.first.to_vec(),
|
||||
second: self.second.map(|s| s.to_vec()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[napi(object)]
|
||||
pub struct PublicKeyCredentialRequestOptions {
|
||||
pub challenge: Uint8Array,
|
||||
pub timeout: i64,
|
||||
pub rp_id: String,
|
||||
pub user_verification: UserVerification,
|
||||
pub allow_credentials: Vec<Uint8Array>,
|
||||
pub prf: Option<PrfConfig>,
|
||||
}
|
||||
|
||||
impl TryInto<fido2_client::PublicKeyCredentialRequestOptions>
|
||||
for PublicKeyCredentialRequestOptions
|
||||
{
|
||||
type Error = napi::Error;
|
||||
|
||||
fn try_into(self) -> Result<fido2_client::PublicKeyCredentialRequestOptions, Self::Error> {
|
||||
Ok(fido2_client::PublicKeyCredentialRequestOptions {
|
||||
challenge: self.challenge.to_vec(),
|
||||
timeout: self.timeout as u64,
|
||||
rp_id: self.rp_id,
|
||||
user_verification: self.user_verification.into(),
|
||||
allow_credentials: self.allow_credentials.iter().map(|c| c.to_vec()).collect(),
|
||||
prf: self.prf.map(|p| p.into()),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[napi(object)]
|
||||
pub struct AuthenticatorAssertionResponse {
|
||||
pub authenticator_data: Uint8Array,
|
||||
pub client_data_json: Uint8Array,
|
||||
pub signature: Uint8Array,
|
||||
pub user_handle: Uint8Array,
|
||||
}
|
||||
|
||||
impl From<fido2_client::AuthenticatorAssertionResponse> for AuthenticatorAssertionResponse {
|
||||
fn from(response: fido2_client::AuthenticatorAssertionResponse) -> Self {
|
||||
AuthenticatorAssertionResponse {
|
||||
authenticator_data: Uint8Array::from(response.authenticator_data),
|
||||
client_data_json: Uint8Array::from(response.client_data_json),
|
||||
signature: Uint8Array::from(response.signature),
|
||||
user_handle: Uint8Array::from(response.user_handle),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[napi(object)]
|
||||
pub struct PublicKeyCredential {
|
||||
pub authenticator_attachment: String,
|
||||
pub id: String,
|
||||
pub raw_id: Uint8Array,
|
||||
pub response: AuthenticatorAssertionResponse,
|
||||
pub r#type: String,
|
||||
pub prf: Option<Uint8Array>,
|
||||
}
|
||||
|
||||
impl Into<PublicKeyCredential> for fido2_client::PublicKeyCredential {
|
||||
fn into(self) -> PublicKeyCredential {
|
||||
PublicKeyCredential {
|
||||
authenticator_attachment: self.authenticator_attachment,
|
||||
id: self.id,
|
||||
raw_id: Uint8Array::from(self.raw_id),
|
||||
response: self.response.into(),
|
||||
r#type: self.r#type,
|
||||
prf: self.prf.map(|p| Uint8Array::from(p)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[napi]
|
||||
pub fn get(
|
||||
assertion_options: PublicKeyCredentialRequestOptions,
|
||||
) -> napi::Result<PublicKeyCredential> {
|
||||
let options: fido2_client::PublicKeyCredentialRequestOptions =
|
||||
assertion_options.try_into()?;
|
||||
fido2_client::fido2_client::get(options)
|
||||
.map_err(|e| napi::Error::from_reason(format!("FIDO2 Authentication failed: {:?}", e)))
|
||||
.map(|credential| credential.into())
|
||||
}
|
||||
|
||||
#[napi]
|
||||
pub fn available() -> bool {
|
||||
fido2_client::fido2_client::available()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ import {
|
||||
tdeDecryptionRequiredGuard,
|
||||
unauthGuardFn,
|
||||
} from "@bitwarden/angular/auth/guards";
|
||||
import { LoginViaWebAuthnComponent } from "@bitwarden/angular/auth/login-via-webauthn/login-via-webauthn.component";
|
||||
import { ChangePasswordComponent } from "@bitwarden/angular/auth/password-management/change-password";
|
||||
import { SetInitialPasswordComponent } from "@bitwarden/angular/auth/password-management/set-initial-password/set-initial-password.component";
|
||||
import {
|
||||
@@ -23,6 +24,7 @@ import {
|
||||
VaultIcon,
|
||||
LockIcon,
|
||||
DomainIcon,
|
||||
TwoFactorAuthSecurityKeyIcon,
|
||||
} from "@bitwarden/assets/svg";
|
||||
import {
|
||||
LoginComponent,
|
||||
@@ -123,6 +125,27 @@ const routes: Routes = [
|
||||
path: "",
|
||||
component: AnonLayoutWrapperComponent,
|
||||
children: [
|
||||
{
|
||||
path: AuthRoute.LoginWithPasskey,
|
||||
canActivate: [unauthGuardFn()],
|
||||
data: {
|
||||
pageIcon: TwoFactorAuthSecurityKeyIcon,
|
||||
pageTitle: {
|
||||
key: "logInWithPasskey",
|
||||
},
|
||||
pageSubtitle: {
|
||||
key: "readingPasskeyLoadingInfo",
|
||||
},
|
||||
} satisfies RouteDataProperties & AnonLayoutWrapperData,
|
||||
children: [
|
||||
{ path: "", component: LoginViaWebAuthnComponent },
|
||||
{
|
||||
path: "",
|
||||
component: EnvironmentSelectorComponent,
|
||||
outlet: "environment-selector",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: AuthRoute.SignUp,
|
||||
canActivate: [unauthGuardFn()],
|
||||
|
||||
@@ -51,6 +51,7 @@ import {
|
||||
} from "@bitwarden/common/auth/abstractions/auth.service";
|
||||
import { MasterPasswordApiService } from "@bitwarden/common/auth/abstractions/master-password-api.service.abstraction";
|
||||
import { SsoLoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/sso-login.service.abstraction";
|
||||
import { NavigatorCredentialsService } from "@bitwarden/common/auth/abstractions/webauthn/navigator-credentials.service";
|
||||
import { AutofillSettingsServiceAbstraction } from "@bitwarden/common/autofill/services/autofill-settings.service";
|
||||
import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions";
|
||||
import { ClientType } from "@bitwarden/common/enums";
|
||||
@@ -119,6 +120,7 @@ import { DefaultSshImportPromptService, SshImportPromptService } from "@bitwarde
|
||||
import { DesktopLoginApprovalDialogComponentService } from "../../auth/login/desktop-login-approval-dialog-component.service";
|
||||
import { DesktopLoginComponentService } from "../../auth/login/desktop-login-component.service";
|
||||
import { DesktopTwoFactorAuthDuoComponentService } from "../../auth/services/desktop-two-factor-auth-duo-component.service";
|
||||
import { RendererNavigatorCredentialsService } from "../../auth/services/renderer-navigator-credentials.service";
|
||||
import { DesktopAutofillSettingsService } from "../../autofill/services/desktop-autofill-settings.service";
|
||||
import { DesktopAutofillService } from "../../autofill/services/desktop-autofill.service";
|
||||
import { DesktopAutotypeDefaultSettingPolicy } from "../../autofill/services/desktop-autotype-policy.service";
|
||||
@@ -310,6 +312,11 @@ const safeProviders: SafeProvider[] = [
|
||||
useClass: WebCryptoFunctionService,
|
||||
deps: [WINDOW],
|
||||
}),
|
||||
safeProvider({
|
||||
provide: NavigatorCredentialsService,
|
||||
useClass: RendererNavigatorCredentialsService,
|
||||
deps: [],
|
||||
}),
|
||||
safeProvider({
|
||||
provide: KeyServiceAbstraction,
|
||||
useClass: ElectronKeyService,
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import { ipcRenderer } from "electron";
|
||||
|
||||
import { navigator_credentials } from "@bitwarden/desktop-napi";
|
||||
|
||||
export default {
|
||||
loginRequest: (alertTitle: string, alertBody: string, buttonText: string): Promise<void> =>
|
||||
ipcRenderer.invoke("loginRequest", {
|
||||
@@ -7,4 +9,10 @@ export default {
|
||||
alertBody,
|
||||
buttonText,
|
||||
}),
|
||||
navigatorCredentialsGet: (
|
||||
options: navigator_credentials.PublicKeyCredentialRequestOptions,
|
||||
): Promise<navigator_credentials.PublicKeyCredential | null> =>
|
||||
ipcRenderer.invoke("navigatorCredentials.get", options),
|
||||
navigatorCredentialsAvailable: (): Promise<boolean> =>
|
||||
ipcRenderer.invoke("navigatorCredentials.available"),
|
||||
};
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
import { ipcMain } from "electron";
|
||||
|
||||
import { navigator_credentials } from "@bitwarden/desktop-napi";
|
||||
|
||||
export class MainNavigatorCredentialsService {
|
||||
constructor() {
|
||||
ipcMain.handle(
|
||||
"navigatorCredentials.get",
|
||||
async (_event: any, message: navigator_credentials.PublicKeyCredentialRequestOptions) => {
|
||||
return navigator_credentials.get(message);
|
||||
},
|
||||
);
|
||||
ipcMain.handle("navigatorCredentials.available", async (_event: any, _message: any) => {
|
||||
return navigator_credentials.available();
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
import { navigator_credentials } from "apps/desktop/desktop_native/napi";
|
||||
|
||||
import { NavigatorCredentialsService } from "@bitwarden/common/auth/abstractions/webauthn/navigator-credentials.service";
|
||||
|
||||
export class RendererNavigatorCredentialsService implements NavigatorCredentialsService {
|
||||
constructor() {}
|
||||
|
||||
async get(options: CredentialRequestOptions): Promise<Credential | null> {
|
||||
return await ipc.auth.navigatorCredentialsGet({
|
||||
challenge: arrayBufferSourceToUint8Array(options.publicKey.challenge),
|
||||
timeout: options.publicKey.timeout,
|
||||
rpId: options.publicKey.rpId,
|
||||
userVerification: convertUserVerification(options.publicKey.userVerification),
|
||||
allowCredentials: options.publicKey.allowCredentials.map((cred) => {
|
||||
return arrayBufferSourceToUint8Array(cred.id);
|
||||
}),
|
||||
prf: options.publicKey.extensions?.prf
|
||||
? {
|
||||
first: arrayBufferSourceToUint8Array(options.publicKey.extensions!.prf!.eval.first),
|
||||
second: undefined,
|
||||
}
|
||||
: undefined,
|
||||
});
|
||||
}
|
||||
|
||||
async available(): Promise<boolean> {
|
||||
return await ipc.auth.navigatorCredentialsAvailable();
|
||||
}
|
||||
}
|
||||
|
||||
function arrayBufferSourceToUint8Array(source: BufferSource): Uint8Array {
|
||||
if (source instanceof ArrayBuffer) {
|
||||
return new Uint8Array(source);
|
||||
} else {
|
||||
return new Uint8Array(source.buffer, source.byteOffset, source.byteLength);
|
||||
}
|
||||
}
|
||||
|
||||
function convertUserVerification(
|
||||
userVerification: UserVerificationRequirement | undefined,
|
||||
): navigator_credentials.UserVerification | undefined {
|
||||
switch (userVerification) {
|
||||
case "required":
|
||||
return navigator_credentials.UserVerification.Required;
|
||||
case "preferred":
|
||||
return navigator_credentials.UserVerification.Preferred;
|
||||
case "discouraged":
|
||||
return navigator_credentials.UserVerification.Discouraged;
|
||||
default:
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
@@ -34,6 +34,7 @@ import {
|
||||
import { SerializedMemoryStorageService, StorageServiceProvider } from "@bitwarden/storage-core";
|
||||
|
||||
import { ChromiumImporterService } from "./app/tools/import/chromium-importer.service";
|
||||
import { MainNavigatorCredentialsService } from "./auth/services/main-navigator-credentials.serivce";
|
||||
import { MainDesktopAutotypeService } from "./autofill/main/main-desktop-autotype.service";
|
||||
import { MainSshAgentService } from "./autofill/main/main-ssh-agent.service";
|
||||
import { DesktopAutofillSettingsService } from "./autofill/services/desktop-autofill-settings.service";
|
||||
@@ -284,6 +285,7 @@ export class Main {
|
||||
app.getPath("exe"),
|
||||
app.getAppPath(),
|
||||
);
|
||||
new MainNavigatorCredentialsService();
|
||||
|
||||
this.desktopAutofillSettingsService = new DesktopAutofillSettingsService(stateProvider);
|
||||
|
||||
|
||||
@@ -62,7 +62,9 @@ import { AccountService } from "@bitwarden/common/auth/abstractions/account.serv
|
||||
import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service";
|
||||
import { MasterPasswordApiService } from "@bitwarden/common/auth/abstractions/master-password-api.service.abstraction";
|
||||
import { SsoLoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/sso-login.service.abstraction";
|
||||
import { NavigatorCredentialsService } from "@bitwarden/common/auth/abstractions/webauthn/navigator-credentials.service";
|
||||
import { OrganizationInviteService } from "@bitwarden/common/auth/services/organization-invite/organization-invite.service";
|
||||
import { DefaultNavigatorCredentialsService } from "@bitwarden/common/auth/services/webauthn-login/default-navigator-credentials.service";
|
||||
import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service";
|
||||
import { ClientType } from "@bitwarden/common/enums";
|
||||
import { ProcessReloadServiceAbstraction } from "@bitwarden/common/key-management/abstractions/process-reload.service";
|
||||
@@ -469,6 +471,11 @@ const safeProviders: SafeProvider[] = [
|
||||
useClass: WebSystemService,
|
||||
deps: [],
|
||||
}),
|
||||
safeProvider({
|
||||
provide: NavigatorCredentialsService,
|
||||
useClass: DefaultNavigatorCredentialsService,
|
||||
deps: [WINDOW, PlatformUtilsService],
|
||||
}),
|
||||
safeProvider({
|
||||
provide: SessionTimeoutSettingsComponentService,
|
||||
useClass: WebSessionTimeoutSettingsComponentService,
|
||||
|
||||
@@ -105,6 +105,7 @@ import { TokenService as TokenServiceAbstraction } from "@bitwarden/common/auth/
|
||||
import { TwoFactorService as TwoFactorServiceAbstraction } from "@bitwarden/common/auth/abstractions/two-factor.service";
|
||||
import { UserVerificationApiServiceAbstraction } from "@bitwarden/common/auth/abstractions/user-verification/user-verification-api.service.abstraction";
|
||||
import { UserVerificationService as UserVerificationServiceAbstraction } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction";
|
||||
import { NavigatorCredentialsService } from "@bitwarden/common/auth/abstractions/webauthn/navigator-credentials.service";
|
||||
import { WebAuthnLoginApiServiceAbstraction } from "@bitwarden/common/auth/abstractions/webauthn/webauthn-login-api.service.abstraction";
|
||||
import { WebAuthnLoginPrfKeyServiceAbstraction } from "@bitwarden/common/auth/abstractions/webauthn/webauthn-login-prf-key.service.abstraction";
|
||||
import { WebAuthnLoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/webauthn/webauthn-login.service.abstraction";
|
||||
@@ -1346,7 +1347,7 @@ const safeProviders: SafeProvider[] = [
|
||||
WebAuthnLoginApiServiceAbstraction,
|
||||
LoginStrategyServiceAbstraction,
|
||||
WebAuthnLoginPrfKeyServiceAbstraction,
|
||||
WINDOW,
|
||||
NavigatorCredentialsService,
|
||||
LogService,
|
||||
],
|
||||
}),
|
||||
|
||||
@@ -23,10 +23,6 @@ export class DefaultLoginComponentService implements LoginComponentService {
|
||||
this.clientType = this.platformUtilsService.getClientType();
|
||||
}
|
||||
|
||||
isLoginWithPasskeySupported(): boolean {
|
||||
return this.clientType === ClientType.Web;
|
||||
}
|
||||
|
||||
/**
|
||||
* Redirects the user to the SSO login page, either via route or in a new browser window.
|
||||
* @param email The email address of the user attempting to log in
|
||||
|
||||
@@ -25,11 +25,6 @@ export abstract class LoginComponentService {
|
||||
*/
|
||||
getOrgPoliciesFromOrgInvite?: (email: string) => Promise<PasswordPolicies | null>;
|
||||
|
||||
/**
|
||||
* Indicates whether login with passkey is supported on the given client
|
||||
*/
|
||||
isLoginWithPasskeySupported: () => boolean;
|
||||
|
||||
/**
|
||||
* Redirects the user to the SSO login page, either via route or in a new browser window.
|
||||
*/
|
||||
|
||||
@@ -27,6 +27,7 @@ import { MasterPasswordPolicyOptions } from "@bitwarden/common/admin-console/mod
|
||||
import { Policy } from "@bitwarden/common/admin-console/models/domain/policy";
|
||||
import { DevicesApiServiceAbstraction } from "@bitwarden/common/auth/abstractions/devices-api.service.abstraction";
|
||||
import { SsoLoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/sso-login.service.abstraction";
|
||||
import { WebauthnLoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/webauthn-login/webauthn-login.service";
|
||||
import { AuthResult } from "@bitwarden/common/auth/models/domain/auth-result";
|
||||
import { ClientType, HttpStatusCode } from "@bitwarden/common/enums";
|
||||
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
|
||||
@@ -146,6 +147,7 @@ export class LoginComponent implements OnInit, OnDestroy {
|
||||
private configService: ConfigService,
|
||||
private ssoLoginService: SsoLoginServiceAbstraction,
|
||||
private environmentService: EnvironmentService,
|
||||
private webauthnLoginService: WebauthnLoginServiceAbstraction,
|
||||
) {
|
||||
this.clientType = this.platformUtilsService.getClientType();
|
||||
}
|
||||
@@ -525,7 +527,7 @@ export class LoginComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
|
||||
isLoginWithPasskeySupported() {
|
||||
return this.loginComponentService.isLoginWithPasskeySupported();
|
||||
return this.webauthnLoginService.available();
|
||||
}
|
||||
|
||||
protected async goToHint(): Promise<void> {
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
export abstract class NavigatorCredentialsService {
|
||||
abstract get(options: CredentialRequestOptions): Promise<Credential | null>;
|
||||
abstract available(): Promise<boolean>;
|
||||
}
|
||||
@@ -38,4 +38,9 @@ export abstract class WebAuthnLoginServiceAbstraction {
|
||||
* that needs to be validated for login.
|
||||
*/
|
||||
abstract logIn(assertion: WebAuthnLoginCredentialAssertionView): Promise<AuthResult>;
|
||||
|
||||
/**
|
||||
* Checks if WebAuthnLogin is available in the current environment.
|
||||
*/
|
||||
abstract available(): Promise<boolean>;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
import { ClientType } from "@bitwarden/client-type";
|
||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||
|
||||
import { NavigatorCredentialsServiceAbstraction } from "../../abstractions/webauthn/navigator-credentials.service";
|
||||
|
||||
export class DefaultNavigatorCredentialsService implements NavigatorCredentialsServiceAbstraction {
|
||||
private navigatorCredentials: CredentialsContainer;
|
||||
|
||||
constructor(
|
||||
private window: Window,
|
||||
private platformUtilsService: PlatformUtilsService,
|
||||
) {
|
||||
this.navigatorCredentials = this.window.navigator.credentials;
|
||||
}
|
||||
|
||||
async get(options: CredentialRequestOptions): Promise<Credential | null> {
|
||||
return await this.navigatorCredentials.get(options);
|
||||
}
|
||||
|
||||
async available(): Promise<boolean> {
|
||||
return this.platformUtilsService.getClientType() === ClientType.Web;
|
||||
}
|
||||
}
|
||||
@@ -6,6 +6,7 @@ import { LoginStrategyServiceAbstraction, WebAuthnLoginCredentials } from "@bitw
|
||||
|
||||
import { LogService } from "../../../platform/abstractions/log.service";
|
||||
import { PrfKey } from "../../../types/key";
|
||||
import { NavigatorCredentialsService } from "../../abstractions/webauthn/navigator-credentials.service";
|
||||
import { WebAuthnLoginApiServiceAbstraction } from "../../abstractions/webauthn/webauthn-login-api.service.abstraction";
|
||||
import { WebAuthnLoginPrfKeyServiceAbstraction } from "../../abstractions/webauthn/webauthn-login-prf-key.service.abstraction";
|
||||
import { WebAuthnLoginServiceAbstraction } from "../../abstractions/webauthn/webauthn-login.service.abstraction";
|
||||
@@ -16,17 +17,13 @@ import { WebAuthnLoginCredentialAssertionView } from "../../models/view/webauthn
|
||||
import { WebAuthnLoginAssertionResponseRequest } from "./request/webauthn-login-assertion-response.request";
|
||||
|
||||
export class WebAuthnLoginService implements WebAuthnLoginServiceAbstraction {
|
||||
private navigatorCredentials: CredentialsContainer;
|
||||
|
||||
constructor(
|
||||
private webAuthnLoginApiService: WebAuthnLoginApiServiceAbstraction,
|
||||
private loginStrategyService: LoginStrategyServiceAbstraction,
|
||||
private webAuthnLoginPrfKeyService: WebAuthnLoginPrfKeyServiceAbstraction,
|
||||
private window: Window,
|
||||
private logService?: LogService,
|
||||
) {
|
||||
this.navigatorCredentials = this.window.navigator.credentials;
|
||||
}
|
||||
protected loginStrategyService: LoginStrategyServiceAbstraction,
|
||||
protected webAuthnLoginPrfKeyService: WebAuthnLoginPrfKeyServiceAbstraction,
|
||||
protected navigatorCredentialsService: NavigatorCredentialsService,
|
||||
protected logService?: LogService,
|
||||
) {}
|
||||
|
||||
async getCredentialAssertionOptions(): Promise<WebAuthnLoginCredentialAssertionOptionsView> {
|
||||
const response = await this.webAuthnLoginApiService.getCredentialAssertionOptions();
|
||||
@@ -45,7 +42,7 @@ export class WebAuthnLoginService implements WebAuthnLoginServiceAbstraction {
|
||||
} as any;
|
||||
|
||||
try {
|
||||
const response = await this.navigatorCredentials.get(nativeOptions);
|
||||
const response = await this.navigatorCredentialsService.get(nativeOptions);
|
||||
if (!(response instanceof PublicKeyCredential)) {
|
||||
return undefined;
|
||||
}
|
||||
@@ -85,4 +82,8 @@ export class WebAuthnLoginService implements WebAuthnLoginServiceAbstraction {
|
||||
const result = await this.loginStrategyService.logIn(credential);
|
||||
return result;
|
||||
}
|
||||
|
||||
async available(): Promise<boolean> {
|
||||
return this.navigatorCredentialsService.available();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user