diff --git a/Cargo.lock b/Cargo.lock index 13d1cf685..c947c990f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -819,9 +819,9 @@ checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" [[package]] name = "js-sys" -version = "0.3.92" +version = "0.3.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc4c90f45aa2e6eacbe8645f77fdea542ac97a494bcd117a67df9ff4d611f995" +checksum = "797146bb2677299a1eb6b7b50a890f4c361b29ef967addf5b2fa45dae1bb6d7d" dependencies = [ "once_cell", "wasm-bindgen", @@ -850,7 +850,7 @@ dependencies = [ "cipher", "ecb", "hex", - "md5", + "md-5", "pbkdf2", "serde", "serde_json", @@ -945,12 +945,6 @@ dependencies = [ "digest", ] -[[package]] -name = "md5" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "490cc448043f947bae3cbee9c203358d62dbee0db12107a74be5c30ccfd09771" - [[package]] name = "memchr" version = "2.8.0" @@ -1952,32 +1946,32 @@ dependencies = [ [[package]] name = "toml_datetime" -version = "1.1.0+spec-1.1.0" +version = "1.1.1+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97251a7c317e03ad83774a8752a7e81fb6067740609f75ea2b585b569a59198f" +checksum = "3165f65f62e28e0115a00b2ebdd37eb6f3b641855f9d636d3cd4103767159ad7" dependencies = [ "serde_core", ] [[package]] name = "toml_edit" -version = "0.25.8+spec-1.1.0" +version = "0.25.9+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16bff38f1d86c47f9ff0647e6838d7bb362522bdf44006c7068c2b1e606f1f3c" +checksum = "da053d28fe57e2c9d21b48261e14e7b4c8b670b54d2c684847b91feaf4c7dac5" dependencies = [ "indexmap", "toml_datetime", "toml_parser", - "winnow 1.0.0", + "winnow 1.0.1", ] [[package]] name = "toml_parser" -version = "1.1.0+spec-1.1.0" +version = "1.1.1+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2334f11ee363607eb04df9b8fc8a13ca1715a72ba8662a26ac285c98aabb4011" +checksum = "39ca317ebc49f06bd748bfba29533eac9485569dc9bf80b849024b025e814fb9" dependencies = [ - "winnow 1.0.0", + "winnow 1.0.1", ] [[package]] @@ -2143,9 +2137,9 @@ dependencies = [ [[package]] name = "wasm-bindgen" -version = "0.2.115" +version = "0.2.116" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6523d69017b7633e396a89c5efab138161ed5aafcbc8d3e5c5a42ae38f50495a" +checksum = "7dc0882f7b5bb01ae8c5215a1230832694481c1a4be062fd410e12ea3da5b631" dependencies = [ "cfg-if", "once_cell", @@ -2156,9 +2150,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.115" +version = "0.2.116" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e3a6c758eb2f701ed3d052ff5737f5bfe6614326ea7f3bbac7156192dc32e67" +checksum = "75973d3066e01d035dbedaad2864c398df42f8dd7b1ea057c35b8407c015b537" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -2166,9 +2160,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.115" +version = "0.2.116" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "921de2737904886b52bcbb237301552d05969a6f9c40d261eb0533c8b055fedf" +checksum = "91af5e4be765819e0bcfee7322c14374dc821e35e72fa663a830bbc7dc199eac" dependencies = [ "bumpalo", "proc-macro2", @@ -2179,9 +2173,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.115" +version = "0.2.116" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a93e946af942b58934c604527337bad9ae33ba1d5c6900bbb41c2c07c2364a93" +checksum = "c9bf0406a78f02f336bf1e451799cca198e8acde4ffa278f0fb20487b150a633" dependencies = [ "unicode-ident", ] @@ -2424,9 +2418,9 @@ dependencies = [ [[package]] name = "winnow" -version = "1.0.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a90e88e4667264a994d34e6d1ab2d26d398dcdca8b7f52bec8668957517fc7d8" +checksum = "09dac053f1cd375980747450bfc7250c264eaae0583872e845c0c7cd578872b5" dependencies = [ "memchr", ] diff --git a/client/src/migration.rs b/client/src/migration.rs index 753f52a95..30e2add50 100644 --- a/client/src/migration.rs +++ b/client/src/migration.rs @@ -1,4 +1,6 @@ -use crate::{AsAttributes, Result, dbus::Service, file::UnlockedKeyring}; +use std::path::Path; + +use crate::{AsAttributes, Result, Secret, dbus::Service, file::UnlockedKeyring}; /// Helper to migrate your secrets from the host Secret Service /// to the sandboxed file backend. @@ -7,17 +9,29 @@ use crate::{AsAttributes, Result, dbus::Service, file::UnlockedKeyring}; /// Secret Service. pub async fn migrate(attributes: Vec, replace: bool) -> Result<()> { let service = Service::new().await?; - let secret = crate::Secret::sandboxed().await?; - let file_backend = - match UnlockedKeyring::load(crate::file::api::Keyring::default_path()?, secret).await { - Ok(file) => Ok(file), - Err(super::file::Error::Portal(ashpd::Error::PortalNotFound(_))) => { - #[cfg(feature = "tracing")] - tracing::debug!("Portal not available, no migration to do"); - return Ok(()); - } - Err(err) => Err(err), - }?; + let secret = match Secret::sandboxed().await { + Ok(secret) => Ok(secret), + Err(super::file::Error::Portal(ashpd::Error::PortalNotFound(_))) => { + #[cfg(feature = "tracing")] + tracing::debug!("Portal not available, no migration to do"); + return Ok(()); + } + Err(err) => Err(err), + }?; + let keyring_path = crate::file::api::Keyring::default_path()?; + + migrate_inner(&service, secret, &keyring_path, attributes, replace).await +} + +/// Inner migration function for testing. +async fn migrate_inner( + service: &Service, + secret: Secret, + keyring_path: &Path, + attributes: Vec, + replace: bool, +) -> Result<()> { + let file_backend = UnlockedKeyring::load(keyring_path, secret).await?; let collection = service.default_collection().await?; let mut all_items = Vec::default(); @@ -58,3 +72,118 @@ pub async fn migrate(attributes: Vec, replace: bool) -> Resul Ok(()) } + +#[cfg(test)] +mod tests { + use super::*; + use crate::{Secret, dbus::Service, file::UnlockedKeyring}; + + #[tokio::test] + #[cfg(feature = "tokio")] + async fn test_migrate_from_dbus_to_file() { + let temp_dir = tempfile::tempdir().unwrap(); + let setup = oo7_server::tests::TestServiceSetup::plain_session(true) + .await + .unwrap(); + + // Create a DBus service with test connection + let service = Service::new_with_connection(&setup.client_conn) + .await + .unwrap(); + + // Create some items on the DBus backend + let collection = service.default_collection().await.unwrap(); + + collection + .create_item( + "Migration Test 1", + &[("app", "test-migration"), ("user", "alice")], + "secret1", + false, + None, + ) + .await + .unwrap(); + + collection + .create_item( + "Migration Test 2", + &[("app", "test-migration"), ("user", "bob")], + "secret2", + false, + None, + ) + .await + .unwrap(); + + // Verify items exist in DBus backend + let items_before = collection + .search_items(&[("app", "test-migration")]) + .await + .unwrap(); + assert_eq!(items_before.len(), 2); + + // Create file backend keyring + let keyring_path = temp_dir.path().join("migrated.keyring"); + let secret = Secret::from([1, 2].into_iter().cycle().take(64).collect::>()); + + // Perform migration using internal function + migrate_inner( + &service, + secret.clone(), + &keyring_path, + vec![&[("app", "test-migration")]], + false, + ) + .await + .unwrap(); + + // Verify items are deleted from DBus backend + let items_after = collection + .search_items(&[("app", "test-migration")]) + .await + .unwrap(); + assert_eq!(items_after.len(), 0); + + // Verify items exist in file backend + let file_backend = UnlockedKeyring::load(&keyring_path, secret).await.unwrap(); + let migrated_items = file_backend + .search_items(&[("app", "test-migration")]) + .await + .unwrap(); + + assert_eq!(migrated_items.len(), 2); + + // Verify item details + let alice_item = migrated_items + .iter() + .find(|item| { + item.attributes() + .get("user") + .map(|u| u == "alice") + .unwrap_or(false) + }) + .expect("Alice's item should exist"); + + assert_eq!(alice_item.label(), "Migration Test 1"); + assert_eq!(alice_item.secret(), Secret::text("secret1")); + assert_eq!( + alice_item.attributes().get("app").unwrap(), + "test-migration" + ); + + let bob_item = migrated_items + .iter() + .find(|item| { + item.attributes() + .get("user") + .map(|u| u == "bob") + .unwrap_or(false) + }) + .expect("Bob's item should exist"); + + assert_eq!(bob_item.label(), "Migration Test 2"); + assert_eq!(bob_item.secret(), Secret::text("secret2")); + assert_eq!(bob_item.attributes().get("app").unwrap(), "test-migration"); + } +} diff --git a/coverage.sh b/coverage.sh index 50e5a000a..1940fa7c9 100755 --- a/coverage.sh +++ b/coverage.sh @@ -60,7 +60,9 @@ grcov coverage-raw/combined.info \ --ignore "**/examples/*" \ --ignore "**/kwallet/*" \ --ignore "**/target/*" \ - --ignore "**/error.rs" + --ignore "**/error.rs" \ + --ignore "**/main.rs" \ + --ignore "**/capability.rs" # Generate HTML report with grcov grcov coverage-raw/combined.info \ @@ -78,7 +80,9 @@ grcov coverage-raw/combined.info \ --ignore "**/examples/*" \ --ignore "**/kwallet/*" \ --ignore "**/target/*" \ - --ignore "**/error.rs" + --ignore "**/error.rs" \ + --ignore "**/main.rs" \ + --ignore "**/capability.rs" # Extract and display coverage percentage if [ -f coverage/html/coverage.json ]; then diff --git a/kwallet/parser/Cargo.toml b/kwallet/parser/Cargo.toml index 5f06c1a65..38b9d5a56 100644 --- a/kwallet/parser/Cargo.toml +++ b/kwallet/parser/Cargo.toml @@ -16,7 +16,7 @@ exclude.workspace = true blowfish = "0.9" cbc = "0.1" ecb = "0.1" -md5 = "0.7" +md-5 = "0.10" sha1 = "0.10" sha2 = "0.10" pbkdf2 = { version = "0.12", default-features = false, features = ["simple"] } diff --git a/kwallet/parser/src/crypto.rs b/kwallet/parser/src/crypto.rs index 2d9d34c78..582ec6c0d 100644 --- a/kwallet/parser/src/crypto.rs +++ b/kwallet/parser/src/crypto.rs @@ -222,7 +222,8 @@ fn validate_sha1(data: &[u8], expected_hash: &[u8]) -> Result<()> { /// Compute MD5 hash pub fn compute_md5(data: &[u8]) -> [u8; 16] { - md5::compute(data).into() + use md5::Digest; + md5::Md5::digest(data).into() } /// Extract wallet data from decrypted payload and validate SHA-1 hash