Skip to content

New secret service#125

Merged
infeo merged 23 commits intocryptomator:developfrom
purejava:new-secret-service
Feb 10, 2026
Merged

New secret service#125
infeo merged 23 commits intocryptomator:developfrom
purejava:new-secret-service

Conversation

@purejava
Copy link
Contributor

@purejava purejava commented Oct 27, 2025

This replaces GnomeKeyringKeychainAccess and KDEWalletKeychainAccess with a new KeychainAccess, that implements the latest Secret Service API Draft 0.2.

So a single keychain interface can be used on Linux. kwallet implements the Secret Service API since version v5.97.0, which was released in August 2022.

SecretServiceKeychainAccess works ootb with existing GNOME keyring entries, former KDE wallet entries get migrated.

Refers to cryptomator/cryptomator#3979.

@coderabbitai
Copy link

coderabbitai bot commented Oct 27, 2025

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

Adds a new SecretServiceKeychainAccess implementation and registers it as a KeychainAccessProvider; updates module-info to require org.purejava.secret and imports the new class; adds org.purejava:secret-service to Maven properties/dependencies; registers the new provider in META-INF/services; introduces JUnit tests for the Secret Service backend (environment-gated, functional CRUD tests); marks GnomeKeyringKeychainAccess and KDEWalletKeychainAccess as deprecated. No other behavior outside the new Secret Service integration and deprecation metadata was changed.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related issues

  • #3979: Merge keychain backends for KDE and GNOME — this PR implements a Secret Service backend intended to consolidate KDE Wallet and GNOME Keyring support.

Possibly related PRs

  • integrations-linux#82: Changes to SecretServiceKeychainAccess (LOG finalization and added exception logging/guarding in isSupported) indicate a direct code-level follow-up to this new implementation.

Suggested labels

bugfix

Suggested reviewers

  • overheadhunter
  • infeo
🚥 Pre-merge checks | ✅ 3 | ❌ 2
❌ Failed checks (1 warning, 1 inconclusive)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The title 'New secret service' is vague and generic, lacking specificity about what the change accomplishes or which component is being replaced. Revise the title to be more specific, such as 'Implement Secret Service keychain backend to replace GNOME and KDE implementations' to clearly indicate the main change.
✅ Passed checks (3 passed)
Check name Status Explanation
Linked Issues check ✅ Passed The PR successfully implements the Secret Service library compatible with both KDE kwallet and GNOME keyring, consolidating two backends into one, enabling DBus dependency upgrades, and deprecating old implementations.
Out of Scope Changes check ✅ Passed All changes are directly scoped to implementing the Secret Service backend, deprecating old implementations, adding tests, and updating dependencies; no unrelated modifications detected.
Description check ✅ Passed The pull request description clearly relates to the changeset, explaining the replacement of deprecated keychain access implementations with a new Secret Service API-based implementation.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (11)
src/main/java/org/cryptomator/linux/keychain/SecretServiceKeychainAccess.java (6)

35-49: Avoid side effects in constructor; defer alias setup and migration.

Constructor sets alias and migrates immediately, which can trigger unlock prompts at startup. Defer to first safe point (e.g., after isSupported() succeeds and store/load/delete is invoked) and gate with a once-only flag.

