1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-12 06:13:38 +00:00

[PM-14252] Switch to oo7 and drop libsecret (#11900)

* Switch to oo7 and drop libsecret

* Fix tests

* Fix windows

* Fix windows

* Fix windows

* Fix windows

* Add migration

* Update apps/desktop/desktop_native/core/src/password/unix.rs

Co-authored-by: Daniel García <dani-garcia@users.noreply.github.com>

* Remove libsecret in ci

* Move allow async to trait level

* Fix comment

* Pin oo7 dependency

---------

Co-authored-by: Matt Bishop <mbishop@bitwarden.com>
Co-authored-by: Daniel García <dani-garcia@users.noreply.github.com>
This commit is contained in:
Bernd Schoolmann
2024-12-04 17:03:34 +01:00
committed by GitHub
parent 98702d9f50
commit 80a898bd8c
14 changed files with 278 additions and 394 deletions

View File

@@ -170,7 +170,7 @@ jobs:
- name: Set up environment - name: Set up environment
run: | run: |
sudo apt-get update sudo apt-get update
sudo apt-get -y install pkg-config libxss-dev libsecret-1-dev rpm musl-dev musl-tools flatpak flatpak-builder sudo apt-get -y install pkg-config libxss-dev rpm musl-dev musl-tools flatpak flatpak-builder
- name: Set up Snap - name: Set up Snap
run: sudo snap install snapcraft --classic run: sudo snap install snapcraft --classic

View File

@@ -138,7 +138,7 @@ jobs:
- name: Set up environment - name: Set up environment
run: | run: |
sudo apt-get update sudo apt-get update
sudo apt-get -y install pkg-config libxss-dev libsecret-1-dev rpm sudo apt-get -y install pkg-config libxss-dev rpm
- name: Set up Snap - name: Set up Snap
run: sudo snap install snapcraft --classic run: sudo snap install snapcraft --classic

View File

@@ -36,6 +36,7 @@ dependencies = [
"cfg-if", "cfg-if",
"cipher", "cipher",
"cpufeatures", "cpufeatures",
"zeroize",
] ]
[[package]] [[package]]
@@ -174,6 +175,17 @@ dependencies = [
"pin-project-lite", "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]] [[package]]
name = "async-process" name = "async-process"
version = "2.3.0" version = "2.3.0"
@@ -422,16 +434,6 @@ dependencies = [
"shlex", "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]] [[package]]
name = "cfg-if" name = "cfg-if"
version = "1.0.0" version = "1.0.0"
@@ -469,6 +471,7 @@ checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad"
dependencies = [ dependencies = [
"crypto-common", "crypto-common",
"inout", "inout",
"zeroize",
] ]
[[package]] [[package]]
@@ -552,6 +555,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3"
dependencies = [ dependencies = [
"generic-array", "generic-array",
"rand_core",
"typenum", "typenum",
] ]
@@ -692,13 +696,12 @@ dependencies = [
"dirs", "dirs",
"ed25519", "ed25519",
"futures", "futures",
"gio",
"homedir", "homedir",
"interprocess", "interprocess",
"keytar", "keytar",
"libc", "libc",
"libsecret",
"log", "log",
"oo7",
"pin-project", "pin-project",
"pkcs8", "pkcs8",
"rand", "rand",
@@ -1079,105 +1082,12 @@ version = "0.31.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" 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]] [[package]]
name = "hashbrown" name = "hashbrown"
version = "0.15.1" version = "0.15.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3a9bfc1af68b1726ea47d3d5109de126281def866b33970e10fbab11b5dafab3" checksum = "3a9bfc1af68b1726ea47d3d5109de126281def866b33970e10fbab11b5dafab3"
[[package]]
name = "heck"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
[[package]] [[package]]
name = "hermit-abi" name = "hermit-abi"
version = "0.3.9" version = "0.3.9"
@@ -1196,6 +1106,15 @@ version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
[[package]]
name = "hkdf"
version = "0.12.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7"
dependencies = [
"hmac",
]
[[package]] [[package]]
name = "hmac" name = "hmac"
version = "0.12.1" version = "0.12.1"
@@ -1306,9 +1225,9 @@ dependencies = [
[[package]] [[package]]
name = "libm" name = "libm"
version = "0.2.8" version = "0.2.11"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058" checksum = "8355be11b20d696c8f18f6cc018c4e372165b1fa8126cef092399c9951984ffa"
[[package]] [[package]]
name = "libredox" name = "libredox"
@@ -1320,32 +1239,6 @@ dependencies = [
"libc", "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]] [[package]]
name = "link-cplusplus" name = "link-cplusplus"
version = "1.0.9" version = "1.0.9"
@@ -1377,6 +1270,16 @@ version = "0.4.22"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" 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]] [[package]]
name = "memchr" name = "memchr"
version = "2.7.4" version = "2.7.4"
@@ -1512,6 +1415,30 @@ dependencies = [
"minimal-lexical", "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]] [[package]]
name = "num-bigint-dig" name = "num-bigint-dig"
version = "0.8.4" version = "0.8.4"
@@ -1525,10 +1452,20 @@ dependencies = [
"num-iter", "num-iter",
"num-traits", "num-traits",
"rand", "rand",
"serde",
"smallvec", "smallvec",
"zeroize", "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]] [[package]]
name = "num-conv" name = "num-conv"
version = "0.1.0" version = "0.1.0"
@@ -1555,6 +1492,17 @@ dependencies = [
"num-traits", "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]] [[package]]
name = "num-traits" name = "num-traits"
version = "0.2.19" version = "0.2.19"
@@ -1688,6 +1636,39 @@ version = "1.20.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" 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]] [[package]]
name = "opaque-debug" name = "opaque-debug"
version = "0.3.1" version = "0.3.1"
@@ -2216,15 +2197,6 @@ dependencies = [
"syn", "syn",
] ]
[[package]]
name = "serde_spanned"
version = "0.6.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "87607cb1398ed59d48732e575a4c28a7a8ebf2454b964fe3f224f2afc07909e1"
dependencies = [
"serde",
]
[[package]] [[package]]
name = "sha1" name = "sha1"
version = "0.10.6" version = "0.10.6"
@@ -2394,25 +2366,6 @@ dependencies = [
"unicode-ident", "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]] [[package]]
name = "tempfile" name = "tempfile"
version = "3.13.0" version = "3.13.0"
@@ -2539,26 +2492,11 @@ dependencies = [
"tokio", "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]] [[package]]
name = "toml_datetime" name = "toml_datetime"
version = "0.6.8" version = "0.6.8"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41"
dependencies = [
"serde",
]
[[package]] [[package]]
name = "toml_edit" name = "toml_edit"
@@ -2567,8 +2505,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4ae48d6208a266e853d946088ed816055e556cc6028c5e8e2b84d9fa5dd7c7f5" checksum = "4ae48d6208a266e853d946088ed816055e556cc6028c5e8e2b84d9fa5dd7c7f5"
dependencies = [ dependencies = [
"indexmap", "indexmap",
"serde",
"serde_spanned",
"toml_datetime", "toml_datetime",
"winnow", "winnow",
] ]
@@ -2662,12 +2598,6 @@ dependencies = [
"subtle", "subtle",
] ]
[[package]]
name = "version-compare"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "852e951cb7832cb45cb1169900d19760cfa39b82bc0ea9c0e5a14ae88411c98b"
[[package]] [[package]]
name = "version_check" name = "version_check"
version = "0.9.5" version = "0.9.5"
@@ -3232,6 +3162,20 @@ name = "zeroize"
version = "1.8.1" version = "1.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" 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]] [[package]]
name = "zvariant" name = "zvariant"

View File

@@ -10,15 +10,13 @@ default = ["sys"]
manual_test = [] manual_test = []
sys = [ sys = [
"dep:widestring", "dep:widestring",
"dep:windows", "dep:windows",
"dep:core-foundation", "dep:core-foundation",
"dep:security-framework", "dep:security-framework",
"dep:security-framework-sys", "dep:security-framework-sys",
"dep:gio", "dep:zbus",
"dep:libsecret", "dep:zbus_polkit",
"dep:zbus",
"dep:zbus_polkit",
] ]
[dependencies] [dependencies]
@@ -85,7 +83,7 @@ security-framework = { version = "=3.0.0", optional = true }
security-framework-sys = { version = "=2.12.0", optional = true } security-framework-sys = { version = "=2.12.0", optional = true }
[target.'cfg(target_os = "linux")'.dependencies] [target.'cfg(target_os = "linux")'.dependencies]
gio = { version = "=0.19.5", optional = true } oo7 = "=0.3.3"
libsecret = { version = "=0.5.0", optional = true }
zbus = { version = "=4.4.0", optional = true } zbus = { version = "=4.4.0", optional = true }
zbus_polkit = { version = "=4.0.0", optional = true } zbus_polkit = { version = "=4.0.0", optional = true }

View File

@@ -18,7 +18,7 @@ impl super::BiometricTrait for Biometric {
bail!("platform not supported"); bail!("platform not supported");
} }
fn get_biometric_secret( async fn get_biometric_secret(
_service: &str, _service: &str,
_account: &str, _account: &str,
_key_material: Option<KeyMaterial>, _key_material: Option<KeyMaterial>,
@@ -26,7 +26,7 @@ impl super::BiometricTrait for Biometric {
bail!("platform not supported"); bail!("platform not supported");
} }
fn set_biometric_secret( async fn set_biometric_secret(
_service: &str, _service: &str,
_account: &str, _account: &str,
_secret: &str, _secret: &str,

View File

@@ -22,20 +22,19 @@ pub struct OsDerivedKey {
pub iv_b64: String, pub iv_b64: String,
} }
#[allow(async_fn_in_trait)]
pub trait BiometricTrait { pub trait BiometricTrait {
#[allow(async_fn_in_trait)]
async fn prompt(hwnd: Vec<u8>, message: String) -> Result<bool>; async fn prompt(hwnd: Vec<u8>, message: String) -> Result<bool>;
#[allow(async_fn_in_trait)]
async fn available() -> Result<bool>; async fn available() -> Result<bool>;
fn derive_key_material(secret: Option<&str>) -> Result<OsDerivedKey>; fn derive_key_material(secret: Option<&str>) -> Result<OsDerivedKey>;
fn set_biometric_secret( async fn set_biometric_secret(
service: &str, service: &str,
account: &str, account: &str,
secret: &str, secret: &str,
key_material: Option<KeyMaterial>, key_material: Option<KeyMaterial>,
iv_b64: &str, iv_b64: &str,
) -> Result<String>; ) -> Result<String>;
fn get_biometric_secret( async fn get_biometric_secret(
service: &str, service: &str,
account: &str, account: &str,
key_material: Option<KeyMaterial>, key_material: Option<KeyMaterial>,

View File

@@ -73,7 +73,7 @@ impl super::BiometricTrait for Biometric {
Ok(OsDerivedKey { key_b64, iv_b64 }) Ok(OsDerivedKey { key_b64, iv_b64 })
} }
fn set_biometric_secret( async fn set_biometric_secret(
service: &str, service: &str,
account: &str, account: &str,
secret: &str, secret: &str,
@@ -85,11 +85,11 @@ impl super::BiometricTrait for Biometric {
))?; ))?;
let encrypted_secret = encrypt(secret, &key_material, iv_b64)?; 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) Ok(encrypted_secret)
} }
fn get_biometric_secret( async fn get_biometric_secret(
service: &str, service: &str,
account: &str, account: &str,
key_material: Option<KeyMaterial>, key_material: Option<KeyMaterial>,
@@ -98,7 +98,7 @@ impl super::BiometricTrait for Biometric {
"Key material is required for polkit protected keys" "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)?; let secret = CipherString::from_str(&encrypted_secret)?;
return Ok(decrypt(&secret, &key_material)?); return Ok(decrypt(&secret, &key_material)?);
} }

View File

@@ -121,7 +121,7 @@ impl super::BiometricTrait for Biometric {
Ok(OsDerivedKey { key_b64, iv_b64 }) Ok(OsDerivedKey { key_b64, iv_b64 })
} }
fn set_biometric_secret( async fn set_biometric_secret(
service: &str, service: &str,
account: &str, account: &str,
secret: &str, secret: &str,
@@ -133,11 +133,11 @@ impl super::BiometricTrait for Biometric {
))?; ))?;
let encrypted_secret = encrypt(secret, &key_material, iv_b64)?; 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) Ok(encrypted_secret)
} }
fn get_biometric_secret( async fn get_biometric_secret(
service: &str, service: &str,
account: &str, account: &str,
key_material: Option<KeyMaterial>, key_material: Option<KeyMaterial>,
@@ -146,7 +146,7 @@ impl super::BiometricTrait for Biometric {
"Key material is required for Windows Hello protected keys" "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) { match CipherString::from_str(&encrypted_secret) {
Ok(secret) => { Ok(secret) => {
// If the secret is a CipherString, it is encrypted and we need to decrypt it. // If the secret is a CipherString, it is encrypted and we need to decrypt it.
@@ -292,9 +292,9 @@ mod tests {
assert_eq!(decrypt(&secret, &key_material).unwrap(), "secret") assert_eq!(decrypt(&secret, &key_material).unwrap(), "secret")
} }
#[test] #[tokio::test]
fn get_biometric_secret_requires_key() { async fn get_biometric_secret_requires_key() {
let result = <Biometric as BiometricTrait>::get_biometric_secret("", "", None); let result = <Biometric as BiometricTrait>::get_biometric_secret("", "", None).await;
assert!(result.is_err()); assert!(result.is_err());
assert_eq!( assert_eq!(
result.unwrap_err().to_string(), result.unwrap_err().to_string(),
@@ -302,29 +302,25 @@ mod tests {
); );
} }
#[test] #[tokio::test]
fn get_biometric_secret_handles_unencrypted_secret() { async fn get_biometric_secret_handles_unencrypted_secret() {
scopeguard::defer! {
crate::password::delete_password("test", "test").unwrap();
}
let test = "test"; let test = "test";
let secret = "password"; let secret = "password";
let key_material = KeyMaterial { let key_material = KeyMaterial {
os_key_part_b64: "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=".to_owned(), os_key_part_b64: "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=".to_owned(),
client_key_part_b64: Some("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 = let result =
<Biometric as BiometricTrait>::get_biometric_secret(test, test, Some(key_material)) <Biometric as BiometricTrait>::get_biometric_secret(test, test, Some(key_material))
.await
.unwrap(); .unwrap();
crate::password::delete_password("test", "test").await.unwrap();
assert_eq!(result, secret); assert_eq!(result, secret);
} }
#[test] #[tokio::test]
fn get_biometric_secret_handles_encrypted_secret() { async fn get_biometric_secret_handles_encrypted_secret() {
scopeguard::defer! {
crate::password::delete_password("test", "test").unwrap();
}
let test = "test"; let test = "test";
let secret = let secret =
CipherString::from_str("0.l9fhDUP/wDJcKwmEzcb/3w==|uP4LcqoCCj5FxBDP77NV6Q==").unwrap(); // output from test_encrypt CipherString::from_str("0.l9fhDUP/wDJcKwmEzcb/3w==|uP4LcqoCCj5FxBDP77NV6Q==").unwrap(); // output from test_encrypt
@@ -332,17 +328,19 @@ mod tests {
os_key_part_b64: "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=".to_owned(), os_key_part_b64: "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=".to_owned(),
client_key_part_b64: Some("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 = let result =
<Biometric as BiometricTrait>::get_biometric_secret(test, test, Some(key_material)) <Biometric as BiometricTrait>::get_biometric_secret(test, test, Some(key_material))
.await
.unwrap(); .unwrap();
crate::password::delete_password("test", "test").await.unwrap();
assert_eq!(result, "secret"); assert_eq!(result, "secret");
} }
#[test] #[tokio::test]
fn set_biometric_secret_requires_key() { async fn set_biometric_secret_requires_key() {
let result = <Biometric as BiometricTrait>::set_biometric_secret("", "", "", None, ""); let result = <Biometric as BiometricTrait>::set_biometric_secret("", "", "", None, "").await;
assert!(result.is_err()); assert!(result.is_err());
assert_eq!( assert_eq!(
result.unwrap_err().to_string(), result.unwrap_err().to_string(),

View File

@@ -3,26 +3,22 @@ use security_framework::passwords::{
delete_generic_password, get_generic_password, set_generic_password, delete_generic_password, get_generic_password, set_generic_password,
}; };
pub fn get_password(service: &str, account: &str) -> Result<String> { pub async fn get_password(service: &str, account: &str) -> Result<String> {
let result = String::from_utf8(get_generic_password(&service, &account)?)?; let result = String::from_utf8(get_generic_password(&service, &account)?)?;
Ok(result) Ok(result)
} }
pub fn get_password_keytar(service: &str, account: &str) -> Result<String> { pub async fn set_password(service: &str, account: &str, password: &str) -> Result<()> {
get_password(service, account)
}
pub fn set_password(service: &str, account: &str, password: &str) -> Result<()> {
let result = set_generic_password(&service, &account, password.as_bytes())?; let result = set_generic_password(&service, &account, password.as_bytes())?;
Ok(result) 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)?; let result = delete_generic_password(&service, &account)?;
Ok(result) Ok(result)
} }
pub fn is_available() -> Result<bool> { pub async fn is_available() -> Result<bool> {
Ok(true) Ok(true)
} }
@@ -30,18 +26,17 @@ pub fn is_available() -> Result<bool> {
mod tests { mod tests {
use super::*; use super::*;
#[test] #[tokio::test]
fn test() { async fn test() {
scopeguard::defer!(delete_password("BitwardenTest", "BitwardenTest").unwrap_or({});); set_password("BitwardenTest", "BitwardenTest", "Random").await.unwrap();
set_password("BitwardenTest", "BitwardenTest", "Random").unwrap();
assert_eq!( assert_eq!(
"Random", "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 // Ensure password is deleted
match get_password("BitwardenTest", "BitwardenTest") { match get_password("BitwardenTest", "BitwardenTest").await {
Ok(_) => panic!("Got a result"), Ok(_) => panic!("Got a result"),
Err(e) => assert_eq!( Err(e) => assert_eq!(
"The specified item could not be found in the keychain.", "The specified item could not be found in the keychain.",
@@ -50,9 +45,9 @@ mod tests {
} }
} }
#[test] #[tokio::test]
fn test_error_no_password() { async fn test_error_no_password() {
match get_password("Unknown", "Unknown") { match get_password("Unknown", "Unknown").await {
Ok(_) => panic!("Got a result"), Ok(_) => panic!("Got a result"),
Err(e) => assert_eq!( Err(e) => assert_eq!(
"The specified item could not be found in the keychain.", "The specified item could not be found in the keychain.",

View File

@@ -1,106 +1,106 @@
use anyhow::{anyhow, Result}; use anyhow::{anyhow, Result};
use libsecret::{password_clear_sync, password_lookup_sync, password_store_sync, Schema}; use oo7::dbus::{self};
use std::collections::HashMap; use std::collections::HashMap;
pub fn get_password(service: &str, account: &str) -> Result<String> { pub async fn get_password(service: &str, account: &str) -> Result<String> {
let res = password_lookup_sync( match get_password_new(service, account).await {
Some(&get_schema()), Ok(res) => Ok(res),
build_attributes(service, account),
gio::Cancellable::NONE,
)?;
match res {
Some(s) => Ok(String::from(s)),
None => Err(anyhow!("No password found")),
}
}
pub fn get_password_keytar(service: &str, account: &str) -> Result<String> {
get_password(service, account)
}
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 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 fn is_available() -> Result<bool> {
let result = password_clear_sync(
Some(&get_schema()),
build_attributes("bitwardenSecretsAvailabilityTest", "test"),
gio::Cancellable::NONE,
);
match result {
Ok(_) => Ok(true),
Err(_) => { Err(_) => {
println!("secret-service unavailable: {:?}", result); get_password_legacy(service, account).await
Ok(false)
} }
} }
} }
fn get_schema() -> Schema { async fn get_password_new(service: &str, account: &str) -> Result<String> {
let mut attributes = std::collections::HashMap::new(); let keyring = oo7::Keyring::new().await?;
attributes.insert("service", libsecret::SchemaAttributeType::String); let attributes = HashMap::from([("service", service), ("account", account)]);
attributes.insert("account", libsecret::SchemaAttributeType::String); let results = keyring.search_items(&attributes).await?;
let res = results.get(0);
libsecret::Schema::new( match res {
"org.freedesktop.Secret.Generic", Some(res) => {
libsecret::SchemaFlags::NONE, let secret = res.secret().await?;
attributes, Ok(String::from_utf8(secret.to_vec())?)
) },
None => Err(anyhow!("no result"))
}
} }
fn build_attributes<'a>(service: &'a str, account: &'a str) -> HashMap<&'a str, &'a str> { // forces to read via secret service; remvove after 2025.03
let mut attributes = HashMap::new(); async fn get_password_legacy(service: &str, account: &str) -> Result<String> {
attributes.insert("service", service); println!("falling back to get legacy {} {}", service, account);
attributes.insert("account", account); let svc = dbus::Service::new().await?;
let collection = svc.default_collection().await?;
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"))
}
}
attributes 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 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 async fn is_available() -> Result<bool> {
match oo7::Keyring::new().await {
Ok(_) => Ok(true),
_ => Ok(false),
}
} }
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
#[test] #[tokio::test]
fn test() { async fn test() {
scopeguard::defer!(delete_password("BitwardenTest", "BitwardenTest").unwrap_or({});); set_password("BitwardenTest", "BitwardenTest", "Random").await.unwrap();
set_password("BitwardenTest", "BitwardenTest", "Random").unwrap();
assert_eq!( assert_eq!(
"Random", "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 // Ensure password is deleted
match get_password("BitwardenTest", "BitwardenTest") { match get_password("BitwardenTest", "BitwardenTest").await {
Ok(_) => panic!("Got a result"), Ok(_) => {
Err(e) => assert_eq!("No password found", e.to_string()), panic!("Got a result")
}
Err(e) => assert_eq!(
"no result",
e.to_string()
),
} }
} }
#[test] #[tokio::test]
fn test_error_no_password() { async fn test_error_no_password() {
match get_password("BitwardenTest", "BitwardenTest") { match get_password("Unknown", "Unknown").await {
Ok(_) => panic!("Got a result"), Ok(_) => panic!("Got a result"),
Err(e) => assert_eq!("No password found", e.to_string()), Err(e) => assert_eq!(
"no result",
e.to_string()
),
} }
} }
} }

View File

@@ -13,7 +13,7 @@ use windows::{
const CRED_FLAGS_NONE: u32 = 0; const CRED_FLAGS_NONE: u32 = 0;
pub fn get_password<'a>(service: &str, account: &str) -> Result<String> { pub async fn get_password<'a>(service: &str, account: &str) -> Result<String> {
let target_name = U16CString::from_str(target_name(service, account))?; let target_name = U16CString::from_str(target_name(service, account))?;
let mut credential: *mut CREDENTIALW = std::ptr::null_mut(); let mut credential: *mut CREDENTIALW = std::ptr::null_mut();
@@ -45,39 +45,7 @@ pub fn get_password<'a>(service: &str, account: &str) -> Result<String> {
Ok(String::from(password)) Ok(String::from(password))
} }
// Remove this after sufficient releases pub async fn set_password(service: &str, account: &str, password: &str) -> Result<()> {
pub fn get_password_keytar<'a>(service: &str, account: &str) -> Result<String> {
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<()> {
let mut target_name = U16CString::from_str(target_name(service, account))?; let mut target_name = U16CString::from_str(target_name(service, account))?;
let mut user_name = U16CString::from_str(account)?; let mut user_name = U16CString::from_str(account)?;
let last_written = FILETIME { let last_written = FILETIME {
@@ -108,7 +76,7 @@ pub fn set_password(service: &str, account: &str, password: &str) -> Result<()>
Ok(()) 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))?; let target_name = U16CString::from_str(target_name(service, account))?;
unsafe { unsafe {
@@ -122,7 +90,7 @@ pub fn delete_password(service: &str, account: &str) -> Result<()> {
Ok(()) Ok(())
} }
pub fn is_available() -> Result<bool> { pub async fn is_available() -> Result<bool> {
Ok(true) Ok(true)
} }
@@ -142,36 +110,25 @@ fn convert_error(e: windows::core::Error) -> String {
mod tests { mod tests {
use super::*; use super::*;
#[test] #[tokio::test]
fn test() { async fn test() {
scopeguard::defer!(delete_password("BitwardenTest", "BitwardenTest").unwrap_or({});); set_password("BitwardenTest", "BitwardenTest", "Random").await.unwrap();
set_password("BitwardenTest", "BitwardenTest", "Random").unwrap();
assert_eq!( assert_eq!(
"Random", "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 // Ensure password is deleted
match get_password("BitwardenTest", "BitwardenTest") { match get_password("BitwardenTest", "BitwardenTest").await {
Ok(_) => panic!("Got a result"), Ok(_) => panic!("Got a result"),
Err(e) => assert_eq!("Password not found.", e.to_string()), Err(e) => assert_eq!("Password not found.", e.to_string()),
} }
} }
#[test] #[tokio::test]
fn test_get_password_keytar() { async fn test_error_no_password() {
scopeguard::defer!(delete_password("BitwardenTest", "BitwardenTest").unwrap_or({});); match get_password("BitwardenTest", "BitwardenTest").await {
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") {
Ok(_) => panic!("Got a result"), Ok(_) => panic!("Got a result"),
Err(e) => assert_eq!("Password not found.", e.to_string()), Err(e) => assert_eq!("Password not found.", e.to_string()),
} }

View File

@@ -6,8 +6,6 @@
export declare namespace passwords { export declare namespace passwords {
/** Fetch the stored password from the keychain. */ /** Fetch the stored password from the keychain. */
export function getPassword(service: string, account: string): Promise<string> export function getPassword(service: string, account: string): Promise<string>
/** Fetch the stored password from the keychain that was stored with Keytar. */
export function getPasswordKeytar(service: string, account: string): Promise<string>
/** Save the password to the keychain. Adds an entry if none exists otherwise updates the existing entry. */ /** 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<void> export function setPassword(service: string, account: string, password: string): Promise<void>
/** Delete the stored password from the keychain. */ /** Delete the stored password from the keychain. */

View File

@@ -8,14 +8,7 @@ pub mod passwords {
/// Fetch the stored password from the keychain. /// Fetch the stored password from the keychain.
#[napi] #[napi]
pub async fn get_password(service: String, account: String) -> napi::Result<String> { pub async fn get_password(service: String, account: String) -> napi::Result<String> {
desktop_core::password::get_password(&service, &account) desktop_core::password::get_password(&service, &account).await
.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<String> {
desktop_core::password::get_password_keytar(&service, &account)
.map_err(|e| napi::Error::from_reason(e.to_string())) .map_err(|e| napi::Error::from_reason(e.to_string()))
} }
@@ -26,21 +19,21 @@ pub mod passwords {
account: String, account: String,
password: String, password: String,
) -> napi::Result<()> { ) -> 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())) .map_err(|e| napi::Error::from_reason(e.to_string()))
} }
/// Delete the stored password from the keychain. /// Delete the stored password from the keychain.
#[napi] #[napi]
pub async fn delete_password(service: String, account: String) -> napi::Result<()> { 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())) .map_err(|e| napi::Error::from_reason(e.to_string()))
} }
// Checks if the os secure storage is available // Checks if the os secure storage is available
#[napi] #[napi]
pub async fn is_available() -> napi::Result<bool> { pub async fn is_available() -> napi::Result<bool> {
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()))
} }
} }
@@ -81,6 +74,7 @@ pub mod biometrics {
key_material.map(|m| m.into()), key_material.map(|m| m.into()),
&iv_b64, &iv_b64,
) )
.await
.map_err(|e| napi::Error::from_reason(e.to_string())) .map_err(|e| napi::Error::from_reason(e.to_string()))
} }
@@ -92,6 +86,7 @@ pub mod biometrics {
) -> napi::Result<String> { ) -> napi::Result<String> {
let result = let result =
Biometric::get_biometric_secret(&service, &account, key_material.map(|m| m.into())) Biometric::get_biometric_secret(&service, &account, key_material.map(|m| m.into()))
.await
.map_err(|e| napi::Error::from_reason(e.to_string())); .map_err(|e| napi::Error::from_reason(e.to_string()));
result result
} }

View File

@@ -224,7 +224,7 @@
}, },
"deb": { "deb": {
"artifactName": "${productName}-${version}-${arch}.${ext}", "artifactName": "${productName}-${version}-${arch}.${ext}",
"depends": ["libnotify4", "libxtst6", "libnss3", "libsecret-1-0", "libxss1"] "depends": ["libnotify4", "libxtst6", "libnss3", "libxss1"]
}, },
"appImage": { "appImage": {
"artifactName": "${productName}-${version}-${arch}.${ext}" "artifactName": "${productName}-${version}-${arch}.${ext}"