diff --git a/.github/workflows/build-desktop.yml b/.github/workflows/build-desktop.yml index 4667a9371134..7504091b8a65 100644 --- a/.github/workflows/build-desktop.yml +++ b/.github/workflows/build-desktop.yml @@ -187,6 +187,7 @@ jobs: TARGET: musl run: | rustup target add x86_64-unknown-linux-musl + rustup target add aarch64-unknown-linux-musl node build.js cross-platform - name: Build application diff --git a/apps/desktop/desktop_native/Cargo.lock b/apps/desktop/desktop_native/Cargo.lock index d8a7c470eba0..cea80985a5f7 100644 --- a/apps/desktop/desktop_native/Cargo.lock +++ b/apps/desktop/desktop_native/Cargo.lock @@ -26,6 +26,7 @@ dependencies = [ "cfg-if", "cipher", "cpufeatures", + "zeroize", ] [[package]] @@ -137,6 +138,17 @@ dependencies = [ "pin-project-lite", ] +[[package]] +name = "async-net" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b948000fad4873c1c9339d60f2623323a0cfd3816e5181033c6a5cb68b2accf7" +dependencies = [ + "async-io", + "blocking", + "futures-lite", +] + [[package]] name = "async-process" version = "2.3.0" @@ -311,16 +323,6 @@ dependencies = [ "shlex", ] -[[package]] -name = "cfg-expr" -version = "0.15.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d067ad48b8650848b989a59a86c6c36a995d02d2bf778d45c3c5d57bc2718f02" -dependencies = [ - "smallvec", - "target-lexicon", -] - [[package]] name = "cfg-if" version = "1.0.0" @@ -347,6 +349,7 @@ checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" dependencies = [ "crypto-common", "inout", + "zeroize", ] [[package]] @@ -424,6 +427,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" dependencies = [ "generic-array", + "rand_core", "typenum", ] @@ -513,12 +517,11 @@ dependencies = [ "core-foundation", "dirs", "futures", - "gio", "interprocess", "keytar", "libc", - "libsecret", "log", + "oo7", "rand", "retry", "scopeguard", @@ -571,6 +574,7 @@ checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ "block-buffer", "crypto-common", + "subtle", ] [[package]] @@ -848,105 +852,12 @@ version = "0.31.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" -[[package]] -name = "gio" -version = "0.19.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be548be810e45dd31d3bbb89c6210980bb7af9bca3ea1292b5f16b75f8e394a7" -dependencies = [ - "futures-channel", - "futures-core", - "futures-io", - "futures-util", - "gio-sys", - "glib", - "libc", - "pin-project-lite", - "smallvec", - "thiserror", -] - -[[package]] -name = "gio-sys" -version = "0.19.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2cd743ba4714d671ad6b6234e8ab2a13b42304d0e13ab7eba1dcdd78a7d6d4ef" -dependencies = [ - "glib-sys", - "gobject-sys", - "libc", - "system-deps", - "windows-sys 0.52.0", -] - -[[package]] -name = "glib" -version = "0.19.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39650279f135469465018daae0ba53357942a5212137515777d5fdca74984a44" -dependencies = [ - "bitflags", - "futures-channel", - "futures-core", - "futures-executor", - "futures-task", - "futures-util", - "gio-sys", - "glib-macros", - "glib-sys", - "gobject-sys", - "libc", - "memchr", - "smallvec", - "thiserror", -] - -[[package]] -name = "glib-macros" -version = "0.19.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4429b0277a14ae9751350ad9b658b1be0abb5b54faa5bcdf6e74a3372582fad7" -dependencies = [ - "heck", - "proc-macro-crate", - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "glib-sys" -version = "0.19.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c2dc18d3a82b0006d470b13304fbbb3e0a9bd4884cf985a60a7ed733ac2c4a5" -dependencies = [ - "libc", - "system-deps", -] - -[[package]] -name = "gobject-sys" -version = "0.19.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e697e252d6e0416fd1d9e169bda51c0f1c926026c39ca21fbe8b1bb5c3b8b9e" -dependencies = [ - "glib-sys", - "libc", - "system-deps", -] - [[package]] name = "hashbrown" version = "0.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3a9bfc1af68b1726ea47d3d5109de126281def866b33970e10fbab11b5dafab3" -[[package]] -name = "heck" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" - [[package]] name = "hermit-abi" version = "0.3.9" @@ -965,6 +876,24 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" +[[package]] +name = "hkdf" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7" +dependencies = [ + "hmac", +] + +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest", +] + [[package]] name = "indexmap" version = "2.6.0" @@ -1027,6 +956,15 @@ dependencies = [ "pkg-config", ] +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" +dependencies = [ + "spin", +] + [[package]] name = "libc" version = "0.2.159" @@ -1043,6 +981,12 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "libm" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8355be11b20d696c8f18f6cc018c4e372165b1fa8126cef092399c9951984ffa" + [[package]] name = "libredox" version = "0.1.3" @@ -1053,32 +997,6 @@ dependencies = [ "libc", ] -[[package]] -name = "libsecret" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50c6ccddc706a38eca477b4d7857acd6c76c7d6fba5d47b4b2e7d800e5a17194" -dependencies = [ - "gio", - "glib", - "libc", - "libsecret-sys", -] - -[[package]] -name = "libsecret-sys" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a1af48e61f1c8e77e9705296f346e45b637754a92348a79b4c62df84d0654c2" -dependencies = [ - "gio-sys", - "glib-sys", - "gobject-sys", - "libc", - "pkg-config", - "system-deps", -] - [[package]] name = "link-cplusplus" version = "1.0.9" @@ -1110,6 +1028,16 @@ version = "0.4.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" +[[package]] +name = "md-5" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf" +dependencies = [ + "cfg-if", + "digest", +] + [[package]] name = "memchr" version = "2.7.4" @@ -1245,12 +1173,103 @@ dependencies = [ "minimal-lexical", ] +[[package]] +name = "num" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35bd024e8b2ff75562e5f34e7f4905839deb4b22955ef5e73d2fea1b9813cb23" +dependencies = [ + "num-bigint", + "num-complex", + "num-integer", + "num-iter", + "num-rational", + "num-traits", +] + +[[package]] +name = "num-bigint" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" +dependencies = [ + "num-integer", + "num-traits", +] + +[[package]] +name = "num-bigint-dig" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc84195820f291c7697304f3cbdadd1cb7199c0efc917ff5eafd71225c136151" +dependencies = [ + "byteorder", + "lazy_static", + "libm", + "num-integer", + "num-iter", + "num-traits", + "rand", + "serde", + "smallvec", + "zeroize", +] + +[[package]] +name = "num-complex" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495" +dependencies = [ + "num-traits", +] + [[package]] name = "num-conv" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-iter" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-rational" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824" +dependencies = [ + "num-bigint", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + [[package]] name = "num_threads" version = "0.1.7" @@ -1374,6 +1393,39 @@ version = "1.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" +[[package]] +name = "oo7" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fc6ce4692fbfd044ce22ca07dcab1a30fa12432ca2aa5b1294eca50d3332a24" +dependencies = [ + "aes", + "async-fs", + "async-io", + "async-lock", + "async-net", + "blocking", + "cbc", + "cipher", + "digest", + "endi", + "futures-lite", + "futures-util", + "hkdf", + "hmac", + "md-5", + "num", + "num-bigint-dig", + "pbkdf2", + "rand", + "serde", + "sha2", + "subtle", + "zbus", + "zeroize", + "zvariant", +] + [[package]] name = "option-ext" version = "0.2.0" @@ -1429,6 +1481,16 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "pbkdf2" +version = "0.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8ed6a7761f76e3b9f92dfb0a60a6a6477c61024b775147ff0973a02653abaf2" +dependencies = [ + "digest", + "hmac", +] + [[package]] name = "petgraph" version = "0.6.5" @@ -1725,15 +1787,6 @@ dependencies = [ "syn", ] -[[package]] -name = "serde_spanned" -version = "0.6.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87607cb1398ed59d48732e575a4c28a7a8ebf2454b964fe3f224f2afc07909e1" -dependencies = [ - "serde", -] - [[package]] name = "sha1" version = "0.10.6" @@ -1807,12 +1860,24 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" + [[package]] name = "static_assertions" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + [[package]] name = "syn" version = "2.0.87" @@ -1824,25 +1889,6 @@ dependencies = [ "unicode-ident", ] -[[package]] -name = "system-deps" -version = "6.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3e535eb8dded36d55ec13eddacd30dec501792ff23a0b1682c38601b8cf2349" -dependencies = [ - "cfg-expr", - "heck", - "pkg-config", - "toml", - "version-compare", -] - -[[package]] -name = "target-lexicon" -version = "0.12.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1" - [[package]] name = "tempfile" version = "3.13.0" @@ -1958,26 +2004,11 @@ dependencies = [ "tokio", ] -[[package]] -name = "toml" -version = "0.8.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1ed1f98e3fdc28d6d910e6737ae6ab1a93bf1985935a1193e68f93eeb68d24e" -dependencies = [ - "serde", - "serde_spanned", - "toml_datetime", - "toml_edit", -] - [[package]] name = "toml_datetime" version = "0.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" -dependencies = [ - "serde", -] [[package]] name = "toml_edit" @@ -1986,8 +2017,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ae48d6208a266e853d946088ed816055e556cc6028c5e8e2b84d9fa5dd7c7f5" dependencies = [ "indexmap", - "serde", - "serde_spanned", "toml_datetime", "winnow", ] @@ -2071,12 +2100,6 @@ version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" -[[package]] -name = "version-compare" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "852e951cb7832cb45cb1169900d19760cfa39b82bc0ea9c0e5a14ae88411c98b" - [[package]] name = "version_check" version = "0.9.5" @@ -2581,6 +2604,26 @@ dependencies = [ "syn", ] +[[package]] +name = "zeroize" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" +dependencies = [ + "zeroize_derive", +] + +[[package]] +name = "zeroize_derive" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "zvariant" version = "4.2.0" diff --git a/apps/desktop/desktop_native/build.js b/apps/desktop/desktop_native/build.js index f2f012bf0880..817cb7b01a13 100644 --- a/apps/desktop/desktop_native/build.js +++ b/apps/desktop/desktop_native/build.js @@ -47,7 +47,8 @@ switch (process.platform) { default: targets = [ - ['x86_64-unknown-linux-musl', 'x64'] + ['x86_64-unknown-linux-musl', 'x64'], + ['aarch64-unknown-linux-musl', 'arm64'] ]; process.env["PKG_CONFIG_ALLOW_CROSS"] = "1"; diff --git a/apps/desktop/desktop_native/core/Cargo.toml b/apps/desktop/desktop_native/core/Cargo.toml index a56c1b574347..ce24aa33285d 100644 --- a/apps/desktop/desktop_native/core/Cargo.toml +++ b/apps/desktop/desktop_native/core/Cargo.toml @@ -15,8 +15,6 @@ sys = [ "dep:core-foundation", "dep:security-framework", "dep:security-framework-sys", - "dep:gio", - "dep:libsecret", "dep:zbus", "dep:zbus_polkit", ] @@ -66,7 +64,7 @@ security-framework = { version = "=2.11.0", optional = true } security-framework-sys = { version = "=2.11.0", optional = true } [target.'cfg(target_os = "linux")'.dependencies] -gio = { version = "=0.19.5", optional = true } -libsecret = { version = "=0.5.0", optional = true } +oo7 = "0.3.3" + zbus = { version = "=4.4.0", optional = true } zbus_polkit = { version = "=4.0.0", optional = true } diff --git a/apps/desktop/desktop_native/core/src/biometric/macos.rs b/apps/desktop/desktop_native/core/src/biometric/macos.rs index 01ee4519ce69..ec09d566e1f6 100644 --- a/apps/desktop/desktop_native/core/src/biometric/macos.rs +++ b/apps/desktop/desktop_native/core/src/biometric/macos.rs @@ -18,7 +18,7 @@ impl super::BiometricTrait for Biometric { bail!("platform not supported"); } - fn get_biometric_secret( + async fn get_biometric_secret( _service: &str, _account: &str, _key_material: Option, @@ -26,7 +26,7 @@ impl super::BiometricTrait for Biometric { bail!("platform not supported"); } - fn set_biometric_secret( + async fn set_biometric_secret( _service: &str, _account: &str, _secret: &str, diff --git a/apps/desktop/desktop_native/core/src/biometric/mod.rs b/apps/desktop/desktop_native/core/src/biometric/mod.rs index c41ad9dda53c..ffec4d5bb148 100644 --- a/apps/desktop/desktop_native/core/src/biometric/mod.rs +++ b/apps/desktop/desktop_native/core/src/biometric/mod.rs @@ -28,14 +28,16 @@ pub trait BiometricTrait { #[allow(async_fn_in_trait)] async fn available() -> Result; fn derive_key_material(secret: Option<&str>) -> Result; - fn set_biometric_secret( + #[allow(async_fn_in_trait)] + async fn set_biometric_secret( service: &str, account: &str, secret: &str, key_material: Option, iv_b64: &str, ) -> Result; - fn get_biometric_secret( + #[allow(async_fn_in_trait)] + async fn get_biometric_secret( service: &str, account: &str, key_material: Option, diff --git a/apps/desktop/desktop_native/core/src/biometric/unix.rs b/apps/desktop/desktop_native/core/src/biometric/unix.rs index 742b736e8124..7fd8ab23d161 100644 --- a/apps/desktop/desktop_native/core/src/biometric/unix.rs +++ b/apps/desktop/desktop_native/core/src/biometric/unix.rs @@ -71,7 +71,7 @@ impl super::BiometricTrait for Biometric { Ok(OsDerivedKey { key_b64, iv_b64 }) } - fn set_biometric_secret( + async fn set_biometric_secret( service: &str, account: &str, secret: &str, @@ -83,11 +83,11 @@ impl super::BiometricTrait for Biometric { ))?; let encrypted_secret = encrypt(secret, &key_material, iv_b64)?; - crate::password::set_password(service, account, &encrypted_secret)?; + crate::password::set_password(service, account, &encrypted_secret).await?; Ok(encrypted_secret) } - fn get_biometric_secret( + async fn get_biometric_secret( service: &str, account: &str, key_material: Option, @@ -96,7 +96,7 @@ impl super::BiometricTrait for Biometric { "Key material is required for polkit protected keys" ))?; - let encrypted_secret = crate::password::get_password(service, account)?; + let encrypted_secret = crate::password::get_password(service, account).await?; let secret = CipherString::from_str(&encrypted_secret)?; return Ok(decrypt(&secret, &key_material)?); } diff --git a/apps/desktop/desktop_native/core/src/biometric/windows.rs b/apps/desktop/desktop_native/core/src/biometric/windows.rs index c5db9e3277b8..03112e358ce7 100644 --- a/apps/desktop/desktop_native/core/src/biometric/windows.rs +++ b/apps/desktop/desktop_native/core/src/biometric/windows.rs @@ -119,7 +119,7 @@ impl super::BiometricTrait for Biometric { Ok(OsDerivedKey { key_b64, iv_b64 }) } - fn set_biometric_secret( + async fn set_biometric_secret( service: &str, account: &str, secret: &str, @@ -131,11 +131,11 @@ impl super::BiometricTrait for Biometric { ))?; let encrypted_secret = encrypt(secret, &key_material, iv_b64)?; - crate::password::set_password(service, account, &encrypted_secret)?; + crate::password::set_password(service, account, &encrypted_secret).await?; Ok(encrypted_secret) } - fn get_biometric_secret( + async fn get_biometric_secret( service: &str, account: &str, key_material: Option, @@ -144,7 +144,7 @@ impl super::BiometricTrait for Biometric { "Key material is required for Windows Hello protected keys" ))?; - let encrypted_secret = crate::password::get_password(service, account)?; + let encrypted_secret = crate::password::get_password(service, account).await?; match CipherString::from_str(&encrypted_secret) { Ok(secret) => { // If the secret is a CipherString, it is encrypted and we need to decrypt it. @@ -291,9 +291,9 @@ mod tests { assert_eq!(decrypt(&secret, &key_material).unwrap(), "secret") } - #[test] - fn get_biometric_secret_requires_key() { - let result = ::get_biometric_secret("", "", None); + #[tokio::test] + async fn get_biometric_secret_requires_key() { + let result = ::get_biometric_secret("", "", None).await; assert!(result.is_err()); assert_eq!( result.unwrap_err().to_string(), @@ -301,29 +301,25 @@ mod tests { ); } - #[test] - fn get_biometric_secret_handles_unencrypted_secret() { - scopeguard::defer! { - crate::password::delete_password("test", "test").unwrap(); - } + #[tokio::test] + async fn get_biometric_secret_handles_unencrypted_secret() { 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(); + crate::password::set_password(test, test, secret).await.unwrap(); let result = ::get_biometric_secret(test, test, Some(key_material)) + .await .unwrap(); + crate::password::delete_password("test", "test").await.unwrap(); assert_eq!(result, secret); } - #[test] - fn get_biometric_secret_handles_encrypted_secret() { - scopeguard::defer! { - crate::password::delete_password("test", "test").unwrap(); - } + #[tokio::test] + async fn get_biometric_secret_handles_encrypted_secret() { let test = "test"; let secret = CipherString::from_str("0.l9fhDUP/wDJcKwmEzcb/3w==|uP4LcqoCCj5FxBDP77NV6Q==").unwrap(); // output from test_encrypt @@ -331,17 +327,19 @@ mod tests { 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(); + crate::password::set_password(test, test, &secret.to_string()).await.unwrap(); let result = ::get_biometric_secret(test, test, Some(key_material)) + .await .unwrap(); + crate::password::delete_password("test", "test").await.unwrap(); assert_eq!(result, "secret"); } - #[test] - fn set_biometric_secret_requires_key() { - let result = ::set_biometric_secret("", "", "", None, ""); + #[tokio::test] + async fn set_biometric_secret_requires_key() { + let result = ::set_biometric_secret("", "", "", None, "").await; assert!(result.is_err()); assert_eq!( result.unwrap_err().to_string(), diff --git a/apps/desktop/desktop_native/core/src/password/macos.rs b/apps/desktop/desktop_native/core/src/password/macos.rs index 408706423e20..c911a0d2430d 100644 --- a/apps/desktop/desktop_native/core/src/password/macos.rs +++ b/apps/desktop/desktop_native/core/src/password/macos.rs @@ -3,26 +3,22 @@ use security_framework::passwords::{ delete_generic_password, get_generic_password, set_generic_password, }; -pub fn get_password(service: &str, account: &str) -> Result { +pub async fn get_password(service: &str, account: &str) -> Result { let result = String::from_utf8(get_generic_password(&service, &account)?)?; Ok(result) } -pub fn get_password_keytar(service: &str, account: &str) -> Result { - get_password(service, account) -} - -pub fn set_password(service: &str, account: &str, password: &str) -> Result<()> { +pub async fn set_password(service: &str, account: &str, password: &str) -> Result<()> { let result = set_generic_password(&service, &account, password.as_bytes())?; Ok(result) } -pub fn delete_password(service: &str, account: &str) -> Result<()> { +pub async fn delete_password(service: &str, account: &str) -> Result<()> { let result = delete_generic_password(&service, &account)?; Ok(result) } -pub fn is_available() -> Result { +pub async fn is_available() -> Result { Ok(true) } @@ -30,18 +26,17 @@ pub fn is_available() -> Result { mod tests { use super::*; - #[test] - fn test() { - scopeguard::defer!(delete_password("BitwardenTest", "BitwardenTest").unwrap_or({});); - set_password("BitwardenTest", "BitwardenTest", "Random").unwrap(); + #[tokio::test] + async fn test() { + set_password("BitwardenTest", "BitwardenTest", "Random").await.unwrap(); assert_eq!( "Random", - get_password("BitwardenTest", "BitwardenTest").unwrap() + get_password("BitwardenTest", "BitwardenTest").await.unwrap() ); - delete_password("BitwardenTest", "BitwardenTest").unwrap(); + delete_password("BitwardenTest", "BitwardenTest").await.unwrap(); // Ensure password is deleted - match get_password("BitwardenTest", "BitwardenTest") { + match get_password("BitwardenTest", "BitwardenTest").await { Ok(_) => panic!("Got a result"), Err(e) => assert_eq!( "The specified item could not be found in the keychain.", @@ -50,9 +45,9 @@ mod tests { } } - #[test] - fn test_error_no_password() { - match get_password("Unknown", "Unknown") { + #[tokio::test] + async fn test_error_no_password() { + match get_password("Unknown", "Unknown").await { Ok(_) => panic!("Got a result"), Err(e) => assert_eq!( "The specified item could not be found in the keychain.", diff --git a/apps/desktop/desktop_native/core/src/password/unix.rs b/apps/desktop/desktop_native/core/src/password/unix.rs index 53053ee467e8..337ffe493f2b 100644 --- a/apps/desktop/desktop_native/core/src/password/unix.rs +++ b/apps/desktop/desktop_native/core/src/password/unix.rs @@ -1,102 +1,109 @@ use anyhow::{anyhow, Result}; -use libsecret::{password_clear_sync, password_lookup_sync, password_store_sync, Schema}; +use oo7::dbus::{self}; use std::collections::HashMap; -pub fn get_password(service: &str, account: &str) -> Result { - let res = password_lookup_sync( - Some(&get_schema()), - build_attributes(service, account), - gio::Cancellable::NONE, - )?; +pub async fn get_password(service: &str, account: &str) -> Result { + match get_password_new(service, account).await { + Ok(res) => Ok(res), + Err(_) => { + get_password_legacy(service, account).await + } + } +} +async fn get_password_new(service: &str, account: &str) -> Result { + let keyring = oo7::Keyring::new().await?; + let attributes = HashMap::from([("service", service), ("account", account)]); + let results = keyring.search_items(&attributes).await?; + let res = results.get(0); match res { - Some(s) => Ok(String::from(s)), - None => Err(anyhow!("No password found")), + Some(res) => { + let secret = res.secret().await?; + Ok(String::from_utf8(secret.to_vec())?) + }, + None => Err(anyhow!("no result")) } } -pub fn get_password_keytar(service: &str, account: &str) -> Result { - get_password(service, account) +// forces to read via secret service; remvove after 2024.03 +async fn get_password_legacy(service: &str, account: &str) -> Result { + println!("falling back to get legacy {} {}", service, account); + let svc = dbus::Service::new().await?; + let collection = match svc.default_collection().await { + Ok(c) => Ok(c), + Err(e) => Err(e), + }?; + let keyring = oo7::Keyring::DBus(collection); + let attributes = HashMap::from([("service", service), ("account", account)]); + let results = keyring.search_items(&attributes).await?; + let res = results.get(0); + match res { + Some(res) => { + let secret = res.secret().await?; + println!("deleting legacy secret service entry {} {}", service, account); + keyring.delete(&attributes).await?; + let secret_string = String::from_utf8(secret.to_vec())?; + set_password(service, account, &secret_string).await?; + Ok(secret_string) + }, + None => Err(anyhow!("no result")) + } } -pub fn set_password(service: &str, account: &str, password: &str) -> Result<()> { - let result = password_store_sync( - Some(&get_schema()), - build_attributes(service, account), - Some(&libsecret::COLLECTION_DEFAULT), - &format!("{}/{}", service, account), - password, - gio::Cancellable::NONE, - )?; - Ok(result) +pub async fn set_password(service: &str, account: &str, password: &str) -> Result<()> { + let keyring = oo7::Keyring::new().await?; + let attributes = HashMap::from([("service", service), ("account", account)]); + keyring.create_item("org.freedesktop.Secret.Generic", &attributes, password, true).await?; + Ok(()) } -pub fn delete_password(service: &str, account: &str) -> Result<()> { - let result = password_clear_sync( - Some(&get_schema()), - build_attributes(service, account), - gio::Cancellable::NONE, - )?; - Ok(result) +pub async fn delete_password(service: &str, account: &str) -> Result<()> { + let keyring = oo7::Keyring::new().await?; + let attributes = HashMap::from([("service", service), ("account", account)]); + keyring.delete(&attributes).await?; + Ok(()) } -pub fn is_available() -> Result { - let result = password_clear_sync(Some(&get_schema()), build_attributes("bitwardenSecretsAvailabilityTest", "test"), gio::Cancellable::NONE); - match result { +pub async fn is_available() -> Result { + match oo7::Keyring::new().await { Ok(_) => Ok(true), - Err(_) => { - println!("secret-service unavailable: {:?}", result); - Ok(false) - } + _ => Ok(false), } } -fn get_schema() -> Schema { - let mut attributes = std::collections::HashMap::new(); - attributes.insert("service", libsecret::SchemaAttributeType::String); - attributes.insert("account", libsecret::SchemaAttributeType::String); - - libsecret::Schema::new( - "org.freedesktop.Secret.Generic", - libsecret::SchemaFlags::NONE, - attributes, - ) -} - -fn build_attributes<'a>(service: &'a str, account: &'a str) -> HashMap<&'a str, &'a str> { - let mut attributes = HashMap::new(); - attributes.insert("service", service); - attributes.insert("account", account); - - attributes -} - #[cfg(test)] mod tests { use super::*; - #[test] - fn test() { - scopeguard::defer!(delete_password("BitwardenTest", "BitwardenTest").unwrap_or({});); - set_password("BitwardenTest", "BitwardenTest", "Random").unwrap(); + #[tokio::test] + async fn test() { + set_password("BitwardenTest", "BitwardenTest", "Random").await.unwrap(); assert_eq!( "Random", - get_password("BitwardenTest", "BitwardenTest").unwrap() + get_password("BitwardenTest", "BitwardenTest").await.unwrap() ); - delete_password("BitwardenTest", "BitwardenTest").unwrap(); + delete_password("BitwardenTest", "BitwardenTest").await.unwrap(); // Ensure password is deleted - match get_password("BitwardenTest", "BitwardenTest") { - Ok(_) => panic!("Got a result"), - Err(e) => assert_eq!("No password found", e.to_string()), + match get_password("BitwardenTest", "BitwardenTest").await { + Ok(_) => { + panic!("Got a result") + } + Err(e) => assert_eq!( + "no result", + e.to_string() + ), } } - #[test] - fn test_error_no_password() { - match get_password("BitwardenTest", "BitwardenTest") { + #[tokio::test] + async fn test_error_no_password() { + match get_password("Unknown", "Unknown").await { Ok(_) => panic!("Got a result"), - Err(e) => assert_eq!("No password found", e.to_string()), + Err(e) => assert_eq!( + "no result", + e.to_string() + ), } } } diff --git a/apps/desktop/desktop_native/core/src/password/windows.rs b/apps/desktop/desktop_native/core/src/password/windows.rs index d932aabae958..873e717ac8bd 100644 --- a/apps/desktop/desktop_native/core/src/password/windows.rs +++ b/apps/desktop/desktop_native/core/src/password/windows.rs @@ -13,7 +13,7 @@ use windows::{ const CRED_FLAGS_NONE: u32 = 0; -pub fn get_password<'a>(service: &str, account: &str) -> Result { +pub async fn get_password<'a>(service: &str, account: &str) -> Result { let target_name = U16CString::from_str(target_name(service, account))?; let mut credential: *mut CREDENTIALW = std::ptr::null_mut(); @@ -45,39 +45,7 @@ pub fn get_password<'a>(service: &str, account: &str) -> Result { Ok(String::from(password)) } -// Remove this after sufficient releases -pub fn get_password_keytar<'a>(service: &str, account: &str) -> Result { - let target_name = U16CString::from_str(target_name(service, account))?; - - let mut credential: *mut CREDENTIALW = std::ptr::null_mut(); - let credential_ptr = &mut credential; - - let result = unsafe { - CredReadW( - PCWSTR(target_name.as_ptr()), - CRED_TYPE_GENERIC, - CRED_FLAGS_NONE, - credential_ptr, - ) - }; - - scopeguard::defer!({ - unsafe { CredFree(credential as *mut _) }; - }); - - result?; - - let password = unsafe { - std::str::from_utf8_unchecked(std::slice::from_raw_parts( - (*credential).CredentialBlob, - (*credential).CredentialBlobSize as usize, - )) - }; - - Ok(String::from(password)) -} - -pub fn set_password(service: &str, account: &str, password: &str) -> Result<()> { +pub async fn set_password(service: &str, account: &str, password: &str) -> Result<()> { let mut target_name = U16CString::from_str(target_name(service, account))?; let mut user_name = U16CString::from_str(account)?; let last_written = FILETIME { @@ -108,7 +76,7 @@ pub fn set_password(service: &str, account: &str, password: &str) -> Result<()> Ok(()) } -pub fn delete_password(service: &str, account: &str) -> Result<()> { +pub async fn delete_password(service: &str, account: &str) -> Result<()> { let target_name = U16CString::from_str(target_name(service, account))?; unsafe { @@ -122,7 +90,7 @@ pub fn delete_password(service: &str, account: &str) -> Result<()> { Ok(()) } -pub fn is_available() -> Result { +pub async fn is_available() -> Result { Ok(true) } @@ -142,36 +110,25 @@ fn convert_error(e: windows::core::Error) -> String { mod tests { use super::*; - #[test] - fn test() { - scopeguard::defer!(delete_password("BitwardenTest", "BitwardenTest").unwrap_or({});); - set_password("BitwardenTest", "BitwardenTest", "Random").unwrap(); + #[tokio::test] + async fn test() { + set_password("BitwardenTest", "BitwardenTest", "Random").await.unwrap(); assert_eq!( "Random", - get_password("BitwardenTest", "BitwardenTest").unwrap() + get_password("BitwardenTest", "BitwardenTest").await.unwrap() ); - delete_password("BitwardenTest", "BitwardenTest").unwrap(); + delete_password("BitwardenTest", "BitwardenTest").await.unwrap(); // Ensure password is deleted - match get_password("BitwardenTest", "BitwardenTest") { + match get_password("BitwardenTest", "BitwardenTest").await { Ok(_) => panic!("Got a result"), Err(e) => assert_eq!("Password not found.", e.to_string()), } } - #[test] - fn test_get_password_keytar() { - scopeguard::defer!(delete_password("BitwardenTest", "BitwardenTest").unwrap_or({});); - keytar::set_password("BitwardenTest", "BitwardenTest", "HelloFromKeytar").unwrap(); - assert_eq!( - "HelloFromKeytar", - get_password_keytar("BitwardenTest", "BitwardenTest").unwrap() - ); - } - - #[test] - fn test_error_no_password() { - match get_password("BitwardenTest", "BitwardenTest") { + #[tokio::test] + async fn test_error_no_password() { + match get_password("BitwardenTest", "BitwardenTest").await { Ok(_) => panic!("Got a result"), Err(e) => assert_eq!("Password not found.", e.to_string()), } diff --git a/apps/desktop/desktop_native/napi/index.d.ts b/apps/desktop/desktop_native/napi/index.d.ts index 8e1c1381b5f0..06a8d6b0e977 100644 --- a/apps/desktop/desktop_native/napi/index.d.ts +++ b/apps/desktop/desktop_native/napi/index.d.ts @@ -6,8 +6,6 @@ export declare namespace passwords { /** Fetch the stored password from the keychain. */ export function getPassword(service: string, account: string): Promise - /** Fetch the stored password from the keychain that was stored with Keytar. */ - export function getPasswordKeytar(service: string, account: string): Promise /** Save the password to the keychain. Adds an entry if none exists otherwise updates the existing entry. */ export function setPassword(service: string, account: string, password: string): Promise /** Delete the stored password from the keychain. */ diff --git a/apps/desktop/desktop_native/napi/index.js b/apps/desktop/desktop_native/napi/index.js index a0cfee8e1a05..352407530dd4 100644 --- a/apps/desktop/desktop_native/napi/index.js +++ b/apps/desktop/desktop_native/napi/index.js @@ -153,9 +153,14 @@ switch (platform) { localFileExisted = existsSync( join(__dirname, 'desktop_napi.linux-x64-musl.node') ) + let glibcLocalFileExisted = existsSync( + join(__dirname, 'desktop_napi.linux-x64-gnu.node') + ) try { if (localFileExisted) { nativeBinding = require('./desktop_napi.linux-x64-musl.node') + } else if (glibcLocalFileExisted) { + nativeBinding = require('./desktop_napi.linux-x64-gnu.node') } else { nativeBinding = require('@bitwarden/desktop-napi-linux-x64-musl') } diff --git a/apps/desktop/desktop_native/napi/src/lib.rs b/apps/desktop/desktop_native/napi/src/lib.rs index face07f2f4e2..0398a89c980c 100644 --- a/apps/desktop/desktop_native/napi/src/lib.rs +++ b/apps/desktop/desktop_native/napi/src/lib.rs @@ -8,14 +8,7 @@ pub mod passwords { /// Fetch the stored password from the keychain. #[napi] pub async fn get_password(service: String, account: String) -> napi::Result { - desktop_core::password::get_password(&service, &account) - .map_err(|e| napi::Error::from_reason(e.to_string())) - } - - /// Fetch the stored password from the keychain that was stored with Keytar. - #[napi] - pub async fn get_password_keytar(service: String, account: String) -> napi::Result { - desktop_core::password::get_password_keytar(&service, &account) + desktop_core::password::get_password(&service, &account).await .map_err(|e| napi::Error::from_reason(e.to_string())) } @@ -26,21 +19,21 @@ pub mod passwords { account: String, password: String, ) -> napi::Result<()> { - desktop_core::password::set_password(&service, &account, &password) + desktop_core::password::set_password(&service, &account, &password).await .map_err(|e| napi::Error::from_reason(e.to_string())) } /// Delete the stored password from the keychain. #[napi] pub async fn delete_password(service: String, account: String) -> napi::Result<()> { - desktop_core::password::delete_password(&service, &account) + desktop_core::password::delete_password(&service, &account).await .map_err(|e| napi::Error::from_reason(e.to_string())) } // Checks if the os secure storage is available #[napi] pub async fn is_available() -> napi::Result { - desktop_core::password::is_available().map_err(|e| napi::Error::from_reason(e.to_string())) + desktop_core::password::is_available().await.map_err(|e| napi::Error::from_reason(e.to_string())) } } @@ -77,6 +70,7 @@ pub mod biometrics { key_material.map(|m| m.into()), &iv_b64, ) + .await .map_err(|e| napi::Error::from_reason(e.to_string())) } @@ -88,6 +82,7 @@ pub mod biometrics { ) -> napi::Result { let result = Biometric::get_biometric_secret(&service, &account, key_material.map(|m| m.into())) + .await .map_err(|e| napi::Error::from_reason(e.to_string())); result } diff --git a/apps/desktop/desktop_native/test.txt b/apps/desktop/desktop_native/test.txt new file mode 100644 index 000000000000..e69de29bb2d1