From 830af7b06d03670ea9e7fd337531a0b4e35a2aa6 Mon Sep 17 00:00:00 2001 From: Matt Gibson Date: Tue, 18 Apr 2023 09:09:47 -0400 Subject: [PATCH] Rework Desktop Biometrics (#5234) --- .github/whitelist-capital-letters.txt | 1 - .vscode/settings.json | 2 +- apps/desktop/desktop_native/Cargo.lock | 783 ++++++++++++------ apps/desktop/desktop_native/Cargo.toml | 11 +- apps/desktop/desktop_native/index.d.ts | 20 + .../desktop_native/src/biometric/macos.rs | 39 +- .../desktop_native/src/biometric/mod.rs | 25 +- .../desktop_native/src/biometric/unix.rs | 39 +- .../desktop_native/src/biometric/windows.rs | 390 ++++++++- .../src/crypto/cipher_string.rs | 212 +++++ .../desktop_native/src/crypto/crypto.rs | 39 + apps/desktop/desktop_native/src/crypto/mod.rs | 5 + apps/desktop/desktop_native/src/error.rs | 43 + apps/desktop/desktop_native/src/lib.rs | 57 +- .../src/app/accounts/settings.component.html | 21 +- .../src/app/accounts/settings.component.ts | 65 +- .../src/app/services/services.module.ts | 31 +- apps/desktop/src/auth/lock.component.html | 2 +- apps/desktop/src/auth/lock.component.ts | 50 +- apps/desktop/src/locales/en/messages.json | 22 +- apps/desktop/src/main.ts | 9 +- .../main/biometric/biometric.darwin.main.ts | 47 +- .../main/biometric/biometric.windows.main.ts | 226 ++++- .../biometrics.service.abstraction.ts | 41 +- .../main/biometric/biometrics.service.spec.ts | 63 +- .../src/main/biometric/biometrics.service.ts | 122 ++- .../desktop-credential-storage-listener.ts | 107 ++- apps/desktop/src/models/account.ts | 11 + apps/desktop/src/scss/misc.scss | 4 + .../src/services/electron-crypto.service.ts | 83 +- .../electron-platform-utils.service.ts | 9 +- .../electron-state.service.abstraction.ts | 17 + .../src/services/electron-state.service.ts | 80 ++ .../encrypted-message-handler.service.ts | 4 +- apps/desktop/src/services/state.service.ts | 16 - apps/desktop/src/types/biometric-message.ts | 11 + libs/common/spec/misc/utils.spec.ts | 4 +- .../{encString.spec.ts => enc-string.spec.ts} | 30 + .../spec/services/encrypt.service.spec.ts | 5 +- .../services/stateMigration.service.spec.ts | 88 +- .../abstractions/cryptoFunction.service.ts | 3 +- libs/common/src/abstractions/state.service.ts | 7 +- libs/common/src/auth/types/biometric-key.d.ts | 6 + libs/common/src/enums/encryption-type.enum.ts | 25 + libs/common/src/enums/state-version.enum.ts | 3 +- libs/common/src/models/domain/account.ts | 35 +- libs/common/src/models/domain/enc-string.ts | 28 +- libs/common/src/models/domain/global-state.ts | 1 - .../src/models/domain/symmetric-crypto-key.ts | 10 +- libs/common/src/services/crypto.service.ts | 17 +- libs/common/src/services/state.service.ts | 47 +- .../src/services/stateMigration.service.ts | 28 +- .../src/services/webCryptoFunction.service.ts | 5 +- libs/common/src/types/csprng.d.ts | 5 + .../services/node-crypto-function.service.ts | 7 +- 55 files changed, 2497 insertions(+), 564 deletions(-) create mode 100644 apps/desktop/desktop_native/src/crypto/cipher_string.rs create mode 100644 apps/desktop/desktop_native/src/crypto/crypto.rs create mode 100644 apps/desktop/desktop_native/src/crypto/mod.rs create mode 100644 apps/desktop/desktop_native/src/error.rs create mode 100644 apps/desktop/src/services/electron-state.service.abstraction.ts create mode 100644 apps/desktop/src/services/electron-state.service.ts delete mode 100644 apps/desktop/src/services/state.service.ts create mode 100644 apps/desktop/src/types/biometric-message.ts rename libs/common/spec/models/domain/{encString.spec.ts => enc-string.spec.ts} (88%) create mode 100644 libs/common/src/auth/types/biometric-key.d.ts create mode 100644 libs/common/src/types/csprng.d.ts diff --git a/.github/whitelist-capital-letters.txt b/.github/whitelist-capital-letters.txt index 7854ccea09..0a73f96cb1 100644 --- a/.github/whitelist-capital-letters.txt +++ b/.github/whitelist-capital-letters.txt @@ -27,7 +27,6 @@ ./libs/components/src/stories/Introduction.stories.mdx ./libs/common/spec/web/services/webCryptoFunction.service.spec.ts ./libs/common/spec/shared/interceptConsole.ts -./libs/common/spec/models/domain/encString.spec.ts ./libs/common/spec/models/domain/symmetricCryptoKey.spec.ts ./libs/common/spec/models/domain/encArrayBuffer.spec.ts ./libs/common/spec/matchers/toEqualBuffer.spec.ts diff --git a/.vscode/settings.json b/.vscode/settings.json index fe586d49e9..48fd373db4 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,3 +1,3 @@ { - "cSpell.words": ["Popout", "Reprompt", "takeuntil"] + "cSpell.words": ["Csprng", "Popout", "Reprompt", "takeuntil"] } diff --git a/apps/desktop/desktop_native/Cargo.lock b/apps/desktop/desktop_native/Cargo.lock index ec8142fae0..1d2d79f2e9 100644 --- a/apps/desktop/desktop_native/Cargo.lock +++ b/apps/desktop/desktop_native/Cargo.lock @@ -3,19 +3,42 @@ version = 3 [[package]] -name = "aho-corasick" -version = "0.7.18" +name = "aes" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f" +checksum = "433cfd6710c9986c576a25ca913c39d66a6474107b406f34f91d4a8923395241" +dependencies = [ + "cfg-if", + "cipher", + "cpufeatures", +] + +[[package]] +name = "aho-corasick" +version = "0.7.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc936419f96fa211c1b9166887b38e5e40b19958e5b895be7c1f93adec7071ac" dependencies = [ "memchr", ] [[package]] name = "anyhow" -version = "1.0.55" +version = "1.0.70" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "159bb86af3a200e19a068f4224eae4c8bb2d0fa054c7e5d1cacd5cef95e684cd" +checksum = "7de8ce5e0f9f8d88245311066a578d72b7af3e7088f32783804676302df237e4" + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "base64" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4a4ddaa51a5bc52a6948f74c06d20aaaddb71924eab79b8c97a8c556e942d6a" [[package]] name = "bitflags" @@ -24,22 +47,55 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] -name = "bytes" -version = "1.1.0" +name = "bitflags" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4872d67bab6358e59559027aa3b9157c53d9358c51423c17554809a8858e0f8" +checksum = "c70beb79cbb5ce9c4f8e20849978f34225931f665bb49efa6982875a4d5facb3" + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "block-padding" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8894febbff9f758034a5b8e12d87918f56dfc64a8e1fe757d65e29041538d93" +dependencies = [ + "generic-array", +] + +[[package]] +name = "bytes" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be" + +[[package]] +name = "cbc" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26b52a9543ae338f279b96b0b9fed9c8093744685043739079ce85cd58f289a6" +dependencies = [ + "cipher", +] [[package]] name = "cc" -version = "1.0.73" +version = "1.0.79" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fff2a6927b3bb87f9595d67196a70493f627687a71d87a0d692242c33f58c11" +checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f" [[package]] name = "cfg-expr" -version = "0.10.2" +version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e068cb2806bbc15b439846dc16c5f89f8599f2c3e4d73d4449d38f9b2f0b6c5" +checksum = "a35b255461940a32985c627ce82900867c61db1659764d3675ea81963f72a4c6" dependencies = [ "smallvec", ] @@ -50,6 +106,16 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "cipher" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" +dependencies = [ + "crypto-common", + "inout", +] + [[package]] name = "codespan-reporting" version = "0.11.1" @@ -62,9 +128,12 @@ dependencies = [ [[package]] name = "convert_case" -version = "0.5.0" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb4a24b1aaf0fd0ce8b45161144d6f42cd91677fd5940fd431183eb023b3a2b8" +checksum = "ec182b0ca2f35d8fc196cf3404988fd8b8c739a4d270ff118a398feb0cbec1ca" +dependencies = [ + "unicode-segmentation", +] [[package]] name = "core-foundation" @@ -78,25 +147,44 @@ dependencies = [ [[package]] name = "core-foundation-sys" -version = "0.8.3" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc" +checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa" + +[[package]] +name = "cpufeatures" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "280a9f2d8b3a38871a3c8a46fb80db65e5e5ed97da80c4d08bf27fb63e35e181" +dependencies = [ + "libc", +] + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] [[package]] name = "ctor" -version = "0.1.21" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ccc0a48a9b826acdf4028595adc9db92caea352f7af011a3034acd172a52a0aa" +checksum = "dd4056f63fce3b82d852c3da92b08ea59959890813a7f4ce9c0ff85b10cf301b" dependencies = [ "quote", - "syn", + "syn 2.0.14", ] [[package]] name = "cxx" -version = "1.0.66" +version = "1.0.94" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce2295fe8865279f404147e9b2328e5af0ad11a2c016e58c13acfd48a07d8a55" +checksum = "f61f1b6389c3fe1c316bf8a4dccc90a38208354b330925bce1f74a6c4756eb93" dependencies = [ "cc", "cxxbridge-flags", @@ -106,9 +194,9 @@ dependencies = [ [[package]] name = "cxx-build" -version = "1.0.66" +version = "1.0.94" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0aaaa055d4908326f1b4524b23ae53758019b806c0c4f382ea240982e9766b26" +checksum = "12cee708e8962df2aeb38f594aae5d827c022b6460ac71a7a3e2c3c2aae5a07b" dependencies = [ "cc", "codespan-reporting", @@ -116,31 +204,34 @@ dependencies = [ "proc-macro2", "quote", "scratch", - "syn", + "syn 2.0.14", ] [[package]] name = "cxxbridge-flags" -version = "1.0.66" +version = "1.0.94" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a670224c6686471df12560a0b97a08145082e70bd38e2b0b5383b79e46c3da7" +checksum = "7944172ae7e4068c533afbb984114a56c46e9ccddda550499caa222902c7f7bb" [[package]] name = "cxxbridge-macro" -version = "1.0.66" +version = "1.0.94" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b700096ca0dece28d9535fdb17ab784a8ae155d7f29d39c273643e6292c9620" +checksum = "2345488264226bf682893e25de0769f3360aac9957980ec49361b083ddaa5bc5" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.14", ] [[package]] name = "desktop_native" version = "0.0.0" dependencies = [ + "aes", "anyhow", + "base64", + "cbc", "core-foundation", "gio", "keytar", @@ -148,34 +239,49 @@ dependencies = [ "napi", "napi-build", "napi-derive", + "rand", + "retry", "scopeguard", "security-framework", "security-framework-sys", + "sha2", + "thiserror", "tokio", + "typenum", "widestring", "windows", ] [[package]] -name = "futures-channel" -version = "0.3.21" +name = "digest" +version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3083ce4b914124575708913bca19bfe887522d6e2e6d0952943f5eac4a74010" +checksum = "8168378f4e5023e7218c89c891c0fd8ecdb5e5e4f18cb78f38cf245dd021e76f" +dependencies = [ + "block-buffer", + "crypto-common", +] + +[[package]] +name = "futures-channel" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "955518d47e09b25bbebc7a18df10b81f0c766eaf4c4f1cccef2fca5f2a4fb5f2" dependencies = [ "futures-core", ] [[package]] name = "futures-core" -version = "0.3.21" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c09fd04b7e4073ac7156a9539b57a484a8ea920f79c7c675d05d289ab6110d3" +checksum = "4bca583b7e26f571124fe5b7561d49cb2868d79116cfa0eefce955557c6fee8c" [[package]] name = "futures-executor" -version = "0.3.21" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9420b90cfa29e327d0429f19be13e7ddb68fa1cccb09d65e5706b8c7a749b8a6" +checksum = "ccecee823288125bd88b4d7f565c9e58e41858e47ab72e8ea2d64e93624386e0" dependencies = [ "futures-core", "futures-task", @@ -184,21 +290,21 @@ dependencies = [ [[package]] name = "futures-io" -version = "0.3.21" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc4045962a5a5e935ee2fdedaa4e08284547402885ab326734432bed5d12966b" +checksum = "4fff74096e71ed47f8e023204cfd0aa1289cd54ae5430a9523be060cdb849964" [[package]] name = "futures-task" -version = "0.3.21" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57c66a976bf5909d801bbef33416c41372779507e7a6b3a5e25e4749c58f776a" +checksum = "76d3d132be6c0e6aa1534069c705a74a5997a356c0dc2f86a47765e5617c5b65" [[package]] name = "futures-util" -version = "0.3.21" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8b7abd5d659d9b90c8cba917f6ec750a74e2dc23902ef9cd4cc8c8b22e6036a" +checksum = "26b01e40b772d54cf6c6d721c1d1abd0647a0106a12ecaa1c186273392a69533" dependencies = [ "futures-core", "futures-task", @@ -208,12 +314,33 @@ dependencies = [ ] [[package]] -name = "gio" -version = "0.15.6" +name = "generic-array" +version = "0.14.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96efd8a1c00d890f6b45671916e165b5e43ccec61957d443aff6d7e44f62d348" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" dependencies = [ - "bitflags", + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c85e1d9ab2eadba7e5040d4e09cbd6d072b76a557ad64e797c2cb9d4da21d7e4" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "gio" +version = "0.15.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68fdbc90312d462781a395f7a16d96a2b379bb6ef8cd6310a2df272771c4283b" +dependencies = [ + "bitflags 1.3.2", "futures-channel", "futures-core", "futures-io", @@ -226,9 +353,9 @@ dependencies = [ [[package]] name = "gio-sys" -version = "0.15.6" +version = "0.15.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d0fa5052773f5a56b8ae47dab09d040f5d9ce1311f4f99006e16e9a08269296" +checksum = "32157a475271e2c4a023382e9cab31c4584ee30a97da41d3c4e9fdd605abcf8d" dependencies = [ "glib-sys", "gobject-sys", @@ -239,11 +366,11 @@ dependencies = [ [[package]] name = "glib" -version = "0.15.6" +version = "0.15.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa570813c504bdf7539a9400180c2dd4b789a819556fb86da7226d7d1b037b49" +checksum = "edb0306fbad0ab5428b0ca674a23893db909a98582969c9b537be4ced78c505d" dependencies = [ - "bitflags", + "bitflags 1.3.2", "futures-channel", "futures-core", "futures-executor", @@ -259,9 +386,9 @@ dependencies = [ [[package]] name = "glib-macros" -version = "0.15.6" +version = "0.15.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41bfd8d227dead0829ac142454e97531b93f576d0805d779c42bfd799c65c572" +checksum = "10c6ae9f6fa26f4fb2ac16b528d138d971ead56141de489f8111e259b9df3c4a" dependencies = [ "anyhow", "heck", @@ -269,14 +396,14 @@ dependencies = [ "proc-macro-error", "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] name = "glib-sys" -version = "0.15.6" +version = "0.15.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4366377bd56697de8aaee24e673c575d2694d72e7756324ded2b0428829a7b8" +checksum = "ef4b192f8e65e9cf76cbf4ea71fa8e3be4a0e18ffe3d68b8da6836974cc5bad4" dependencies = [ "libc", "system-deps", @@ -284,9 +411,9 @@ dependencies = [ [[package]] name = "gobject-sys" -version = "0.15.5" +version = "0.15.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df6859463843c20cf3837e3a9069b6ab2051aeeadf4c899d33344f4aea83189a" +checksum = "0d57ce44246becd17153bd035ab4d32cfee096a657fc01f2231c9278378d1e0a" dependencies = [ "glib-sys", "libc", @@ -294,20 +421,46 @@ dependencies = [ ] [[package]] -name = "heck" -version = "0.4.0" +name = "hashbrown" +version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" + +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" [[package]] name = "hermit-abi" -version = "0.1.19" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" +checksum = "ee512640fe35acbfb4bb779db6f0d80704c2cacfa2e39b601ef3e3f47d1ae4c7" dependencies = [ "libc", ] +[[package]] +name = "indexmap" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" +dependencies = [ + "autocfg", + "hashbrown", +] + +[[package]] +name = "inout" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5" +dependencies = [ + "block-padding", + "generic-array", +] + [[package]] name = "keytar" version = "0.1.6" @@ -331,15 +484,15 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.119" +version = "0.2.141" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bf2e165bb3457c8e098ea76f3e3bc9db55f87aa90d52d0e6be741470916aaa4" +checksum = "3304a64d199bb964be99741b7a14d26972741915b3649639149b2479bb46f4b5" [[package]] name = "libloading" -version = "0.7.3" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "efbc0f03f9a775e9f6aed295c6a1ba2253c5757a9e03d55c6caa46a681abcddd" +checksum = "b67380fd3b2fbe7527a606e18729d21c6f3951633d0500574c4dc22d2d638b9f" dependencies = [ "cfg-if", "winapi", @@ -347,11 +500,11 @@ dependencies = [ [[package]] name = "libsecret" -version = "0.1.4" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4af5a2342942fa42d706a424e9f9914287fb8317132750fd73a241140ac38c1" +checksum = "a15ed5dab9789d9c4caedf88457214d822cfbad5562b63f6c8f61597501f6b8b" dependencies = [ - "bitflags", + "bitflags 1.3.2", "gio", "glib", "libc", @@ -361,9 +514,9 @@ dependencies = [ [[package]] name = "libsecret-sys" -version = "0.1.3" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "287d2a0fcd95e4d7b0ac6fc9f802691a790d7e522138713b0cacebc4e63cab91" +checksum = "77abfe0fd35152462d1736d11f5c1ba381f74535171b2138fed1604bcb803d2b" dependencies = [ "gio-sys", "glib-sys", @@ -375,70 +528,61 @@ dependencies = [ [[package]] name = "link-cplusplus" -version = "1.0.6" +version = "1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8cae2cd7ba2f3f63938b9c724475dfb7b9861b545a90324476324ed21dbc8c8" +checksum = "ecd207c9c713c34f95a097a5b029ac2ce6010530c7b49d7fea24d977dede04f5" dependencies = [ "cc", ] [[package]] name = "lock_api" -version = "0.4.6" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88943dd7ef4a2e5a4bfa2753aaab3013e34ce2533d1996fb18ef591e315e2b3b" +checksum = "435011366fe56583b16cf956f9df0095b405b82d76425bc8981c0e22e60ec4df" dependencies = [ + "autocfg", "scopeguard", ] [[package]] name = "log" -version = "0.4.14" +version = "0.4.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710" +checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" dependencies = [ "cfg-if", ] [[package]] name = "memchr" -version = "2.4.1" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a" +checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" [[package]] name = "mio" -version = "0.8.0" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba272f85fa0b41fc91872be579b3bbe0f56b792aa361a380eb669469f68dafb2" +checksum = "5b9d9a46eff5b4ff64b45a9e316a6d1e0bc719ef429cbec4dc630684212bfdf9" dependencies = [ "libc", "log", - "miow", - "ntapi", - "winapi", -] - -[[package]] -name = "miow" -version = "0.3.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9f1c5b025cda876f66ef43a113f91ebc9f4ccef34843000e0adf6ebbab84e21" -dependencies = [ - "winapi", + "wasi", + "windows-sys", ] [[package]] name = "napi" -version = "2.9.1" +version = "2.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "743fece4c26c5132f8559080145fde9ba88700c0f1aa30a1ab3e057ab105814d" +checksum = "556470a21074b55be8adee5f27ca04389cfdaca323a28b4b0e9c15466de94731" dependencies = [ - "bitflags", + "bitflags 2.1.0", "ctor", + "napi-derive", "napi-sys", "once_cell", - "thread_local", "tokio", ] @@ -450,54 +594,46 @@ checksum = "882a73d9ef23e8dc2ebbffb6a6ae2ef467c0f18ac10711e4cc59c5485d41df0e" [[package]] name = "napi-derive" -version = "2.9.1" +version = "2.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39f3d8b02ef355898ea98f69082d9a183c8701c836942c2daf3e92364e88a0fa" +checksum = "af2ac63101a19228b0881694cac07468d642fd10e4f943a9c9feebeebf1a4787" dependencies = [ "convert_case", "napi-derive-backend", "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] name = "napi-derive-backend" -version = "1.0.38" +version = "1.0.49" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c35640513eb442fcbd1653a1c112fb6b2cc12b54d82f9c141f5859c721cab36" +checksum = "0e32b5bc4d803e40b783b0aa3fe488eac8711cfaa4c5c9915293dfd3d0b99925" dependencies = [ "convert_case", "once_cell", "proc-macro2", "quote", "regex", - "syn", + "semver", + "syn 1.0.109", ] [[package]] name = "napi-sys" -version = "2.2.2" +version = "2.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "529671ebfae679f2ce9630b62dd53c72c56b3eb8b2c852e7e2fa91704ff93d67" +checksum = "166b5ef52a3ab5575047a9fe8d4a030cdd0f63c96f071cd6907674453b07bae3" dependencies = [ "libloading", ] -[[package]] -name = "ntapi" -version = "0.3.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c28774a7fd2fbb4f0babd8237ce554b73af68021b5f695a3cebd6c59bac0980f" -dependencies = [ - "winapi", -] - [[package]] name = "num_cpus" -version = "1.13.1" +version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19e64526ebdee182341572e50e9ad03965aa510cd94427a4549448f285e957a1" +checksum = "0fac9e2da13b5eb447a6ce3d392f23a29d8694bff781bf03a16cd9ac8697593b" dependencies = [ "hermit-abi", "libc", @@ -505,15 +641,15 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.9.0" +version = "1.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da32515d9f6e6e489d7bc9d84c71b060db7247dc035bbe44eac88cf87486d8d5" +checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3" [[package]] name = "parking_lot" -version = "0.12.0" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87f5ec2493a61ac0506c0f4199f99070cbe83857b0337006a30f3e6719b8ef58" +checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" dependencies = [ "lock_api", "parking_lot_core", @@ -521,9 +657,9 @@ dependencies = [ [[package]] name = "parking_lot_core" -version = "0.9.3" +version = "0.9.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09a279cbf25cb0757810394fbc1e359949b59e348145c643a939a525692e6929" +checksum = "9069cbb9f99e3a5083476ccb29ceb1de18b9118cafa53e90c9551235de2b9521" dependencies = [ "cfg-if", "libc", @@ -534,9 +670,9 @@ dependencies = [ [[package]] name = "pin-project-lite" -version = "0.2.8" +version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e280fbe77cc62c91527259e9442153f4688736748d24660126286329742b4c6c" +checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" [[package]] name = "pin-utils" @@ -546,18 +682,24 @@ checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] name = "pkg-config" -version = "0.3.24" +version = "0.3.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58893f751c9b0412871a09abd62ecd2a00298c6c83befa223ef98c52aef40cbe" +checksum = "6ac9a59f73473f1b8d852421e59e64809f025994837ef743615c6d0c5b305160" + +[[package]] +name = "ppv-lite86" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" [[package]] name = "proc-macro-crate" -version = "1.1.3" +version = "1.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e17d47ce914bf4de440332250b0edd23ce48c005f59fab39d3335866b114f11a" +checksum = "7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919" dependencies = [ - "thiserror", - "toml", + "once_cell", + "toml_edit", ] [[package]] @@ -569,7 +711,7 @@ dependencies = [ "proc-macro-error-attr", "proc-macro2", "quote", - "syn", + "syn 1.0.109", "version_check", ] @@ -586,36 +728,66 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.36" +version = "1.0.56" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7342d5883fbccae1cc37a2353b09c87c9b0f3afd73f5fb9bba687a1f733b029" +checksum = "2b63bdb0cd06f1f4dedf69b254734f9b45af66e4a031e42a7480257d9898b435" dependencies = [ - "unicode-xid", + "unicode-ident", ] [[package]] name = "quote" -version = "1.0.15" +version = "1.0.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "864d3e96a899863136fc6e99f3d7cae289dafe43bf2c5ac19b70df7210c0a145" +checksum = "4424af4bf778aae2051a77b60283332f386554255d722233d09fbfc7e30da2fc" dependencies = [ "proc-macro2", ] [[package]] -name = "redox_syscall" -version = "0.2.10" +name = "rand" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8383f39639269cde97d255a32bdb68c047337295414940c68bdd30c2e13203ff" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ - "bitflags", + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + +[[package]] +name = "redox_syscall" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" +dependencies = [ + "bitflags 1.3.2", ] [[package]] name = "regex" -version = "1.5.4" +version = "1.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d07a8629359eb56f1e2fb1652bb04212c072a87ba68546a04065d525673ac461" +checksum = "8b1f693b24f6ac912f4893ef08244d70b6067480d2f1a46e950c9691e6749d1d" dependencies = [ "aho-corasick", "memchr", @@ -624,9 +796,18 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.6.25" +version = "0.6.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b" +checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" + +[[package]] +name = "retry" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9166d72162de3575f950507683fac47e30f6f2c3836b71b7fbc61aa517c9c5f4" +dependencies = [ + "rand", +] [[package]] name = "scopeguard" @@ -636,17 +817,17 @@ checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" [[package]] name = "scratch" -version = "1.0.1" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96311ef4a16462c757bb6a39152c40f58f31cd2602a40fceb937e2bc34e6cbab" +checksum = "1792db035ce95be60c3f8853017b3999209281c24e2ba5bc8e59bf97a0c590c1" [[package]] name = "security-framework" -version = "2.6.1" +version = "2.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dc14f172faf8a0194a3aded622712b0de276821addc574fa54fc0a1167e10dc" +checksum = "a332be01508d814fed64bf28f798a146d73792121129962fdf335bb3c49a4254" dependencies = [ - "bitflags", + "bitflags 1.3.2", "core-foundation", "core-foundation-sys", "libc", @@ -655,46 +836,75 @@ dependencies = [ [[package]] name = "security-framework-sys" -version = "2.6.1" +version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0160a13a177a45bfb43ce71c01580998474f556ad854dcbca936dd2841a5c556" +checksum = "31c9bb296072e961fcbd8853511dd39c2d8be2deb1e17c6860b1d30732b323b4" dependencies = [ "core-foundation-sys", "libc", ] [[package]] -name = "serde" -version = "1.0.136" +name = "semver" +version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce31e24b01e1e524df96f1c2fdd054405f8d7376249a5110886fb4b658484789" +checksum = "bebd363326d05ec3e2f532ab7660680f3b02130d780c299bca73469d521bc0ed" + +[[package]] +name = "serde" +version = "1.0.160" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb2f3770c8bce3bcda7e149193a069a0f4365bda1fa5cd88e03bca26afc1216c" + +[[package]] +name = "serde_spanned" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0efd8caf556a6cebd3b285caf480045fcc1ac04f6bd786b09a6f11af30c4fcf4" +dependencies = [ + "serde", +] + +[[package]] +name = "sha2" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82e6b795fe2e3b1e845bafcb27aa35405c4d47cdfc92af5fc8d3002f76cebdc0" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] [[package]] name = "signal-hook-registry" -version = "1.4.0" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e51e73328dc4ac0c7ccbda3a494dfa03df1de2f46018127f60c693f2648455b0" +checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1" dependencies = [ "libc", ] [[package]] name = "slab" -version = "0.4.5" +version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9def91fd1e018fe007022791f865d0ccc9b3a0d5001e01aabb8b40e46000afb5" +checksum = "6528351c9bc8ab22353f9d776db39a20288e8d6c37ef8cfe3317cf875eecfc2d" +dependencies = [ + "autocfg", +] [[package]] name = "smallvec" -version = "1.8.0" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2dd574626839106c320a323308629dcb1acfc96e32a8cba364ddc61ac23ee83" +checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0" [[package]] name = "socket2" -version = "0.4.4" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "66d72b759436ae32898a2af0a14218dbf55efde3feeb170eb623637db85ee1e0" +checksum = "64a4a911eed85daf18834cfaa86a79b7d266ff93ff5ba14005426219480ed662" dependencies = [ "libc", "winapi", @@ -702,20 +912,31 @@ dependencies = [ [[package]] name = "syn" -version = "1.0.86" +version = "1.0.109" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a65b3f4ffa0092e9887669db0eae07941f023991ab58ea44da8fe8e2d511c6b" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" dependencies = [ "proc-macro2", "quote", - "unicode-xid", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcf316d5356ed6847742d036f8a39c3b8435cac10bd528a4bd461928a6ab34d5" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", ] [[package]] name = "system-deps" -version = "6.0.2" +version = "6.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1a45a1c4c9015217e12347f2a411b57ce2c4fc543913b14b6fe40483328e709" +checksum = "555fc8147af6256f3931a36bb83ad0023240ce9cf2b319dec8236fd1f220b05f" dependencies = [ "cfg-expr", "heck", @@ -726,99 +947,126 @@ dependencies = [ [[package]] name = "termcolor" -version = "1.1.3" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755" +checksum = "be55cf8942feac5c765c2c993422806843c9a9a45d4d5c407ad6dd2ea95eb9b6" dependencies = [ "winapi-util", ] [[package]] name = "thiserror" -version = "1.0.30" +version = "1.0.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "854babe52e4df1653706b98fcfc05843010039b406875930a70e4d9644e5c417" +checksum = "978c9a314bd8dc99be594bc3c175faaa9794be04a5a5e153caba6915336cebac" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.30" +version = "1.0.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa32fd3f627f367fe16f893e2597ae3c05020f8bba2666a4e6ea73d377e5714b" +checksum = "f9456a42c5b0d803c8cd86e73dd7cc9edd429499f37a3550d286d5e86720569f" dependencies = [ "proc-macro2", "quote", - "syn", -] - -[[package]] -name = "thread_local" -version = "1.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5516c27b78311c50bf42c071425c560ac799b11c30b31f87e3081965fe5e0180" -dependencies = [ - "once_cell", + "syn 2.0.14", ] [[package]] name = "tokio" -version = "1.17.0" +version = "1.27.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2af73ac49756f3f7c01172e34a23e5d0216f6c32333757c2c61feb2bbff5a5ee" +checksum = "d0de47a4eecbe11f498978a9b29d792f0d2692d1dd003650c24c76510e3bc001" dependencies = [ + "autocfg", "bytes", "libc", - "memchr", "mio", "num_cpus", - "once_cell", "parking_lot", "pin-project-lite", "signal-hook-registry", "socket2", "tokio-macros", - "winapi", + "windows-sys", ] [[package]] name = "tokio-macros" -version = "1.7.0" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b557f72f448c511a979e2564e55d74e6c4432fc96ff4f6241bc6bded342643b7" +checksum = "61a573bdc87985e9d6ddeed1b3d864e8a302c847e40d647746df2f1de209d1ce" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.14", ] [[package]] name = "toml" -version = "0.5.8" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a31142970826733df8241ef35dc040ef98c679ab14d7c3e54d827099b3acecaa" +checksum = "b403acf6f2bb0859c93c7f0d967cb4a75a7ac552100f9322faf64dc047669b21" +dependencies = [ + "serde", + "serde_spanned", + "toml_datetime", + "toml_edit", +] + +[[package]] +name = "toml_datetime" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ab8ed2edee10b50132aed5f331333428b011c99402b5a534154ed15746f9622" dependencies = [ "serde", ] [[package]] -name = "unicode-width" -version = "0.1.9" +name = "toml_edit" +version = "0.19.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ed742d4ea2bd1176e236172c8429aaf54486e7ac098db29ffe6529e0ce50973" +checksum = "239410c8609e8125456927e6707163a3b1fdb40561e4b803bc041f466ccfdc13" +dependencies = [ + "indexmap", + "serde", + "serde_spanned", + "toml_datetime", + "winnow", +] [[package]] -name = "unicode-xid" -version = "0.2.2" +name = "typenum" +version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" +checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba" + +[[package]] +name = "unicode-ident" +version = "1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5464a87b239f13a63a501f2701565754bae92d243d4bb7eb12f6d57d2269bf4" + +[[package]] +name = "unicode-segmentation" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36" + +[[package]] +name = "unicode-width" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b" [[package]] name = "version-compare" -version = "0.1.0" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe88247b92c1df6b6de80ddc290f3976dbdf2f5f5d3fd049a9fb598c6dd5ca73" +checksum = "579a42fc0b8e0c63b76519a339be31bed574929511fa53c1a3acae26eb258f29" [[package]] name = "version_check" @@ -826,6 +1074,12 @@ version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + [[package]] name = "widestring" version = "0.5.1" @@ -865,86 +1119,141 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "windows" -version = "0.39.0" +version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1c4bd0a50ac6020f65184721f758dba47bb9fbc2133df715ec74a237b26794a" +checksum = "e686886bc078bc1b0b600cac0147aadb815089b6e4da64016cbd754b6342700f" dependencies = [ - "windows_aarch64_msvc 0.39.0", - "windows_i686_gnu 0.39.0", - "windows_i686_msvc 0.39.0", - "windows_x86_64_gnu 0.39.0", - "windows_x86_64_msvc 0.39.0", + "windows-targets 0.48.0", ] [[package]] name = "windows-sys" -version = "0.36.1" +version = "0.45.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea04155a16a59f9eab786fe12a4a450e75cdb175f9e0d80da1e17db09f55b8d2" +checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" dependencies = [ - "windows_aarch64_msvc 0.36.1", - "windows_i686_gnu 0.36.1", - "windows_i686_msvc 0.36.1", - "windows_x86_64_gnu 0.36.1", - "windows_x86_64_msvc 0.36.1", + "windows-targets 0.42.2", ] [[package]] -name = "windows_aarch64_msvc" -version = "0.36.1" +name = "windows-targets" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9bb8c3fd39ade2d67e9874ac4f3db21f0d710bee00fe7cab16949ec184eeaa47" +checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" +dependencies = [ + "windows_aarch64_gnullvm 0.42.2", + "windows_aarch64_msvc 0.42.2", + "windows_i686_gnu 0.42.2", + "windows_i686_msvc 0.42.2", + "windows_x86_64_gnu 0.42.2", + "windows_x86_64_gnullvm 0.42.2", + "windows_x86_64_msvc 0.42.2", +] + +[[package]] +name = "windows-targets" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b1eb6f0cd7c80c79759c929114ef071b87354ce476d9d94271031c0497adfd5" +dependencies = [ + "windows_aarch64_gnullvm 0.48.0", + "windows_aarch64_msvc 0.48.0", + "windows_i686_gnu 0.48.0", + "windows_i686_msvc 0.48.0", + "windows_x86_64_gnu 0.48.0", + "windows_x86_64_gnullvm 0.48.0", + "windows_x86_64_msvc 0.48.0", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc" [[package]] name = "windows_aarch64_msvc" -version = "0.39.0" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec7711666096bd4096ffa835238905bb33fb87267910e154b18b44eaabb340f2" +checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3" [[package]] name = "windows_i686_gnu" -version = "0.36.1" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "180e6ccf01daf4c426b846dfc66db1fc518f074baa793aa7d9b9aaeffad6a3b6" +checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" [[package]] name = "windows_i686_gnu" -version = "0.39.0" +version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "763fc57100a5f7042e3057e7e8d9bdd7860d330070251a73d003563a3bb49e1b" +checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241" [[package]] name = "windows_i686_msvc" -version = "0.36.1" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2e7917148b2812d1eeafaeb22a97e4813dfa60a3f8f78ebe204bcc88f12f024" +checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" [[package]] name = "windows_i686_msvc" -version = "0.39.0" +version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7bc7cbfe58828921e10a9f446fcaaf649204dcfe6c1ddd712c5eebae6bda1106" +checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00" [[package]] name = "windows_x86_64_gnu" -version = "0.36.1" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4dcd171b8776c41b97521e5da127a2d86ad280114807d0b2ab1e462bc764d9e1" +checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" [[package]] name = "windows_x86_64_gnu" -version = "0.39.0" +version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6868c165637d653ae1e8dc4d82c25d4f97dd6605eaa8d784b5c6e0ab2a252b65" +checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953" [[package]] name = "windows_x86_64_msvc" -version = "0.36.1" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680" +checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" [[package]] name = "windows_x86_64_msvc" -version = "0.39.0" +version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e4d40883ae9cae962787ca76ba76390ffa29214667a111db9e0a1ad8377e809" +checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" + +[[package]] +name = "winnow" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae8970b36c66498d8ff1d66685dc86b91b29db0c7739899012f63a63814b4b28" +dependencies = [ + "memchr", +] diff --git a/apps/desktop/desktop_native/Cargo.toml b/apps/desktop/desktop_native/Cargo.toml index 1923e46d41..60b330ea75 100644 --- a/apps/desktop/desktop_native/Cargo.toml +++ b/apps/desktop/desktop_native/Cargo.toml @@ -13,20 +13,29 @@ default=[] manual_test=[] [dependencies] +aes = "0.8.2" anyhow = "1.0" +base64 = "0.21.0" +cbc = { version = "0.1.2", features = ["alloc"] } napi = {version = "2.9.1", features = ["async"]} napi-derive = "2.9.1" +rand = "0.8.5" +retry = "2.0.0" scopeguard = "1.1.0" +sha2 = "0.10.6" +thiserror = "1.0.38" tokio = {version = "1.17.0", features = ["full"]} +typenum = "1.16.0" [build-dependencies] napi-build = "2.0.1" [target.'cfg(windows)'.dependencies] widestring = "0.5.1" -windows = {version = "0.39.0", features = [ +windows = {version = "0.48.0", features = [ "Foundation", "Security_Credentials_UI", + "Security_Cryptography", "Storage_Streams", "Win32_Foundation", "Win32_Security_Credentials", diff --git a/apps/desktop/desktop_native/index.d.ts b/apps/desktop/desktop_native/index.d.ts index 023c7eccac..573e2e4930 100644 --- a/apps/desktop/desktop_native/index.d.ts +++ b/apps/desktop/desktop_native/index.d.ts @@ -16,4 +16,24 @@ export namespace passwords { export namespace biometrics { export function prompt(hwnd: Buffer, message: string): Promise export function available(): Promise + export function setBiometricSecret(service: string, account: string, secret: string, keyMaterial: KeyMaterial | undefined | null, ivB64: string): Promise + export function getBiometricSecret(service: string, account: string, keyMaterial?: KeyMaterial | undefined | null): Promise + /** + * Derives key material from biometric data. Returns a string encoded with a + * base64 encoded key and the base64 encoded challenge used to create it + * separated by a `|` character. + * + * If the iv is provided, it will be used as the challenge. Otherwise a random challenge will be generated. + * + * `format!("|")` + */ + export function deriveKeyMaterial(iv?: string | undefined | null): Promise + export interface KeyMaterial { + osKeyPartB64: string + clientKeyPartB64?: string + } + export interface OsDerivedKey { + keyB64: string + ivB64: string + } } diff --git a/apps/desktop/desktop_native/src/biometric/macos.rs b/apps/desktop/desktop_native/src/biometric/macos.rs index 35539dae26..9f0ecf83de 100644 --- a/apps/desktop/desktop_native/src/biometric/macos.rs +++ b/apps/desktop/desktop_native/src/biometric/macos.rs @@ -1,9 +1,38 @@ use anyhow::{bail, Result}; -pub fn prompt(_hwnd: Vec, _message: String) -> Result { - bail!("platform not supported"); -} +use crate::biometrics::{KeyMaterial, OsDerivedKey}; -pub fn available() -> Result { - bail!("platform not supported"); +/// The MacOS implementation of the biometric trait. +pub struct Biometric {} + +impl super::BiometricTrait for Biometric { + fn prompt(_hwnd: Vec, _message: String) -> Result { + bail!("platform not supported"); + } + + fn available() -> Result { + bail!("platform not supported"); + } + + fn derive_key_material(_iv_str: Option<&str>) -> Result { + bail!("platform not supported"); + } + + fn get_biometric_secret( + _service: &str, + _account: &str, + _key_material: Option, + ) -> Result { + bail!("platform not supported"); + } + + fn set_biometric_secret( + _service: &str, + _account: &str, + _secret: &str, + _key_material: Option, + _iv_b64: &str, + ) -> Result { + bail!("platform not supported"); + } } diff --git a/apps/desktop/desktop_native/src/biometric/mod.rs b/apps/desktop/desktop_native/src/biometric/mod.rs index 5ad1403f44..0280647e8e 100644 --- a/apps/desktop/desktop_native/src/biometric/mod.rs +++ b/apps/desktop/desktop_native/src/biometric/mod.rs @@ -1,5 +1,28 @@ +use anyhow::Result; + #[cfg_attr(target_os = "linux", path = "unix.rs")] #[cfg_attr(target_os = "windows", path = "windows.rs")] #[cfg_attr(target_os = "macos", path = "macos.rs")] mod biometric; -pub use biometric::*; + +pub use biometric::Biometric; + +use crate::biometrics::{KeyMaterial, OsDerivedKey}; + +pub trait BiometricTrait { + fn prompt(hwnd: Vec, message: String) -> Result; + fn available() -> Result; + fn derive_key_material(secret: Option<&str>) -> Result; + fn set_biometric_secret( + service: &str, + account: &str, + secret: &str, + key_material: Option, + iv_b64: &str, + ) -> Result; + fn get_biometric_secret( + service: &str, + account: &str, + key_material: Option, + ) -> Result; +} diff --git a/apps/desktop/desktop_native/src/biometric/unix.rs b/apps/desktop/desktop_native/src/biometric/unix.rs index 35539dae26..8b9d1a84bb 100644 --- a/apps/desktop/desktop_native/src/biometric/unix.rs +++ b/apps/desktop/desktop_native/src/biometric/unix.rs @@ -1,9 +1,38 @@ use anyhow::{bail, Result}; -pub fn prompt(_hwnd: Vec, _message: String) -> Result { - bail!("platform not supported"); -} +use crate::biometrics::{KeyMaterial, OsDerivedKey}; -pub fn available() -> Result { - bail!("platform not supported"); +/// The Unix implementation of the biometric trait. +pub struct Biometric {} + +impl super::BiometricTrait for Biometric { + fn prompt(_hwnd: Vec, _message: String) -> Result { + bail!("platform not supported"); + } + + fn available() -> Result { + bail!("platform not supported"); + } + + fn derive_key_material(_iv_str: Option<&str>) -> Result { + bail!("platform not supported"); + } + + fn get_biometric_secret( + _service: &str, + _account: &str, + _key_material: Option, + ) -> Result { + bail!("platform not supported"); + } + + fn set_biometric_secret( + _service: &str, + _account: &str, + _secret: &str, + _key_material: Option, + _iv_b64: &str, + ) -> Result { + bail!("platform not supported"); + } } diff --git a/apps/desktop/desktop_native/src/biometric/windows.rs b/apps/desktop/desktop_native/src/biometric/windows.rs index da7f5fae20..0ed39b98e0 100644 --- a/apps/desktop/desktop_native/src/biometric/windows.rs +++ b/apps/desktop/desktop_native/src/biometric/windows.rs @@ -1,8 +1,21 @@ -use anyhow::Result; +use std::str::FromStr; + +use aes::cipher::generic_array::GenericArray; +use anyhow::{anyhow, Result}; +use base64::{engine::general_purpose::STANDARD as base64_engine, Engine}; +use rand::RngCore; +use retry::delay::Fixed; +use sha2::{Digest, Sha256}; use windows::{ + h, core::{factory, HSTRING}, Foundation::IAsyncOperation, - Security::Credentials::UI::*, + Security::{ + Credentials::{ + KeyCredentialCreationOption, KeyCredentialManager, KeyCredentialStatus, UI::*, + }, + Cryptography::CryptographicBuffer, + }, Win32::{ Foundation::HWND, System::WinRT::IUserConsentVerifierInterop, @@ -11,40 +24,195 @@ use windows::{ keybd_event, GetAsyncKeyState, SetFocus, KEYEVENTF_EXTENDEDKEY, KEYEVENTF_KEYUP, VK_MENU, }, - WindowsAndMessaging::SetForegroundWindow, + WindowsAndMessaging::{FindWindowA, SetForegroundWindow}, }, }, }; -pub fn prompt(hwnd: Vec, message: String) -> Result { - let h = isize::from_le_bytes(hwnd.clone().try_into().unwrap()); - let window = HWND(h); +use crate::{ + biometrics::{KeyMaterial, OsDerivedKey}, + crypto::{self, CipherString}, +}; - // The Windows Hello prompt is displayed inside the application window. For best result we - // should set the window to the foreground and focus it. - set_focus(window); +/// The Windows OS implementation of the biometric trait. +pub struct Biometric {} - let interop = factory::()?; - let operation: IAsyncOperation = - unsafe { interop.RequestVerificationForWindowAsync(window, &HSTRING::from(message))? }; - let result = operation.get()?; +impl super::BiometricTrait for Biometric { + fn prompt(hwnd: Vec, message: String) -> Result { + let h = isize::from_le_bytes(hwnd.clone().try_into().unwrap()); + let window = HWND(h); - match result { - UserConsentVerificationResult::Verified => Ok(true), - _ => Ok(false), + // The Windows Hello prompt is displayed inside the application window. For best result we + // should set the window to the foreground and focus it. + set_focus(window); + + let interop = factory::()?; + let operation: IAsyncOperation = + unsafe { interop.RequestVerificationForWindowAsync(window, &HSTRING::from(message))? }; + let result = operation.get()?; + + match result { + UserConsentVerificationResult::Verified => Ok(true), + _ => Ok(false), + } + } + + fn available() -> Result { + let ucv_available = UserConsentVerifier::CheckAvailabilityAsync()?.get()?; + + match ucv_available { + UserConsentVerifierAvailability::Available => Ok(true), + UserConsentVerifierAvailability::DeviceBusy => Ok(true), // TODO: Look into removing this and making the check more ad-hoc + _ => Ok(false), + } + } + + /// Derive the symmetric encryption key from the Windows Hello signature. + /// + /// This works by signing a static challenge string with Windows Hello protected key store. The + /// signed challenge is then hashed using SHA-256 and used as the symmetric encryption key for the + /// Windows Hello protected keys. + /// + /// Windows will only sign the challenge if the user has successfully authenticated with Windows, + /// ensuring user presence. + fn derive_key_material(challenge_str: Option<&str>) -> Result { + let challenge: [u8; 16] = match challenge_str { + Some(challenge_str) => base64_engine + .decode(challenge_str)? + .try_into() + .map_err(|e: Vec<_>| anyhow!("Expect length {}, got {}", 16, e.len()))?, + None => random_challenge(), + }; + let bitwarden = h!("Bitwarden"); + + let result = KeyCredentialManager::RequestCreateAsync( + &bitwarden, + KeyCredentialCreationOption::FailIfExists, + )? + .get()?; + + let result = match result.Status()? { + KeyCredentialStatus::CredentialAlreadyExists => { + KeyCredentialManager::OpenAsync(&bitwarden)?.get()? + } + KeyCredentialStatus::Success => result, + _ => return Err(anyhow!("Failed to create key credential")), + }; + + let challenge_buffer = CryptographicBuffer::CreateFromByteArray(&challenge)?; + let async_operation = result.Credential()?.RequestSignAsync(&challenge_buffer)?; + focus_security_prompt()?; + let signature = async_operation.get()?; + + if signature.Status()? != KeyCredentialStatus::Success { + return Err(anyhow!("Failed to sign data")); + } + + let signature_buffer = signature.Result()?; + let mut signature_value = + windows::core::Array::::with_len(signature_buffer.Length().unwrap() as usize); + CryptographicBuffer::CopyToByteArray(&signature_buffer, &mut signature_value)?; + + let key = Sha256::digest(&*signature_value); + let key_b64 = base64_engine.encode(&key); + let iv_b64 = base64_engine.encode(&challenge); + Ok(OsDerivedKey { key_b64, iv_b64 }) + } + + fn set_biometric_secret( + service: &str, + account: &str, + secret: &str, + key_material: Option, + iv_b64: &str, + ) -> Result { + let key_material = key_material.ok_or(anyhow!( + "Key material is required for Windows Hello protected keys" + ))?; + + let encrypted_secret = encrypt(secret, &key_material, iv_b64)?; + crate::password::set_password(service, account, &encrypted_secret)?; + Ok(encrypted_secret) + } + + fn get_biometric_secret( + service: &str, + account: &str, + key_material: Option, + ) -> Result { + let key_material = key_material.ok_or(anyhow!( + "Key material is required for Windows Hello protected keys" + ))?; + + let encrypted_secret = crate::password::get_password(service, account)?; + match CipherString::from_str(&encrypted_secret) { + Ok(secret) => { + // If the secret is a CipherString, it is encrypted and we need to decrypt it. + let secret = decrypt(&secret, &key_material)?; + return Ok(secret); + } + Err(_) => { + // If the secret is not a CipherString, it is not encrypted and we can return it + // directly. + return Ok(encrypted_secret); + } + } } } -pub fn available() -> Result { - let ucv_available = UserConsentVerifier::CheckAvailabilityAsync()?.get()?; +fn encrypt(secret: &str, key_material: &KeyMaterial, iv_b64: &str) -> Result { + let iv = base64_engine + .decode(iv_b64)? + .try_into() + .map_err(|e: Vec<_>| anyhow!("Expected length {}, got {}", 16, e.len()))?; - match ucv_available { - UserConsentVerifierAvailability::Available => Ok(true), - UserConsentVerifierAvailability::DeviceBusy => Ok(true), // TODO: Look into removing this and making the check more ad-hoc - _ => Ok(false), + let encrypted = crypto::encrypt_aes256(secret.as_bytes(), iv, key_material.derive_key()?)?; + + Ok(encrypted.to_string()) +} + +fn decrypt(secret: &CipherString, key_material: &KeyMaterial) -> Result { + if let CipherString::AesCbc256_B64 { iv, data } = secret { + let decrypted = crypto::decrypt_aes256(&iv, &data, key_material.derive_key()?)?; + + Ok(String::from_utf8(decrypted)?) + } else { + Err(anyhow!("Invalid cipher string")) } } +fn random_challenge() -> [u8; 16] { + let mut challenge = [0u8; 16]; + rand::thread_rng().fill_bytes(&mut challenge); + challenge +} + +/// Searches for a window that looks like a security prompt and set it as focused. +/// +/// Gives up after 1.5 seconds with a delay of 500ms between each try. +fn focus_security_prompt() -> Result<()> { + unsafe fn try_find_and_set_focus( + class_name: windows::core::PCSTR, + ) -> retry::OperationResult<(), ()> { + let hwnd = unsafe { FindWindowA(class_name, None) }; + if hwnd.0 != 0 { + set_focus(hwnd); + return retry::OperationResult::Ok(()); + } + retry::OperationResult::Retry(()) + } + + let class_name = windows::s!("Credential Dialog Xaml Host"); + retry::retry_with_index(Fixed::from_millis(500), |current_try| { + if current_try > 3 { + return retry::OperationResult::Err(()); + } + + unsafe { try_find_and_set_focus(class_name) } + }) + .map_err(|_| anyhow!("Failed to find security prompt")) +} + fn set_focus(window: HWND) { let mut pressed = false; @@ -70,14 +238,49 @@ fn set_focus(window: HWND) { } } +impl KeyMaterial { + fn digest_material(&self) -> String { + match self.client_key_part_b64.as_deref() { + Some(client_key_part_b64) => format!("{}|{}", self.os_key_part_b64, client_key_part_b64), + None => self.os_key_part_b64.clone(), + } + } + + pub fn derive_key(&self) -> Result> { + Ok(Sha256::digest(self.digest_material())) + } +} + #[cfg(test)] mod tests { use super::*; + use crate::biometric::BiometricTrait; + + #[test] + #[cfg(feature = "manual_test")] + fn test_derive_key_material() { + let iv_input = "l9fhDUP/wDJcKwmEzcb/3w=="; + let result = ::derive_key_material(Some(iv_input)).unwrap(); + let key = base64_engine.decode(result.key_b64).unwrap(); + assert_eq!(key.len(), 32); + assert_eq!(result.iv_b64, iv_input) + } + + #[test] + #[cfg(feature = "manual_test")] + fn test_derive_key_material_no_iv() { + let result = ::derive_key_material(None).unwrap(); + let key = base64_engine.decode(result.key_b64).unwrap(); + assert_eq!(key.len(), 32); + let iv = base64_engine.decode(result.iv_b64).unwrap(); + assert_eq!(iv.len(), 16); + } + #[test] #[cfg(feature = "manual_test")] fn test_prompt() { - prompt( + ::prompt( vec![0, 0, 0, 0, 0, 0, 0, 0], String::from("Hello from Rust"), ) @@ -87,6 +290,145 @@ mod tests { #[test] #[cfg(feature = "manual_test")] fn test_available() { - assert!(available().unwrap()) + assert!(::available().unwrap()) + } + + #[test] + fn test_encrypt() { + let key_material = KeyMaterial { + os_key_part_b64: "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=".to_owned(), + client_key_part_b64: Some("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=".to_owned()), + }; + let iv_b64 = "l9fhDUP/wDJcKwmEzcb/3w==".to_owned(); + let secret = encrypt("secret", &key_material, &iv_b64) + .unwrap() + .parse::() + .unwrap(); + + match secret { + CipherString::AesCbc256_B64 { iv, data: _ } => { + assert_eq!(iv_b64, base64_engine.encode(&iv)); + } + _ => panic!("Invalid cipher string"), + } + } + + #[test] + fn test_decrypt() { + let secret = + CipherString::from_str("0.l9fhDUP/wDJcKwmEzcb/3w==|uP4LcqoCCj5FxBDP77NV6Q==").unwrap(); // output from test_encrypt + let key_material = KeyMaterial { + os_key_part_b64: "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=".to_owned(), + client_key_part_b64: Some("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=".to_owned()), + }; + assert_eq!(decrypt(&secret, &key_material).unwrap(), "secret") + } + + #[test] + fn get_biometric_secret_requires_key() { + let result = ::get_biometric_secret("", "", None); + assert!(result.is_err()); + assert_eq!( + result.unwrap_err().to_string(), + "Key material is required for Windows Hello protected keys" + ); + } + + #[test] + fn get_biometric_secret_handles_unencrypted_secret() { + scopeguard::defer! { + crate::password::delete_password("test", "test").unwrap(); + } + let test = "test"; + let secret = "password"; + let key_material = KeyMaterial { + os_key_part_b64: "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=".to_owned(), + client_key_part_b64: Some("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=".to_owned()), + }; + crate::password::set_password(test, test, secret).unwrap(); + let result = + ::get_biometric_secret(test, test, Some(key_material)) + .unwrap(); + assert_eq!(result, secret); + } + + #[test] + fn get_biometric_secret_handles_encrypted_secret() { + scopeguard::defer! { + crate::password::delete_password("test", "test").unwrap(); + } + let test = "test"; + let secret = + CipherString::from_str("0.l9fhDUP/wDJcKwmEzcb/3w==|uP4LcqoCCj5FxBDP77NV6Q==").unwrap(); // output from test_encrypt + let key_material = KeyMaterial { + os_key_part_b64: "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=".to_owned(), + client_key_part_b64: Some("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=".to_owned()), + }; + crate::password::set_password(test, test, &secret.to_string()).unwrap(); + + let result = + ::get_biometric_secret(test, test, Some(key_material)) + .unwrap(); + assert_eq!(result, "secret"); + } + + #[test] + fn set_biometric_secret_requires_key() { + let result = ::set_biometric_secret("", "", "", None, ""); + assert!(result.is_err()); + assert_eq!( + result.unwrap_err().to_string(), + "Key material is required for Windows Hello protected keys" + ); + } + + fn key_material() -> KeyMaterial { + KeyMaterial { + os_key_part_b64: "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=".to_owned(), + client_key_part_b64: Some("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=".to_owned()), + } + } + + #[test] + fn key_material_produces_valid_key() { + let result = key_material().derive_key().unwrap(); + assert_eq!(result.len(), 32); + } + + #[test] + fn key_material_uses_os_part() { + let mut key_material = key_material(); + let result = key_material.derive_key().unwrap(); + key_material.os_key_part_b64 = "BAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=".to_owned(); + let result2 = key_material.derive_key().unwrap(); + assert_ne!(result, result2); + } + + #[test] + fn key_material_uses_client_part() { + let mut key_material = key_material(); + let result = key_material.derive_key().unwrap(); + key_material.client_key_part_b64 = + Some("BAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=".to_owned()); + let result2 = key_material.derive_key().unwrap(); + assert_ne!(result, result2); + } + + #[test] + fn key_material_produces_consistent_os_only_key() { + let mut key_material = key_material(); + key_material.client_key_part_b64 = None; + let result = key_material.derive_key().unwrap(); + assert_eq!(result, [81, 100, 62, 172, 151, 119, 182, 58, 123, 38, 129, 116, 209, 253, 66, 118, 218, 237, 236, 155, 201, 234, 11, 198, 229, 171, 246, 144, 71, 188, 84, 246].into()); + } + + #[test] + fn key_material_produces_unique_os_only_key() { + let mut key_material = key_material(); + key_material.client_key_part_b64 = None; + let result = key_material.derive_key().unwrap(); + key_material.os_key_part_b64 = "BAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=".to_owned(); + let result2 = key_material.derive_key().unwrap(); + assert_ne!(result, result2); } } diff --git a/apps/desktop/desktop_native/src/crypto/cipher_string.rs b/apps/desktop/desktop_native/src/crypto/cipher_string.rs new file mode 100644 index 0000000000..81f734bf0f --- /dev/null +++ b/apps/desktop/desktop_native/src/crypto/cipher_string.rs @@ -0,0 +1,212 @@ +use std::{fmt::Display, str::FromStr}; + +use base64::{engine::general_purpose::STANDARD as base64_engine, Engine}; + +use crate::error::{CSParseError, Error}; + +#[allow(unused, non_camel_case_types)] +pub enum CipherString { + // 0 + AesCbc256_B64 { + iv: [u8; 16], + data: Vec, + }, + // 1 + AesCbc128_HmacSha256_B64 { + iv: [u8; 16], + mac: [u8; 32], + data: Vec, + }, + // 2 + AesCbc256_HmacSha256_B64 { + iv: [u8; 16], + mac: [u8; 32], + data: Vec, + }, + // 3 + Rsa2048_OaepSha256_B64 { + data: Vec, + }, + // 4 + Rsa2048_OaepSha1_B64 { + data: Vec, + }, + // 5 + Rsa2048_OaepSha256_HmacSha256_B64 { + mac: [u8; 32], + data: Vec, + }, + // 6 + Rsa2048_OaepSha1_HmacSha256_B64 { + mac: [u8; 32], + data: Vec, + }, +} + +// We manually implement these to make sure we don't print any sensitive data +impl std::fmt::Debug for CipherString { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("CipherString") + .field("type", &self.enc_type_name()) + .finish() + } +} + +fn invalid_len_error(expected: usize) -> impl Fn(Vec) -> CSParseError { + move |e: Vec<_>| CSParseError::InvalidBase64Length { + expected, + got: e.len(), + } +} + +impl FromStr for CipherString { + type Err = Error; + + fn from_str(s: &str) -> Result { + let (enc_type, data) = s.split_once('.').ok_or(CSParseError::NoType)?; + + let parts: Vec<_> = data.split('|').collect(); + match (enc_type, parts.len()) { + ("0", 2) => { + let iv_str = parts[0]; + let data_str = parts[1]; + + let iv = base64_engine + .decode(iv_str) + .map_err(CSParseError::InvalidBase64)? + .try_into() + .map_err(invalid_len_error(16))?; + + let data = base64_engine + .decode(data_str) + .map_err(CSParseError::InvalidBase64)?; + + Ok(CipherString::AesCbc256_B64 { iv, data }) + } + + ("1" | "2", 3) => { + let iv_str = parts[0]; + let data_str = parts[1]; + let mac_str = parts[2]; + + let iv = base64_engine + .decode(iv_str) + .map_err(CSParseError::InvalidBase64)? + .try_into() + .map_err(invalid_len_error(16))?; + + let mac = base64_engine + .decode(mac_str) + .map_err(CSParseError::InvalidBase64)? + .try_into() + .map_err(invalid_len_error(32))?; + + let data = base64_engine + .decode(data_str) + .map_err(CSParseError::InvalidBase64)?; + + if enc_type == "1" { + Ok(CipherString::AesCbc128_HmacSha256_B64 { iv, mac, data }) + } else { + Ok(CipherString::AesCbc256_HmacSha256_B64 { iv, mac, data }) + } + } + + ("3" | "4", 1) => { + let data = base64_engine + .decode(data) + .map_err(CSParseError::InvalidBase64)?; + if enc_type == "3" { + Ok(CipherString::Rsa2048_OaepSha256_B64 { data }) + } else { + Ok(CipherString::Rsa2048_OaepSha1_B64 { data }) + } + } + ("5" | "6", 2) => { + unimplemented!() + } + + (enc_type, parts) => Err(CSParseError::InvalidType { + enc_type: enc_type.to_string(), + parts, + } + .into()), + } + } +} + +impl Display for CipherString { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}.", self.enc_type())?; + + let mut parts = Vec::<&[u8]>::new(); + + match self { + CipherString::AesCbc256_B64 { iv, data } => { + parts.push(iv); + parts.push(data); + } + CipherString::AesCbc128_HmacSha256_B64 { iv, mac, data } => { + parts.push(iv); + parts.push(data); + parts.push(mac); + } + CipherString::AesCbc256_HmacSha256_B64 { iv, mac, data } => { + parts.push(iv); + parts.push(data); + parts.push(mac); + } + CipherString::Rsa2048_OaepSha256_B64 { data } => { + parts.push(data); + } + CipherString::Rsa2048_OaepSha1_B64 { data } => { + parts.push(data); + } + CipherString::Rsa2048_OaepSha256_HmacSha256_B64 { mac, data } => { + parts.push(data); + parts.push(mac); + } + CipherString::Rsa2048_OaepSha1_HmacSha256_B64 { mac, data } => { + parts.push(data); + parts.push(mac); + } + } + + for i in 0..parts.len() { + if i == parts.len() - 1 { + write!(f, "{}", base64_engine.encode(parts[i]))?; + } else { + write!(f, "{}|", base64_engine.encode(parts[i]))?; + } + } + + Ok(()) + } +} + +impl CipherString { + fn enc_type(&self) -> u8 { + match self { + CipherString::AesCbc256_B64 { .. } => 0, + CipherString::AesCbc128_HmacSha256_B64 { .. } => 1, + CipherString::AesCbc256_HmacSha256_B64 { .. } => 2, + CipherString::Rsa2048_OaepSha256_B64 { .. } => 3, + CipherString::Rsa2048_OaepSha1_B64 { .. } => 4, + CipherString::Rsa2048_OaepSha256_HmacSha256_B64 { .. } => 5, + CipherString::Rsa2048_OaepSha1_HmacSha256_B64 { .. } => 6, + } + } + + fn enc_type_name(&self) -> &str { + match self.enc_type() { + 0 => "AesCbc256_B64", + 1 => "AesCbc128_HmacSha256_B64", + 2 => "AesCbc256_HmacSha256_B64", + 3 => "Rsa2048_OaepSha256_B64", + 4 => "Rsa2048_OaepSha1_B64", + 5 => "Rsa2048_OaepSha256_HmacSha256_B64", + 6 => "Rsa2048_OaepSha1_HmacSha256_B64", + _ => "Unknown", + } + } +} diff --git a/apps/desktop/desktop_native/src/crypto/crypto.rs b/apps/desktop/desktop_native/src/crypto/crypto.rs new file mode 100644 index 0000000000..b1fd4426fa --- /dev/null +++ b/apps/desktop/desktop_native/src/crypto/crypto.rs @@ -0,0 +1,39 @@ +//! Cryptographic primitives used in the SDK + +use aes::cipher::{ + block_padding::Pkcs7, generic_array::GenericArray, typenum::U32, BlockDecryptMut, + BlockEncryptMut, KeyIvInit, +}; + +use crate::error::{CryptoError, Result}; + +use super::CipherString; + +pub fn decrypt_aes256( + iv: &[u8; 16], + data: &Vec, + key: GenericArray, +) -> Result> { + let iv = GenericArray::from_slice(iv); + let mut data = data.clone(); + let decrypted_key_slice = cbc::Decryptor::::new(&key, iv) + .decrypt_padded_mut::(&mut data) + .map_err(|_| CryptoError::KeyDecrypt)?; + + // Data is decrypted in place and returns a subslice of the original Vec, to avoid cloning it, we truncate to the subslice length + let decrypted_len = decrypted_key_slice.len(); + data.truncate(decrypted_len); + + Ok(data) +} + +pub fn encrypt_aes256( + data_dec: &[u8], + iv: [u8; 16], + key: GenericArray, +) -> Result { + let data = cbc::Encryptor::::new(&key, &iv.into()) + .encrypt_padded_vec_mut::(data_dec); + + Ok(CipherString::AesCbc256_B64 { iv, data }) +} diff --git a/apps/desktop/desktop_native/src/crypto/mod.rs b/apps/desktop/desktop_native/src/crypto/mod.rs new file mode 100644 index 0000000000..13ac4b947b --- /dev/null +++ b/apps/desktop/desktop_native/src/crypto/mod.rs @@ -0,0 +1,5 @@ +pub use cipher_string::*; +pub use crypto::*; + +mod cipher_string; +mod crypto; diff --git a/apps/desktop/desktop_native/src/error.rs b/apps/desktop/desktop_native/src/error.rs new file mode 100644 index 0000000000..d3104cb6f4 --- /dev/null +++ b/apps/desktop/desktop_native/src/error.rs @@ -0,0 +1,43 @@ +use std::fmt::Debug; + +use thiserror::Error; + +#[derive(Error, Debug)] +pub enum Error { + #[error("Error parsing CipherString: {0}")] + InvalidCipherString(#[from] CSParseError), + + #[error("Cryptography Error, {0}")] + Crypto(#[from] CryptoError), +} + +#[derive(Debug, Error)] +pub enum CSParseError { + #[error("No type detected, missing '.' separator")] + NoType, + #[error("Invalid type, got {enc_type} with {parts} parts")] + InvalidType { enc_type: String, parts: usize }, + #[error("Error decoding base64: {0}")] + InvalidBase64(#[from] base64::DecodeError), + #[error("Invalid base64 length: expected {expected}, got {got}")] + InvalidBase64Length { expected: usize, got: usize }, +} + +#[derive(Debug, Error)] +pub enum CryptoError { + #[error("Error while decrypting cipher string")] + KeyDecrypt, +} + +// Ensure that the error messages implement Send and Sync +#[cfg(test)] +const _: () = { + fn assert_send() {} + fn assert_sync() {} + fn assert_all() { + assert_send::(); + assert_sync::(); + } +}; + +pub type Result = std::result::Result; diff --git a/apps/desktop/desktop_native/src/lib.rs b/apps/desktop/desktop_native/src/lib.rs index 1906dccb39..fefede4f74 100644 --- a/apps/desktop/desktop_native/src/lib.rs +++ b/apps/desktop/desktop_native/src/lib.rs @@ -2,6 +2,8 @@ extern crate napi_derive; mod biometric; +mod crypto; +mod error; mod password; #[napi] @@ -41,18 +43,67 @@ pub mod passwords { #[napi] pub mod biometrics { + use super::biometric::{Biometric, BiometricTrait}; + // Prompt for biometric confirmation #[napi] pub async fn prompt( hwnd: napi::bindgen_prelude::Buffer, message: String, ) -> napi::Result { - super::biometric::prompt(hwnd.into(), message) - .map_err(|e| napi::Error::from_reason(e.to_string())) + Biometric::prompt(hwnd.into(), message).map_err(|e| napi::Error::from_reason(e.to_string())) } #[napi] pub async fn available() -> napi::Result { - super::biometric::available().map_err(|e| napi::Error::from_reason(e.to_string())) + Biometric::available().map_err(|e| napi::Error::from_reason(e.to_string())) + } + + #[napi] + pub async fn set_biometric_secret( + service: String, + account: String, + secret: String, + key_material: Option, + iv_b64: String, + ) -> napi::Result { + Biometric::set_biometric_secret(&service, &account, &secret, key_material, &iv_b64) + .map_err(|e| napi::Error::from_reason(e.to_string())) + } + + #[napi] + pub async fn get_biometric_secret( + service: String, + account: String, + key_material: Option, + ) -> napi::Result { + let result = Biometric::get_biometric_secret(&service, &account, key_material) + .map_err(|e| napi::Error::from_reason(e.to_string())); + result + } + + /// Derives key material from biometric data. Returns a string encoded with a + /// base64 encoded key and the base64 encoded challenge used to create it + /// separated by a `|` character. + /// + /// If the iv is provided, it will be used as the challenge. Otherwise a random challenge will be generated. + /// + /// `format!("|")` + #[napi] + pub async fn derive_key_material(iv: Option) -> napi::Result { + Biometric::derive_key_material(iv.as_deref()) + .map_err(|e| napi::Error::from_reason(e.to_string())) + } + + #[napi(object)] + pub struct KeyMaterial { + pub os_key_part_b64: String, + pub client_key_part_b64: Option, + } + + #[napi(object)] + pub struct OsDerivedKey { + pub key_b64: String, + pub iv_b64: String, } } diff --git a/apps/desktop/src/app/accounts/settings.component.html b/apps/desktop/src/app/accounts/settings.component.html index e0663efdc6..30deb88cfa 100644 --- a/apps/desktop/src/app/accounts/settings.component.html +++ b/apps/desktop/src/app/accounts/settings.component.html @@ -108,9 +108,12 @@ {{ biometricText | i18n }} + {{ + additionalBiometricSettingsText | i18n + }}
-
+
+
+
+ +
+ {{ + "recommendedForSecurity" | i18n + }} +
-
+