From a1b8ed3ba8f78eeeac6feed07e04031c298c56d0 Mon Sep 17 00:00:00 2001 From: Bilal Elmoussaoui Date: Thu, 5 Nov 2026 12:07:26 +0100 Subject: [PATCH 1/2] Post release version bump --- Cargo.lock | 22 +++++++++++----------- Cargo.toml | 4 ++-- cli/Cargo.toml | 2 +- client/Cargo.toml | 2 +- kwallet/cli/Cargo.toml | 2 +- python/Cargo.toml | 2 +- server/Cargo.toml | 2 +- 7 files changed, 18 insertions(+), 18 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 25b6e5b7..385a3348 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -271,7 +271,7 @@ dependencies = [ [[package]] name = "cargo-credential-oo7" -version = "0.7.0-alpha" +version = "0.8.0-alpha" dependencies = [ "cargo-credential", "oo7", @@ -631,7 +631,7 @@ dependencies = [ [[package]] name = "git-credential-oo7" -version = "0.7.0-alpha" +version = "0.8.0-alpha" dependencies = [ "oo7", "tokio", @@ -718,7 +718,7 @@ dependencies = [ [[package]] name = "kwallet-cli" -version = "0.7.0-alpha" +version = "0.8.0-alpha" dependencies = [ "clap", "kwallet-parser", @@ -731,7 +731,7 @@ dependencies = [ [[package]] name = "kwallet-parser" -version = "0.7.0-alpha" +version = "0.8.0-alpha" dependencies = [ "base64", "blowfish", @@ -1010,7 +1010,7 @@ checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" [[package]] name = "oo7" -version = "0.7.0-alpha" +version = "0.8.0-alpha" dependencies = [ "aes", "ashpd", @@ -1047,7 +1047,7 @@ dependencies = [ [[package]] name = "oo7-cli" -version = "0.7.0-alpha" +version = "0.8.0-alpha" dependencies = [ "anstyle", "clap", @@ -1066,7 +1066,7 @@ dependencies = [ [[package]] name = "oo7-daemon" -version = "0.7.0-alpha" +version = "0.8.0-alpha" dependencies = [ "ashpd", "base64", @@ -1098,7 +1098,7 @@ dependencies = [ [[package]] name = "oo7-macros" -version = "0.7.0-alpha" +version = "0.8.0-alpha" dependencies = [ "proc-macro2", "quote", @@ -1107,7 +1107,7 @@ dependencies = [ [[package]] name = "oo7-pam" -version = "0.7.0-alpha" +version = "0.8.0-alpha" dependencies = [ "libc", "serde", @@ -1123,7 +1123,7 @@ dependencies = [ [[package]] name = "oo7-portal" -version = "0.7.0-alpha" +version = "0.8.0-alpha" dependencies = [ "ashpd", "clap", @@ -1136,7 +1136,7 @@ dependencies = [ [[package]] name = "oo7-python" -version = "0.7.0-alpha" +version = "0.8.0-alpha" dependencies = [ "oo7", "pyo3", diff --git a/Cargo.toml b/Cargo.toml index 75b30484..b34adde5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,7 +16,7 @@ members = [ ] [workspace.package] -version = "0.7.0-alpha" +version = "0.8.0-alpha" edition = "2024" authors = ["Bilal Elmoussaoui", "Sophie Herold", "Maximiliano Sandoval"] keywords = ["keyring", "secret", "service", "portal", "keychain"] @@ -41,7 +41,7 @@ libc = "0.2" md-5 = "0.11" num = "0.4.0" num-bigint-dig = { version = "0.9", features = ["zeroize"] } -oo7 = { path = "client", version = "0.7.0-alpha", default-features = false, features = ["unstable", "tracing"] } +oo7 = { path = "client", version = "0.8.0-alpha", default-features = false, features = ["unstable", "tracing"] } openssl = "0.10" pbkdf2 = { version = "0.13", default-features = false } rpassword = "7.5" diff --git a/cli/Cargo.toml b/cli/Cargo.toml index 29c21271..10948c3d 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -16,7 +16,7 @@ version.workspace = true anstyle = "1.0" clap.workspace = true hex = "0.4" -kwallet-parser = { version = "0.7.0-alpha", path = "../kwallet/parser", optional = true } +kwallet-parser = { version = "0.8.0-alpha", path = "../kwallet/parser", optional = true } oo7 = { workspace = true, features = ["tokio"] } rpassword.workspace = true serde.workspace = true diff --git a/client/Cargo.toml b/client/Cargo.toml index f46c6d75..947d07dd 100644 --- a/client/Cargo.toml +++ b/client/Cargo.toml @@ -29,7 +29,7 @@ hkdf = { workspace = true, optional = true } md-5 = { workspace = true, optional = true } num.workspace = true num-bigint-dig.workspace = true -oo7-macros = { path = "../macros", version = "0.7.0-alpha", optional = true } +oo7-macros = { path = "../macros", version = "0.8.0-alpha", optional = true } openssl = { workspace = true, optional = true } pbkdf2 = { workspace = true, optional = true, features = ["hmac"] } serde.workspace = true diff --git a/kwallet/cli/Cargo.toml b/kwallet/cli/Cargo.toml index 9306d54e..fc697eae 100644 --- a/kwallet/cli/Cargo.toml +++ b/kwallet/cli/Cargo.toml @@ -11,7 +11,7 @@ rust-version.workspace = true [dependencies] clap.workspace = true -kwallet-parser = { version = "0.7.0-alpha", path = "../parser" } +kwallet-parser = { version = "0.8.0-alpha", path = "../parser" } oo7 = { workspace = true, features = ["tokio", "native_crypto"] } rpassword.workspace = true serde.workspace = true diff --git a/python/Cargo.toml b/python/Cargo.toml index 927b3620..fcfbef4e 100644 --- a/python/Cargo.toml +++ b/python/Cargo.toml @@ -16,7 +16,7 @@ name = "oo7" crate-type = ["cdylib"] [dependencies] -oo7_rs = { package = "oo7", path = "../client", version = "0.7.0-alpha" } +oo7_rs = { package = "oo7", path = "../client", version = "0.8.0-alpha" } pyo3 = { version = "0.29", features = ["extension-module", "abi3-py38"] } pyo3-async-runtimes = { version = "0.29", features = ["tokio-runtime"] } tokio = { workspace = true, features = ["rt-multi-thread"] } diff --git a/server/Cargo.toml b/server/Cargo.toml index c30cb0ef..573bc7f2 100644 --- a/server/Cargo.toml +++ b/server/Cargo.toml @@ -30,7 +30,7 @@ clap.workspace = true formatx = "0.4" gettext-rs = { version = "0.8", features = ["gettext-system"] } hkdf = { workspace = true, optional = true } -kwallet-parser = { version = "0.7.0-alpha", path = "../kwallet/parser", optional = true } +kwallet-parser = { version = "0.8.0-alpha", path = "../kwallet/parser", optional = true } libc.workspace = true num.workspace = true num-bigint-dig.workspace = true From 9749c90d76ffe04d1449cf5a087edca4c6e46bd2 Mon Sep 17 00:00:00 2001 From: Bilal Elmoussaoui Date: Fri, 6 Nov 2026 11:27:20 +0100 Subject: [PATCH 2/2] client: Fix encrypted session fallback Since zbus 6, org.freedesktop.DBus.Error.* replies arrive as zbus::Error::FDO instead of zbus::Error::MethodError. This broke Service::new_with_connection's fallback to plain. Match the FDO variant alongside MethodError so the fallback works again. Closes: https://github.com/linux-credentials/oo7/issues/643 --- client/src/dbus/service.rs | 12 +-- client/tests/dbus_service.rs | 59 +++++++++++++ server/src/service/mod.rs | 17 +++- server/src/service/tests.rs | 8 +- server/src/tests.rs | 155 +++++++++++++++++++---------------- 5 files changed, 169 insertions(+), 82 deletions(-) diff --git a/client/src/dbus/service.rs b/client/src/dbus/service.rs index 1ce03c03..f727e8a3 100644 --- a/client/src/dbus/service.rs +++ b/client/src/dbus/service.rs @@ -63,12 +63,12 @@ impl Service { pub async fn new_with_connection(cnx: &zbus::Connection) -> Result { let service = match Self::encrypted_with_connection(cnx).await { Ok(service) => Ok(service), - Err(Error::ZBus(zbus::Error::MethodError(..))) => { - Self::plain_with_connection(cnx).await - } - Err(Error::Service(ServiceError::ZBus(zbus::Error::MethodError(..)))) => { - Self::plain_with_connection(cnx).await - } + Err( + Error::ZBus(zbus::Error::MethodError(..) | zbus::Error::FDO(_)) + | Error::Service(ServiceError::ZBus( + zbus::Error::MethodError(..) | zbus::Error::FDO(_), + )), + ) => Self::plain_with_connection(cnx).await, Err(e) => Err(e), }?; Ok(service) diff --git a/client/tests/dbus_service.rs b/client/tests/dbus_service.rs index 604ab5c6..7a12f696 100644 --- a/client/tests/dbus_service.rs +++ b/client/tests/dbus_service.rs @@ -1,6 +1,65 @@ use futures_util::StreamExt; use oo7::dbus::Service; +// https://github.com/linux-credentials/oo7/issues/643 +#[tokio::test] +#[cfg(feature = "tokio")] +async fn encrypted_not_supported_falls_back_to_plain() { + let setup = oo7_daemon::tests::TestServiceSetup::plain_session_no_encrypted() + .await + .unwrap(); + + // new_with_connection tries encrypted first, should fall back to plain. + let service = Service::new_with_connection(&setup.client_conn).await; + assert!( + service.is_ok(), + "Should fall back to plain session, got: {service:?}" + ); +} + +#[tokio::test] +#[cfg(feature = "tokio")] +async fn encrypted_on_plain_only_server_errors() { + let setup = oo7_daemon::tests::TestServiceSetup::plain_session_no_encrypted() + .await + .unwrap(); + + let result = Service::encrypted_with_connection(&setup.client_conn).await; + assert!( + result.is_err(), + "encrypted_with_connection should fail on a plain-only server" + ); +} + +#[tokio::test] +#[cfg(feature = "tokio")] +async fn plain_on_plain_only_server() { + let setup = oo7_daemon::tests::TestServiceSetup::plain_session_no_encrypted() + .await + .unwrap(); + + let service = Service::plain_with_connection(&setup.client_conn).await; + assert!( + service.is_ok(), + "plain_with_connection should work on a plain-only server, got: {service:?}" + ); +} + +#[tokio::test] +#[cfg(feature = "tokio")] +async fn new_with_connection_on_full_server() { + let setup = oo7_daemon::tests::TestServiceSetup::encrypted_session(true) + .await + .unwrap(); + + // Should succeed with encrypted (no fallback needed). + let service = Service::new_with_connection(&setup.client_conn).await; + assert!( + service.is_ok(), + "new_with_connection should work on a full server, got: {service:?}" + ); +} + #[tokio::test] #[cfg(feature = "tokio")] async fn create_collection() { diff --git a/server/src/service/mod.rs b/server/src/service/mod.rs index 46f8fc20..4bd24335 100644 --- a/server/src/service/mod.rs +++ b/server/src/service/mod.rs @@ -71,6 +71,8 @@ pub struct Service { pub(crate) pam_socket: Option, // Override for prompter type (mainly for tests) pub(crate) prompter_type_override: Arc>>, + // Whether encrypted sessions are supported + encrypted_supported: bool, } #[zbus::interface(name = "org.freedesktop.Secret.Service")] @@ -85,6 +87,14 @@ impl Service { ) -> Result<(OwnedVariant, OwnedObjectPath), ServiceError> { let (public_key, aes_key) = match algorithm { Algorithm::Plain => (None, None), + Algorithm::Encrypted if !self.encrypted_supported => { + return Err(ServiceError::ZBus(zbus::Error::FDO(Box::new( + zbus::fdo::Error::NotSupported( + "Algorithm dh-ietf1024-sha256-aes128-cbc-pkcs7 is not supported" + .to_string(), + ), + )))); + } Algorithm::Encrypted => { let client_public_key = Key::try_from(input).map_err(|err| { custom_service_error(&format!( @@ -598,6 +608,7 @@ impl Service { pub(crate) fn new( data_dir: std::path::PathBuf, pam_socket: Option, + encrypted_supported: bool, ) -> Self { Self { collections: Arc::new(Mutex::new(HashMap::new())), @@ -612,6 +623,7 @@ impl Service { data_dir, pam_socket, prompter_type_override: Arc::new(Mutex::new(None)), + encrypted_supported, } } @@ -640,7 +652,7 @@ impl Service { // Compute PAM socket path from environment variable let pam_socket = std::env::var_os("OO7_PAM_SOCKET").map(std::path::PathBuf::from); - let service = Self::new(data_dir, pam_socket); + let service = Self::new(data_dir, pam_socket, true); // Start PAM listener early so it can buffer secrets arriving before // D-Bus is ready (e.g. during PAM-initiated login startup). @@ -709,8 +721,9 @@ impl Service { data_dir: std::path::PathBuf, pam_socket: Option, secret: Option, + encrypted_supported: bool, ) -> Result { - let service = Self::new(data_dir, pam_socket); + let service = Self::new(data_dir, pam_socket, encrypted_supported); // Serve the service at the standard path connection diff --git a/server/src/service/tests.rs b/server/src/service/tests.rs index 89e0729f..1827caee 100644 --- a/server/src/service/tests.rs +++ b/server/src/service/tests.rs @@ -1086,7 +1086,7 @@ async fn complete_collection_creation_no_pending() -> Result<(), Box Result<(), Box> { // Set up a temporary data directory let temp_dir = tempfile::tempdir()?; - let service = Service::new(temp_dir.path().to_path_buf(), None); + let service = Service::new(temp_dir.path().to_path_buf(), None, true); // Create v1 keyrings directory let v1_dir = temp_dir.path().join("keyrings/v1"); @@ -1314,7 +1314,7 @@ async fn unlock_pending_v0_migration_impl( #[tokio::test] async fn discover_v0_keyrings() -> Result<(), Box> { let temp_dir = tempfile::tempdir()?; - let service = Service::new(temp_dir.path().to_path_buf(), None); + let service = Service::new(temp_dir.path().to_path_buf(), None, true); let keyrings_dir = temp_dir.path().join("keyrings"); let v1_dir = keyrings_dir.join("v1"); @@ -1401,7 +1401,7 @@ async fn discover_v0_keyrings() -> Result<(), Box> { #[tokio::test] async fn discover_plain_v0_keyrings() -> Result<(), Box> { let temp_dir = tempfile::tempdir()?; - let service = Service::new(temp_dir.path().to_path_buf(), None); + let service = Service::new(temp_dir.path().to_path_buf(), None, true); let keyrings_dir = temp_dir.path().join("keyrings"); tokio::fs::create_dir_all(keyrings_dir.join("v1")).await?; @@ -1434,7 +1434,7 @@ async fn discover_plain_v0_keyrings() -> Result<(), Box> #[tokio::test] async fn discover_kwallet_keyrings() -> Result<(), Box> { let temp_dir = tempfile::tempdir()?; - let service = Service::new(temp_dir.path().to_path_buf(), None); + let service = Service::new(temp_dir.path().to_path_buf(), None, true); let kwallet_dir = temp_dir.path().join("kwalletd"); let v1_dir = temp_dir.path().join("keyrings/v1"); diff --git a/server/src/tests.rs b/server/src/tests.rs index ce4c2899..aaaca369 100644 --- a/server/src/tests.rs +++ b/server/src/tests.rs @@ -14,7 +14,7 @@ use crate::gnome::{ use crate::service::{PrompterType, Service}; /// Helper to create a peer-to-peer connection pair using Unix socket -async fn create_p2p_connection() +pub(crate) async fn create_p2p_connection() -> Result<(zbus::Connection, zbus::Connection), Box> { let guid = zbus::Guid::generate(); let (p0, p1) = UnixStream::pair()?; @@ -32,6 +32,17 @@ async fn create_p2p_connection() Ok((server_conn, client_conn)) } +struct ServerSetup { + server: Service, + client_conn: zbus::Connection, + service_api: dbus::api::Service, + #[cfg(any(feature = "gnome_native_crypto", feature = "gnome_openssl_crypto"))] + mock_prompter: MockPrompterService, + #[cfg(any(feature = "plasma_native_crypto", feature = "plasma_openssl_crypto"))] + mock_prompter_plasma: MockPrompterServicePlasma, + temp_dir: tempfile::TempDir, +} + pub struct TestServiceSetup { pub server: Service, pub client_conn: zbus::Connection, @@ -69,28 +80,23 @@ impl TestServiceSetup { Err("Default collection not found".into()) } - pub async fn plain_session( - with_default_collection: bool, - ) -> Result> { + async fn setup_server( + secret: Option, + encrypted_supported: bool, + ) -> Result> { let (server_conn, client_conn) = create_p2p_connection().await?; - let secret = if with_default_collection { - Some(Secret::from("test-password-long-enough")) - } else { - None - }; - let temp_dir = tempfile::TempDir::new()?; let server = Service::run_with_connection( server_conn.clone(), temp_dir.path().to_path_buf(), None, - secret.clone(), + secret, + encrypted_supported, ) .await?; server.set_prompter_type(PrompterType::GNOME).await; - // Create and serve the mock prompter #[cfg(any(feature = "gnome_native_crypto", feature = "gnome_openssl_crypto"))] let mock_prompter = { let mock_prompter = MockPrompterService::new(); @@ -110,25 +116,14 @@ impl TestServiceSetup { mock_prompter_plasma }; - // Give the server a moment to fully initialize tokio::time::sleep(tokio::time::Duration::from_millis(50)).await; let service_api = dbus::api::Service::new(&client_conn).await?; - let (server_public_key, session) = service_api.open_session(None).await?; - let session = Arc::new(session); - - let collections = service_api.collections().await?; - - Ok(TestServiceSetup { + Ok(ServerSetup { server, - keyring_secret: secret, client_conn, service_api, - session, - collections, - server_public_key, - aes_key: None, #[cfg(any(feature = "gnome_native_crypto", feature = "gnome_openssl_crypto"))] mock_prompter, #[cfg(any(feature = "plasma_native_crypto", feature = "plasma_openssl_crypto"))] @@ -137,80 +132,100 @@ impl TestServiceSetup { }) } - pub async fn encrypted_session( + pub async fn plain_session( with_default_collection: bool, ) -> Result> { - let (server_conn, client_conn) = create_p2p_connection().await?; - let secret = if with_default_collection { Some(Secret::from("test-password-long-enough")) } else { None }; - let temp_dir = tempfile::TempDir::new()?; - let server = Service::run_with_connection( - server_conn.clone(), - temp_dir.path().to_path_buf(), - None, - secret.clone(), - ) - .await?; - server.set_prompter_type(PrompterType::GNOME).await; + let s = Self::setup_server(secret.clone(), true).await?; - // Create and serve the mock prompter - #[cfg(any(feature = "gnome_native_crypto", feature = "gnome_openssl_crypto"))] - let mock_prompter = { - let mock_prompter = MockPrompterService::new(); - client_conn - .object_server() - .at("/org/gnome/keyring/Prompter", mock_prompter.clone()) - .await?; - mock_prompter - }; + let (server_public_key, session) = s.service_api.open_session(None).await?; + let collections = s.service_api.collections().await?; - #[cfg(any(feature = "plasma_native_crypto", feature = "plasma_openssl_crypto"))] - let mock_prompter_plasma = { - let mock_prompter_plasma = MockPrompterServicePlasma::new(); - client_conn - .object_server() - .at("/SecretPrompter", mock_prompter_plasma.clone()) - .await?; - mock_prompter_plasma - }; + Ok(TestServiceSetup { + server: s.server, + keyring_secret: secret, + client_conn: s.client_conn, + service_api: s.service_api, + session: Arc::new(session), + collections, + server_public_key, + aes_key: None, + #[cfg(any(feature = "gnome_native_crypto", feature = "gnome_openssl_crypto"))] + mock_prompter: s.mock_prompter, + #[cfg(any(feature = "plasma_native_crypto", feature = "plasma_openssl_crypto"))] + mock_prompter_plasma: s.mock_prompter_plasma, + temp_dir: s.temp_dir, + }) + } - // Give the server a moment to fully initialize - tokio::time::sleep(tokio::time::Duration::from_millis(50)).await; + #[allow(unused)] + pub async fn plain_session_no_encrypted() -> Result> + { + let secret = Some(Secret::from("test-password-long-enough")); - let service_api = dbus::api::Service::new(&client_conn).await?; + let s = Self::setup_server(secret.clone(), false).await?; + + let (server_public_key, session) = s.service_api.open_session(None).await?; + let collections = s.service_api.collections().await?; + + Ok(TestServiceSetup { + server: s.server, + keyring_secret: secret, + client_conn: s.client_conn, + service_api: s.service_api, + session: Arc::new(session), + collections, + server_public_key, + aes_key: None, + #[cfg(any(feature = "gnome_native_crypto", feature = "gnome_openssl_crypto"))] + mock_prompter: s.mock_prompter, + #[cfg(any(feature = "plasma_native_crypto", feature = "plasma_openssl_crypto"))] + mock_prompter_plasma: s.mock_prompter_plasma, + temp_dir: s.temp_dir, + }) + } + + pub async fn encrypted_session( + with_default_collection: bool, + ) -> Result> { + let secret = if with_default_collection { + Some(Secret::from("test-password-long-enough")) + } else { + None + }; + + let s = Self::setup_server(secret.clone(), true).await?; - // Generate client key pair for encrypted session let client_private_key = oo7::Key::generate_private_key()?; let client_public_key = oo7::Key::generate_public_key(&client_private_key)?; let (server_public_key, session) = - service_api.open_session(Some(client_public_key)).await?; - let session = Arc::new(session); + s.service_api.open_session(Some(client_public_key)).await?; let aes_key = oo7::Key::generate_aes_key(&client_private_key, server_public_key.as_ref().unwrap())?; - let collections = service_api.collections().await?; + let collections = s.service_api.collections().await?; Ok(Self { - server, + server: s.server, keyring_secret: secret, - client_conn, - service_api, - session, + client_conn: s.client_conn, + service_api: s.service_api, + session: Arc::new(session), collections, server_public_key, aes_key: Some(Arc::new(aes_key)), #[cfg(any(feature = "gnome_native_crypto", feature = "gnome_openssl_crypto"))] - mock_prompter, + mock_prompter: s.mock_prompter, #[cfg(any(feature = "plasma_native_crypto", feature = "plasma_openssl_crypto"))] - mock_prompter_plasma, - temp_dir, + mock_prompter_plasma: s.mock_prompter_plasma, + temp_dir: s.temp_dir, }) } @@ -226,7 +241,7 @@ impl TestServiceSetup { let (server_conn, client_conn) = create_p2p_connection().await?; let temp_dir = tempfile::TempDir::new()?; - let service = crate::Service::new(data_dir, pam_socket); + let service = crate::Service::new(data_dir, pam_socket, true); server_conn .object_server()