@@
-    public SecretServiceKeychainAccess() {
+    private static final java.util.concurrent.atomic.AtomicBoolean MIGRATED = new java.util.concurrent.atomic.AtomicBoolean(false);
+
+    public SecretServiceKeychainAccess() {
         session.getService().addCollectionChangedHandler(collection -> LOG.debug("Collection {} changed", collection.getPath()));
@@
-        var getAlias = session.getService().readAlias("default");
-        if (getAlias.isSuccess() && "/".equals(getAlias.value().getPath())) {
-            // default alias is not set; set it to the login keyring
-            session.getService().setAlias("default", new DBusPath(Static.DBusPath.LOGIN_COLLECTION));
-        }
+        // Defer alias setup until session is established
@@
-        migrateKDEWalletEntries();
+        // Defer migration; run once after session is ready (see isSupported())
     }
+
+    private void ensurePostInit() {
+        // Safe to call often
+        var getAlias = session.getService().readAlias("default");
+        if (getAlias.isSuccess() && "/".equals(getAlias.value().getPath())) {
+            session.getService().setAlias("default", new DBusPath(Static.DBusPath.LOGIN_COLLECTION));
+        }
+        if (MIGRATED.compareAndSet(false, true)) {
+            migrateKDEWalletEntries();
+        }
+    }

77-96: Make load resilient; ensure session and post-init.

Prevent dependency on external initialization.

@@
     public char[] loadPassphrase(String key) throws KeychainAccessException {
         try {
+            if (!session.setupEncryptedSession()) {
+                throw new KeychainAccessException("Loading password failed: unable to establish encrypted session.");
+            }
+            ensurePostInit();
             var call = collection.searchItems(createAttributes(key));

98-121: Same initialization concerns in deletePassphrase.

Mirror the session/post-init checks.

@@
     public void deletePassphrase(String key) throws KeychainAccessException {
         try {
+            if (!session.setupEncryptedSession()) {
+                throw new KeychainAccessException("Deleting password failed: unable to establish encrypted session.");
+            }
+            ensurePostInit();
             var call = collection.searchItems(createAttributes(key));

123-146: Same initialization concerns in changePassphrase.

Add session handshake and post-init.

@@
     public void changePassphrase(String key, String displayName, CharSequence passphrase) throws KeychainAccessException {
         try {
+            if (!session.setupEncryptedSession()) {
+                throw new KeychainAccessException("Updating password failed: unable to establish encrypted session.");
+            }
+            ensurePostInit();
             var call = collection.searchItems(createAttributes(key));

154-158: isLocked should tolerate uninitialized session.

Optional, but avoids false negatives.

@@
     public boolean isLocked() {
-        var call = collection.isLocked();
+        session.setupEncryptedSession();
+        ensurePostInit();
+        var call = collection.isLocked();
         return call.isSuccess() && call.value();
     }

164-191: Migration hygiene: avoid String for secrets, wipe char[], and check delete result.

Reduce exposure of secrets and handle errors explicitly.

@@
-        var getItems = collection.getItems();
+        var getItems = collection.getItems();
         if (getItems.isSuccess() && !getItems.value().isEmpty()) {
             for (DBusPath i : getItems.value()) {
                 session.getService().ensureUnlocked(i);
                 var attribs = new Item(i).getAttributes();
                 if (attribs.isSuccess() &&
                         attribs.value().containsKey("server") &&
                         attribs.value().containsKey("user") &&
                         attribs.value().get("server").equals("Cryptomator")) {
@@
-                    var item = new Item(i);
-                    var secret = item.getSecret(session.getSession());
-                    try {
-                        storePassphrase(attribs.value().get("user"), "Cryptomator", new String(session.decrypt(secret)));
-                    } catch (KeychainAccessException | NoSuchPaddingException | NoSuchAlgorithmException |
-                             InvalidAlgorithmParameterException | InvalidKeyException | BadPaddingException |
-                             IllegalBlockSizeException e) {
-                        LOG.error("Migrating entry {} for vault {} failed", i.getPath(), attribs.value().get("user"));
-                     }
-                     item.delete();
-                     LOG.info("Successfully migrated password for vault {}", attribs.value().get("user"));
+                    var item = new Item(i);
+                    var secret = item.getSecret(session.getSession());
+                    char[] decrypted = null;
+                    try {
+                        decrypted = session.decrypt(secret);
+                        storePassphrase(attribs.value().get("user"), "Cryptomator", java.nio.CharBuffer.wrap(decrypted));
+                        var deleted = item.delete();
+                        if (!deleted.isSuccess()) {
+                            LOG.warn("Migrated but failed to delete legacy entry {} for vault {}", i.getPath(), attribs.value().get("user"));
+                        } else {
+                            LOG.info("Successfully migrated password for vault {}", attribs.value().get("user"));
+                        }
+                    } catch (KeychainAccessException | NoSuchPaddingException | NoSuchAlgorithmException |
+                             InvalidAlgorithmParameterException | InvalidKeyException | BadPaddingException |
+                             IllegalBlockSizeException e) {
+                        LOG.error("Migrating entry {} for vault {} failed", i.getPath(), attribs.value().get("user"), e);
+                    } finally {
+                        if (decrypted != null) {
+                            java.util.Arrays.fill(decrypted, '\0');
+                        }
+                    }
                 }
             }
         }
src/test/java/org/cryptomator/linux/keychain/SecretServiceKeychainAccessTest.java (5)

22-24: Avoid DISPLAY gate; rely on actual D‑Bus availability.

DISPLAY may not be set on Wayland/headless although org.freedesktop.secrets is available. Prefer enabling based on the service probe.

-@EnabledIfEnvironmentVariable(named = "DISPLAY", matches = ".*")
-public class SecretServiceKeychainAccessTest {
+public class SecretServiceKeychainAccessTest {

27-41: Do not throw from @BeforeAll; catch and mark unavailable.

Throwing IOException from @BeforeAll can fail the whole class on environments without dbus-send/grep.

-    @BeforeAll
-    public static void checkSystemAndSetup() throws IOException {
+    @BeforeAll
+    public static void checkSystemAndSetup() {
         ProcessBuilder dbusSend = new ProcessBuilder("dbus-send", "--print-reply", "--dest=org.freedesktop.DBus", "/org/freedesktop/DBus", "org.freedesktop.DBus.ListNames");
         ProcessBuilder grep = new ProcessBuilder("grep", "-q", "org.freedesktop.secrets");
         try {
             Process end = ProcessBuilder.startPipeline(List.of(dbusSend, grep)).get(1);
             if (end.waitFor(1000, TimeUnit.MILLISECONDS)) {
                 isInstalled = end.exitValue() == 0;
             } else {
                 isInstalled = false;
             }
-        } catch (InterruptedException e) {
+        } catch (InterruptedException e) {
             Thread.currentThread().interrupt();
+            isInstalled = false;
+        } catch (IOException e) {
+            isInstalled = false;
         }
     }

45-47: Assert supported state to surface misconfig early.

If the service is probed as installed, assert that isSupported() is true to catch alias/session issues.

-        var service = new SecretServiceKeychainAccess();
-        Assertions.assertEquals(isInstalled, service.isSupported());
+        var service = new SecretServiceKeychainAccess();
+        Assertions.assertEquals(isInstalled, service.isSupported(), "Secret Service installed state should match isSupported()");

51-56: KEYRING self‑init shouldn’t require manual warm‑up.

After provider changes to self‑initialize, this warm‑up call can be removed or asserted.

-        final static SecretServiceKeychainAccess KEYRING = new SecretServiceKeychainAccess();
+        final static SecretServiceKeychainAccess KEYRING = new SecretServiceKeychainAccess();

(Leave as-is for now; consider removing KEYRING.isSupported() below once provider self-inits.)


60-62: Turn warm‑up into an assertion (or remove after provider self‑inits).

-            KEYRING.isSupported(); // ensure encrypted session
+            Assertions.assertTrue(KEYRING.isSupported(), "Secret Service is not supported");
             KEYRING.storePassphrase(KEY_ID, "cryptomator-test", "p0ssw0rd");
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 34377f1 and 161ea15.

📒 Files selected for processing (8)
  • pom.xml (2 hunks)
  • src/main/java/module-info.java (2 hunks)
  • src/main/java/org/cryptomator/linux/keychain/GnomeKeyringKeychainAccess.java (0 hunks)
  • src/main/java/org/cryptomator/linux/keychain/KDEWalletKeychainAccess.java (0 hunks)
  • src/main/java/org/cryptomator/linux/keychain/SecretServiceKeychainAccess.java (1 hunks)
  • src/main/resources/META-INF/services/org.cryptomator.integrations.keychain.KeychainAccessProvider (1 hunks)
  • src/test/java/org/cryptomator/linux/keychain/KDEWalletKeychainAccessTest.java (0 hunks)
  • src/test/java/org/cryptomator/linux/keychain/SecretServiceKeychainAccessTest.java (2 hunks)
💤 Files with no reviewable changes (3)
  • src/main/java/org/cryptomator/linux/keychain/GnomeKeyringKeychainAccess.java
  • src/test/java/org/cryptomator/linux/keychain/KDEWalletKeychainAccessTest.java
  • src/main/java/org/cryptomator/linux/keychain/KDEWalletKeychainAccess.java
🔇 Additional comments (3)
src/main/resources/META-INF/services/org.cryptomator.integrations.keychain.KeychainAccessProvider (1)

1-1: Service registration aligned with the unified provider.

Matches module-info; good for classpath consumers. No further action.

pom.xml (1)

44-44: All verification checks passed—no issues found.

JPMS module coordination is correct:

  • org.purejava.secret module is properly declared in requires (module-info.java:18)
  • Service descriptor correctly points to SecretServiceKeychainAccess implementation
  • No stale references to KDEWalletKeychainAccess, GnomeKeyringKeychainAccess, de.swiesend.secretservice, or kwallet exist in the codebase
  • pom.xml dependency correctly updated to org.purejava:secret-service:1.0.0
src/main/java/module-info.java (1)

7-8: No module name mismatch found; code is correct as-is.

Verification confirms that requires org.purejava.secret; correctly matches the actual module name declared in the org.purejava:secret-service library. The library's module-info.java declares module org.purejava.secret, which aligns with your requires statement. Service wiring to SecretServiceKeychainAccess is properly configured in the provides clause.

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This pull request replaces the existing GNOME Keyring and KDE Wallet keychain implementations with a unified Secret Service implementation that adheres to the latest Secret Service API Draft 0.2. This allows for a single keychain interface on Linux, as KWallet has supported the Secret Service API since version v5.97.0 (August 2022). The new implementation maintains compatibility with existing GNOME keyring entries and includes automatic migration for former KDE wallet entries.

Key Changes:

  • Unified Secret Service implementation replacing separate GNOME Keyring and KDE Wallet providers
  • Automatic migration of KDE Wallet entries to the new Secret Service format
  • Updated dependencies from de.swiesend:secret-service:2.0.1-alpha and org.purejava:kdewallet:1.4.0 to org.purejava:secret-service:1.0.0

Reviewed Changes

Copilot reviewed 8 out of 8 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
SecretServiceKeychainAccess.java New unified keychain implementation using Secret Service API with KDE Wallet entry migration
GnomeKeyringKeychainAccess.java Removed old GNOME Keyring implementation
KDEWalletKeychainAccess.java Removed old KDE Wallet implementation
SecretServiceKeychainAccessTest.java Updated tests to use new Secret Service implementation
KDEWalletKeychainAccessTest.java Removed KDE Wallet-specific tests
module-info.java Updated module to reference new Secret Service provider and dependency
pom.xml Updated to use single org.purejava:secret-service:1.0.0 dependency
META-INF/services/org.cryptomator.integrations.keychain.KeychainAccessProvider Updated service provider configuration to reference unified implementation

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@overheadhunter
Copy link
Member

Haven't looked into the details yet, but I would like to keep the old implementations (mark them as deprecated) and remove them in a separate PR.

This way we can release a version where in case of a bug the user can switch back to the old impl. If everything went smooth, the next Cryptomator release will then drop these.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 161ea15 and b9c6eb1.

📒 Files selected for processing (5)
  • pom.xml (2 hunks)
  • src/main/java/module-info.java (2 hunks)
  • src/main/java/org/cryptomator/linux/keychain/KDEWalletKeychainAccess.java (1 hunks)
  • src/main/resources/META-INF/services/org.cryptomator.integrations.keychain.KeychainAccessProvider (1 hunks)
  • src/test/java/org/cryptomator/linux/keychain/GnomeKeyringKeychainAccessTest.java (1 hunks)
✅ Files skipped from review due to trivial changes (2)
  • src/test/java/org/cryptomator/linux/keychain/GnomeKeyringKeychainAccessTest.java
  • src/main/java/org/cryptomator/linux/keychain/KDEWalletKeychainAccess.java
🚧 Files skipped from review as they are similar to previous changes (1)
  • pom.xml
🔇 Additional comments (2)
src/main/resources/META-INF/services/org.cryptomator.integrations.keychain.KeychainAccessProvider (1)

1-3: LGTM! Provider ordering and backwards compatibility look good.

The new SecretServiceKeychainAccess is correctly registered as the first provider, which means it will be discovered first by Java's ServiceLoader mechanism. Keeping the existing KDE Wallet and GNOME Keyring providers ensures backwards compatibility, aligning with the maintainer's suggestion to retain old implementations.

src/main/java/module-info.java (1)

8-9: LGTM! Import additions are correct.

The new SecretServiceKeychainAccess import is properly added to support the new provider registration.

@purejava

This comment was marked as outdated.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b9c6eb1 and 1c54f97.

📒 Files selected for processing (1)
  • src/main/java/module-info.java (2 hunks)
🔇 Additional comments (2)
src/main/java/module-info.java (2)

8-9: LGTM: Import statements updated correctly.

The new SecretServiceKeychainAccess import is properly added to support the unified Linux keychain backend.


26-26: LGTM: Provider registration order is correct.

The provider list correctly places SecretServiceKeychainAccess first, making it the preferred implementation while retaining GnomeKeyringKeychainAccess and KDEWalletKeychainAccess for backward compatibility. This aligns with the maintainer's guidance to keep old implementations available initially.

Note: As flagged in past review comments, the old implementations should be marked with @Deprecated annotations in their respective class files.

@purejava purejava marked this pull request as draft October 28, 2025 06:11
Give it a higher priority
Use latest Secret Service library
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (3)
src/main/java/module-info.java (1)

31-31: Verify that old keychain implementations are marked @Deprecated.

The provider list correctly prioritizes SecretServiceKeychainAccess first, with the old implementations (GnomeKeyringKeychainAccess, KDEWalletKeychainAccess) retained for backwards compatibility. Per the maintainer's request in the PR comments, these old implementations should be marked with @Deprecated annotations before removal in a future release.

#!/bin/bash
# Check if old keychain implementations have @Deprecated annotations

echo "=== Checking GnomeKeyringKeychainAccess ==="
if rg -q "@Deprecated" src/main/java/org/cryptomator/linux/keychain/GnomeKeyringKeychainAccess.java; then
    echo "✓ GnomeKeyringKeychainAccess is marked @Deprecated"
else
    echo "✗ GnomeKeyringKeychainAccess is NOT marked @Deprecated"
fi

echo ""
echo "=== Checking KDEWalletKeychainAccess ==="
if rg -q "@Deprecated" src/main/java/org/cryptomator/linux/keychain/KDEWalletKeychainAccess.java; then
    echo "✓ KDEWalletKeychainAccess is marked @Deprecated"
else
    echo "✗ KDEWalletKeychainAccess is NOT marked @Deprecated"
fi
src/test/java/org/cryptomator/linux/keychain/SecretServiceKeychainAccessTest.java (1)

22-22: Class-level DISPLAY gate is too restrictive; rely on nested class gate instead.

The @EnabledIfEnvironmentVariable(named = "DISPLAY", matches = ".*") annotation at the class level prevents testIsSupported() from running even though it uses dbus-send CLI and doesn't require a display. The nested FunctionalTests class already has @EnabledIf("serviceAvailableAndUnlocked"), which comprehensively checks both service availability and lock state.

Remove the class-level DISPLAY gate to allow testIsSupported() to run in headless environments while functional tests remain properly gated.

🔎 Proposed fix
 /**
  * Unit tests for Secret Service access via Dbus.
  */
-@EnabledIfEnvironmentVariable(named = "DISPLAY", matches = ".*")
 public class SecretServiceKeychainAccessTest {
src/main/java/org/cryptomator/linux/keychain/SecretServiceKeychainAccess.java (1)

177-194: Risk of data loss if migration fails after deletion.

Line 186 deletes the original KDE Wallet item before line 188 confirms that storePassphrase succeeded. If storing the migrated passphrase fails (network issue, permissions, disk full, etc.), the original data is permanently lost.

Per the maintainer's earlier comment, deletion should only occur after successful migration. The current code structure has improved but still deletes before the final store completes.

🔎 Recommended fix: Move deletion after successful migration
 					var itemProps = Item.createProperties(label, newAttribs);
 					var replace = collection.createItem(itemProps, secret, true);
 					if (!replace.isSuccess()) {
 						LOG.error("Replacing migrated item {} failed: {}", i.getPath(), replace.error());
 						continue;
 					}
-					item.delete();
  					try {
 						storePassphrase(attribs.value().get("user"), "Cryptomator", new String(session.decrypt(secret)));
+						item.delete();
 						LOG.info("Successfully migrated password for vault {}", attribs.value().get("user"));
 					} catch (KeychainAccessException | NoSuchPaddingException | NoSuchAlgorithmException |
 							 InvalidAlgorithmParameterException | InvalidKeyException | BadPaddingException |
 							 IllegalBlockSizeException e) {
 						LOG.error("Migrating entry {} for vault {} failed", i.getPath(), attribs.value().get("user"));
 					 }
🧹 Nitpick comments (1)
src/main/java/org/cryptomator/linux/keychain/SecretServiceKeychainAccess.java (1)

36-50: Consider deferring migration to first use rather than constructor.

Running migrateKDEWalletEntries() in the constructor can cause the constructor to fail or block for an extended time if migration encounters issues. This impacts application startup and makes error handling more difficult.

Consider deferring migration to the first keychain operation (e.g., lazy initialization pattern) or providing a separate initialize() method that callers can invoke explicitly with appropriate error handling.

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2366a34 and 857c812.

📒 Files selected for processing (7)
  • pom.xml (2 hunks)
  • src/main/java/module-info.java (2 hunks)
  • src/main/java/org/cryptomator/linux/keychain/KDEWalletKeychainAccess.java (1 hunks)
  • src/main/java/org/cryptomator/linux/keychain/SecretServiceKeychainAccess.java (1 hunks)
  • src/main/resources/META-INF/services/org.cryptomator.integrations.keychain.KeychainAccessProvider (1 hunks)
  • src/test/java/org/cryptomator/linux/keychain/GnomeKeyringKeychainAccessTest.java (1 hunks)
  • src/test/java/org/cryptomator/linux/keychain/SecretServiceKeychainAccessTest.java (1 hunks)
✅ Files skipped from review due to trivial changes (1)
  • src/main/java/org/cryptomator/linux/keychain/KDEWalletKeychainAccess.java
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/test/java/org/cryptomator/linux/keychain/GnomeKeyringKeychainAccessTest.java
  • src/main/resources/META-INF/services/org.cryptomator.integrations.keychain.KeychainAccessProvider
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2024-07-16T22:36:32.769Z
Learnt from: infeo
Repo: cryptomator/integrations-linux PR: 80
File: src/main/java/org/cryptomator/linux/autostart/FreedesktopAutoStartService.java:56-62
Timestamp: 2024-07-16T22:36:32.769Z
Learning: For the `FreedesktopAutoStartService` in the Cryptomator project, exceptions are preferred to contain all necessary debugging information without additional logging before throwing them.

Applied to files:

  • src/main/java/module-info.java
🔇 Additional comments (1)
pom.xml (1)

107-111: Verify that org.purejava:secret-service:1.0.1 is published and accessible.

This PR adds org.purejava:secret-service version 1.0.1 alongside de.swiesend:secret-service version 2.0.1-alpha. Prior reviews indicated version 1.0.0 was unavailable on Maven Central. Confirm version 1.0.1 is published before merging to prevent build failures.

@purejava
Copy link
Contributor Author

purejava commented Jan 1, 2026

I did another test from top to toe and noticed a corner case, where an Item on the DBus provided by gnome-keyring suddenly disappears, after all Items from the collection have been retrieved by collection.getItems().

This in the end kept the SecretServiceKeychainAccess from being loaded and is fixed with the following commit. Now, these occurrences are simply logged as a warning:

16:23:08.290 [JavaFX Application Thread] INFO  o.f.d.c.transports.TransportBuilder - Using transport dbus-java-transport-native-unixsocket for address unix:path=/run/user/1000/bus
16:23:08.357 [JavaFX Application Thread] WARN  org.purejava.secret.api.Item - DBus error on calling Get(Locked) for /org/freedesktop/secrets/collection/login/7: Das Objekt existiert nicht am Pfad »/org/freedesktop/secrets/collection/login/7«
16:23:08.358 [JavaFX Application Thread] WARN  org.purejava.secret.api.Item - DBus error on calling Get(Attributes) for /org/freedesktop/secrets/collection/login/7: Das Objekt existiert nicht am Pfad »/org/freedesktop/secrets/collection/login/7«
16:23:08.374 [JavaFX Application Thread] INFO  o.f.d.c.transports.TransportBuilder - Using transport dbus-java-transport-native-unixsocket for address unix:path=/run/user/1000/bus

Copy link
Member

@infeo infeo left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I finally had the time to review. Will test it in the next days and hopefully this will be integrated into the next Cryptomator release.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In
`@src/test/java/org/cryptomator/linux/keychain/SecretServiceKeychainAccessTest.java`:
- Around line 29-37: The pipeline started via
ProcessBuilder.startPipeline(List.of(dbusSend, grep)) can leave child processes
running when end.waitFor(1000, TimeUnit.MILLISECONDS) times out; change the code
to capture the full Process[] returned by startPipeline, call waitFor on the
final process as before, and if it times out iterate over all processes (from
the Process[]), call destroy() then destroyForcibly() if needed and waitFor
their termination to ensure no stray dbus-send/grep remain; reference the
variables ProcessBuilder dbusSend, grep, the Process[] from
ProcessBuilder.startPipeline and the Process end when implementing the cleanup
on timeout.
🧹 Nitpick comments (1)
src/test/java/org/cryptomator/linux/keychain/SecretServiceKeychainAccessTest.java (1)

22-23: Add an OS gate to prevent non-Linux runs.

This test suite is Linux-specific; add @EnabledOnOs(OS.LINUX) to avoid accidental execution on other platforms where DBus behavior is undefined.

🔧 Suggested change
 import org.junit.jupiter.api.condition.EnabledIf;
 import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable;
+import org.junit.jupiter.api.condition.EnabledOnOs;
+import org.junit.jupiter.api.condition.OS;
@@
 `@EnabledIfEnvironmentVariable`(named = "DBUS_SESSION_BUS_ADDRESS", matches = ".*")
+@EnabledOnOs(OS.LINUX)
 public class SecretServiceKeychainAccessTest {

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In
`@src/main/java/org/cryptomator/linux/keychain/SecretServiceKeychainAccess.java`:
- Around line 154-158: The isLocked() method in SecretServiceKeychainAccess uses
collection.isLocked() and currently returns call.isSuccess() && call.value(),
which yields false on errors; change it to fail-safe and return true when
call.isSuccess() is false (i.e., treat unknown/error as locked) so it matches
GnomeKeyringKeychainAccess/KDEWalletKeychainAccess behavior — update the logic
in SecretServiceKeychainAccess.isLocked() to return call.value() when successful
and true when not.
- Around line 111-114: SecretServiceKeychainAccess currently throws a
KeychainAccessException when deletePassphrase cannot find the item ("Vault {key}
not found, deleting failed"), which is inconsistent with
GnomeKeyringKeychainAccess and KDEWalletKeychainAccess that treat missing items
as a no-op; make delete idempotent by converting the "not found" branch in
SecretServiceKeychainAccess (the block that currently builds msg and throws
KeychainAccessException) to silently succeed (optionally log at debug/trace
level) and return normally so all implementations consistently no-op on missing
keys, or alternatively document and centralize behavior if you prefer strict
semantics across all KeychainAccess implementations.

Return an unknown state as locked (fail-safe)
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In
`@src/main/java/org/cryptomator/linux/keychain/GnomeKeyringKeychainAccess.java`:
- Around line 16-22: The Javadoc version and the `@Deprecated` annotation in
GnomeKeyringKeychainAccess are inconsistent (Javadoc "since version 1.19.0" vs
`@Deprecated`(since = "1.7.0")); update one so both match—e.g., change the
`@Deprecated`(since = "...") value to "1.19.0" (or adjust the Javadoc to "1.7.0"
if that is the intended baseline) so the class GnomeKeyringKeychainAccess and
its deprecation metadata are consistent with KDEWalletKeychainAccess.

In `@src/main/java/org/cryptomator/linux/keychain/KDEWalletKeychainAccess.java`:
- Around line 22-28: The Javadoc deprecation tag and the `@Deprecated`(since =
"...") annotation on KDEWalletKeychainAccess disagree (Javadoc says 1.19.0,
annotation says 1.7.0); update them to be consistent by either changing the
Javadoc to reference the same version scheme as the annotation or update the
`@Deprecated`(since = "...") value to match the Javadoc, or alternatively add a
brief clarifying note in the Javadoc explaining the two different versioning
schemes (integrations-linux vs. Cryptomator) and then align the
`@Deprecated`(since) value accordingly so both the Javadoc and the `@Deprecated`
annotation convey the same, unambiguous deprecation version for
KDEWalletKeychainAccess.
🧹 Nitpick comments (1)
src/main/java/org/cryptomator/linux/keychain/SecretServiceKeychainAccess.java (1)

72-74: Exception double-wrapping: KeychainAccessException gets re-wrapped.

The catch (Exception e) block catches KeychainAccessException instances thrown at lines 64 and 70, wrapping them in another KeychainAccessException. This creates nested exceptions with similar messages, making debugging harder.

♻️ Proposed fix to rethrow already-wrapped exceptions
-		} catch (Exception e) {
+		} catch (KeychainAccessException e) {
+			throw e;
+		} catch (Exception e) {
 			throw new KeychainAccessException("Storing password failed.", e);
 		}

This same pattern appears in loadPassphrase, deletePassphrase, and changePassphrase—consider applying the fix consistently across all methods.

@infeo
Copy link
Member

infeo commented Feb 6, 2026

@purejava which version of kWallet or GnomeKeyring is needed in order to test the new keychain backend?

@purejava
Copy link
Contributor Author

purejava commented Feb 6, 2026

@purejava which version of kWallet or GnomeKeyring is needed in order to test the new keychain backend?

@infeo kwallet implements the Secret Service API since version v5.97.0, which was released in August 2022.

GnomeKeyring: any. The Secret Service API is native to the GnomeKeyring interface.

Copy link
Member

@infeo infeo left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tested it on Ubuntu 24.0.4.3.

I noticed the different naming scheme:
image

With the GnomeKeychainAccess, the entry was displayed as "Cryptomator" with "vault=[some id]" as details. The description should stay "Cryptomator", to easily identify what the entry is for (the vault name can change, but the app stays the same). You can add another detail containing the vault name ("vault name" or something), if you wan to.

@purejava
Copy link
Contributor Author

purejava commented Feb 9, 2026

With the GnomeKeychainAccess, the entry was displayed as "Cryptomator" with "vault=[some id]" as details. The description should stay "Cryptomator", to easily identify what the entry is for (the vault name can change, but the app stays the same). You can add another detail containing the vault name ("vault name" or something), if you wan to.

Adding more details is no problem. The "Name" of the vault can be added as an additional attribute, so that it gets displayed on opening an entry. Adding the further attribute here does the job.

However: someone having many vaults will have to look through many entries all stating "Cryptomator" on searching for the password of a certain vault. A displayName like "Cryptomator - Vault: %name" would be an alternative.

I'll change it as you wished and test, that collection.searchItems() is still working as expected.

Have the name of a vault as an attribute of the keychain entry
@purejava
Copy link
Contributor Author

purejava commented Feb 9, 2026

I noticed the different naming scheme

@infeo Changes done. The last commit / collection.searchItems() does work.

I also tested migrating the keyrings from one to the other (GNOME Keyring / KDE Wallet / Secret Service).

@purejava
Copy link
Contributor Author

@infeo Nonetheless what keychain backend was selected, it gets set to "GNOME Keyring" on application start, where "GNOME Keyring" is available and to a blank selection box on a KDE DE.

This is due to:

private void migrateLegacySettings(SettingsJson json) {
		// migrate renamed keychainAccess
		if(this.keychainProvider.getValueSafe().equals("org.cryptomator.linux.keychain.SecretServiceKeychainAccess")) {
			this.keychainProvider.setValue("org.cryptomator.linux.keychain.GnomeKeyringKeychainAccess");
		}
		...
}

in Settings, introduced with cryptomator/cryptomator@dd1af8c.

I am confused. A org.cryptomator.linux.keychain.SecretServiceKeychainAccess was already introduced in the past? I wasn't aware of that and thought, there were only the "KDE Wallet" and the "GNOME Keyring" backends. Could you please elaborate on the purpose of that and whether migrateLegacySettings is still needed?

@infeo
Copy link
Member

infeo commented Feb 10, 2026

However: someone having many vaults will have to look through many entries all stating "Cryptomator" on searching for the password of a certain vault. A displayName like "Cryptomator - Vault: %name" would be an alternative.

"Cryptomator" is the most general term where users are still be able to roughly identify a keychain entry. Also, this follows the same pattern as "Chromium Safe Storage". Searching will take a longer time, but from my point of view this is acceptable, since i believe users have most of the times less than 5 vaults and from within the app a password could be deleted or changed.

Could you please elaborate on the purpose of that and whether migrateLegacySettings is still needed

It is not needed anymore. In 826e29d the keychain service was renamed, thus the main app required a migration strategy. But since GnomeKeyringKeychainAccess (formerly known as SecretService) and SecretServiceKeychainAccess (formerly not existing) are both effectively using the secret-service API and addtionally, this API also works properly with KWallet, the migration is not required anymore.

add to easy to read "with-er" methods
@infeo infeo self-requested a review February 10, 2026 10:28
Copy link
Member

@infeo infeo left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@purejava i made a small cleanup.

Additionally, i changed the handling when deleting a not-existing item: Just log the event, do not throw an error: Deleting a password should be idempotent (several deletions still still have the outcome of "item does not exist"), additionally, it reduces API friction (aka handling deletion cases is easier)

@infeo infeo added the enhancement New feature or request label Feb 10, 2026
@infeo infeo added this to the 1.7.0 milestone Feb 10, 2026
@infeo infeo merged commit e98c9c4 into cryptomator:develop Feb 10, 2026
8 checks passed
purejava added a commit to purejava/cryptomator that referenced this pull request Feb 11, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants