From 7347609720ac34ed61fa19e256218aaf3cded995 Mon Sep 17 00:00:00 2001 From: Ralph Plawetzki Date: Fri, 7 Aug 2026 18:18:43 +0200 Subject: [PATCH 1/7] Use nested records due to secret-service 1.2.0 --- .../keychain/SecretServiceKeychainAccess.java | 269 ++++++++++++++---- 1 file changed, 214 insertions(+), 55 deletions(-) diff --git a/src/main/java/org/cryptomator/linux/keychain/SecretServiceKeychainAccess.java b/src/main/java/org/cryptomator/linux/keychain/SecretServiceKeychainAccess.java index 84938f4..16cca0d 100644 --- a/src/main/java/org/cryptomator/linux/keychain/SecretServiceKeychainAccess.java +++ b/src/main/java/org/cryptomator/linux/keychain/SecretServiceKeychainAccess.java @@ -7,8 +7,10 @@ import org.cryptomator.integrations.keychain.KeychainAccessProvider; import org.freedesktop.dbus.DBusPath; import org.purejava.secret.api.Collection; +import org.purejava.secret.api.DBusMessageHandler; import org.purejava.secret.api.EncryptedSession; import org.purejava.secret.api.Item; +import org.purejava.secret.api.Pair; import org.purejava.secret.api.Static; import org.purejava.secret.api.Util; import org.slf4j.Logger; @@ -19,6 +21,8 @@ import java.util.Map; import java.util.Objects; +import static org.purejava.secret.api.DBusMessageHandler.DBusResult.*; + @Priority(1100) @OperatingSystem(OperatingSystem.Value.LINUX) @DisplayName("Secret Service") @@ -36,9 +40,15 @@ public SecretServiceKeychainAccess() { session.getService().addCollectionCreatedHandler(collection -> LOG.debug("Collection {} created", collection.getPath())); session.getService().addCollectionDeletedHandler(collection -> LOG.debug("Collection {} deleted", 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)); + switch (getAlias) { + case Success success-> { + if ("/".equals(success.value().getPath())) { + // default alias is not set; set it to the login keyring + session.getService().setAlias("default", new DBusPath(Static.DBusPath.LOGIN_COLLECTION)); + } + } + case Failure failure + -> LOG.warn("Getting the collection with the \"default\" alias failed with: {}", failure.error().getMessage()); } collection.addItemChangedHandler(item -> LOG.debug("Item {} changed", item.getPath())); collection.addItemCreatedHandler(item -> LOG.debug("Item {} created", item.getPath())); @@ -50,28 +60,71 @@ public SecretServiceKeychainAccess() { public void storePassphrase(String key, String displayName, CharSequence passphrase) throws KeychainAccessException { try { var call = collection.searchItems(withKey(key)); - if (call.isSuccess()) { - if (call.value().isEmpty()) { - List lockable = new ArrayList<>(); - lockable.add(new DBusPath(collection.getDBusPath())); - var promptNeededToUnlock = session.getService().unlock(lockable); - if (promptNeededToUnlock.isSuccess() && !"/".equals(promptNeededToUnlock.value().b.getPath())) { - Util.promptAndGetResultAsArrayList(promptNeededToUnlock.value().b); - } - var itemProps = Item.createProperties(LABEL_FOR_SECRET_IN_KEYRING, withKeyAndName(key, displayName)); - var secret = session.encrypt(passphrase); - var created = collection.createItem(itemProps, secret, false); - if (!created.isSuccess()) { - throw new KeychainAccessException("Storing password failed", created.error()); + + switch (call) { + case DBusMessageHandler.DBusResult.Success> success -> { + if (success.value().isEmpty()) { + List lockable = new ArrayList<>(); + lockable.add(new DBusPath(collection.getDBusPath())); + + var unlockResult = session.getService().unlock(lockable); + + switch (unlockResult) { + case Success, DBusPath>> unlockSuccess -> { + var prompt = unlockSuccess.value().b; + + if (!"/".equals(prompt.getPath())) { + Util.promptAndGetResultAsArrayList(prompt); + } + } + + case Failure, DBusPath>> unlockFailure -> + LOG.warn( + "Failed to unlock collection {}", + collection.getDBusPath(), + unlockFailure.error() + ); + } + + var itemProps = Item.createProperties( + LABEL_FOR_SECRET_IN_KEYRING, + withKeyAndName(key, displayName) + ); + + var secret = session.encrypt(passphrase); + var created = collection.createItem(itemProps, secret, false); + + switch (created) { + case Success> successful -> + LOG.debug( + "Created item {} on collection {}", + successful.value().a.getPath(), + collection.getDBusPath() + ); + + case Failure> failure -> + throw new KeychainAccessException( + "Storing password failed", + failure.error() + ); + } + } else { + changePassphrase(key, displayName, passphrase); } - } else { - changePassphrase(key, displayName, passphrase); } - } else { - throw new KeychainAccessException("Storing password failed", call.error()); + + case DBusMessageHandler.DBusResult.Failure> failure -> + throw new KeychainAccessException( + "Storing password failed", + failure.error() + ); } } catch (Exception e) { - throw new KeychainAccessException("Storing password failed.", e); + throw new KeychainAccessException( + "Storing password failed for collection " + + collection.getDBusPath(), + e + ); } } @@ -79,20 +132,34 @@ public void storePassphrase(String key, String displayName, CharSequence passphr public char[] loadPassphrase(String key) throws KeychainAccessException { try { var call = collection.searchItems(withKey(key)); - if (call.isSuccess()) { - if (!call.value().isEmpty()) { - var path = call.value().getFirst(); + + switch (call) { + case Success> success -> { + if (success.value().isEmpty()) { + return null; + } + + var path = success.value().getFirst(); + session.getService().ensureUnlocked(path); + var secret = new Item(path).getSecret(session.getSession()); return session.decrypt(secret); - } else { - return null; } - } else { - throw new KeychainAccessException("Loading password failed", call.error()); + + case Failure> failure -> + throw new KeychainAccessException( + "Loading password failed for collection " + + collection.getDBusPath(), + failure.error() + ); } } catch (Exception e) { - throw new KeychainAccessException("Loading password failed.", e); + throw new KeychainAccessException( + "Loading password failed for collection " + + collection.getDBusPath(), + e + ); } } @@ -100,48 +167,129 @@ public char[] loadPassphrase(String key) throws KeychainAccessException { public void deletePassphrase(String key) throws KeychainAccessException { try { var call = collection.searchItems(withKey(key)); - if (call.isSuccess()) { - if (!call.value().isEmpty()) { - var path = call.value().getFirst(); + + switch (call) { + case Success> success -> { + if (success.value().isEmpty()) { + LOG.debug( + "Deleting entry with {}={} failed: No such item found", + ID_KEY, + key + ); + return; + } + + var path = success.value().getFirst(); + session.getService().ensureUnlocked(path); + var item = new Item(path); - var deleted = item.delete(); - if (!deleted.isSuccess()) { - throw new KeychainAccessException("Deleting password failed", deleted.error()); + + switch (item.delete()) { + case Success _ -> + LOG.debug( + "Deleted item {} from collection {}", + path.getPath(), + collection.getDBusPath() + ); + + case Failure failure -> { + LOG.warn( + "Failed to delete item {} from collection {}", + path.getPath(), + collection.getDBusPath(), + failure.error() + ); + + throw new KeychainAccessException( + "Deleting password failed for collection " + + collection.getDBusPath(), + failure.error() + ); + } } - } else { - LOG.debug("Deleting entry with {}={} failed: No such item found", ID_KEY, key); } - } else { - throw new KeychainAccessException("Deleting password failed", call.error()); + + case Failure> failure -> + throw new KeychainAccessException( + "Deleting password failed for collection " + + collection.getDBusPath(), + failure.error() + ); } } catch (Exception e) { - throw new KeychainAccessException("Deleting password failed", e); + throw new KeychainAccessException( + "Deleting password failed for collection " + + collection.getDBusPath(), + e + ); } } @Override - public void changePassphrase(String key, String displayName, CharSequence passphrase) throws KeychainAccessException { + public void changePassphrase(String key, String displayName, CharSequence passphrase) + throws KeychainAccessException { + try { var call = collection.searchItems(withKey(key)); - if (call.isSuccess()) { - if (!call.value().isEmpty()) { - session.getService().ensureUnlocked(call.value().getFirst()); + + switch (call) { + case Success> success -> { + if (success.value().isEmpty()) { + var message = "Vault " + key + " not found, updating failed"; + throw new KeychainAccessException(message); + } + + var path = success.value().getFirst(); + + session.getService().ensureUnlocked(path); + var secret = session.encrypt(passphrase); - var itemProps = Item.createProperties(LABEL_FOR_SECRET_IN_KEYRING, withKeyAndName(key, displayName)); + var itemProps = Item.createProperties( + LABEL_FOR_SECRET_IN_KEYRING, + withKeyAndName(key, displayName) + ); + var updated = collection.createItem(itemProps, secret, true); - if (!updated.isSuccess()) { - throw new KeychainAccessException("Updating password failed", updated.error()); + + switch (updated) { + case Success> _ -> + LOG.debug( + "Updated item {} in collection {}", + path.getPath(), + collection.getDBusPath() + ); + + case Failure> failure -> { + LOG.warn( + "Failed to update item {} in collection {}", + path.getPath(), + collection.getDBusPath(), + failure.error() + ); + + throw new KeychainAccessException( + "Updating password failed for collection " + + collection.getDBusPath(), + failure.error() + ); + } } - } else { - var msg = "Vault " + key + " not found, updating failed"; - throw new KeychainAccessException(msg); } - } else { - throw new KeychainAccessException("Updating password failed", call.error()); + + case Failure> failure -> + throw new KeychainAccessException( + "Updating password failed for collection " + + collection.getDBusPath(), + failure.error() + ); } } catch (Exception e) { - throw new KeychainAccessException("Updating password failed", e); + throw new KeychainAccessException( + "Updating password failed for collection " + + collection.getDBusPath(), + e + ); } } @@ -158,8 +306,19 @@ public boolean isSupported() { @Override public boolean isLocked() { - var call = collection.isLocked(); - return !call.isSuccess() || call.value(); + return switch (collection.isLocked()) { + case Success success -> + success.value(); // yields the value + + case Failure failure -> { + LOG.warn( + "Failed to determine lock state of collection {}", + collection.getDBusPath(), + failure.error() + ); + yield true; + } + }; } private Map withKey(String key) { From f4a89584afd9f838c865867cb7480f2bac761356 Mon Sep 17 00:00:00 2001 From: Ralph Plawetzki Date: Sun, 9 Aug 2026 13:45:51 +0200 Subject: [PATCH 2/7] Fixes suggested by coderabbitai --- pom.xml | 2 +- .../keychain/SecretServiceKeychainAccess.java | 86 +++++++------------ 2 files changed, 31 insertions(+), 57 deletions(-) diff --git a/pom.xml b/pom.xml index 5a25d71..1c60f85 100644 --- a/pom.xml +++ b/pom.xml @@ -44,7 +44,7 @@ 2.22.1 2.0.1-alpha 1.4.0 - 1.1.1 + 1.2.0 1.1.1 1.4.2 diff --git a/src/main/java/org/cryptomator/linux/keychain/SecretServiceKeychainAccess.java b/src/main/java/org/cryptomator/linux/keychain/SecretServiceKeychainAccess.java index 16cca0d..e88e320 100644 --- a/src/main/java/org/cryptomator/linux/keychain/SecretServiceKeychainAccess.java +++ b/src/main/java/org/cryptomator/linux/keychain/SecretServiceKeychainAccess.java @@ -60,51 +60,41 @@ public SecretServiceKeychainAccess() { public void storePassphrase(String key, String displayName, CharSequence passphrase) throws KeychainAccessException { try { var call = collection.searchItems(withKey(key)); - switch (call) { case DBusMessageHandler.DBusResult.Success> success -> { if (success.value().isEmpty()) { List lockable = new ArrayList<>(); lockable.add(new DBusPath(collection.getDBusPath())); - var unlockResult = session.getService().unlock(lockable); - switch (unlockResult) { case Success, DBusPath>> unlockSuccess -> { var prompt = unlockSuccess.value().b; - if (!"/".equals(prompt.getPath())) { Util.promptAndGetResultAsArrayList(prompt); } } - case Failure, DBusPath>> unlockFailure -> - LOG.warn( - "Failed to unlock collection {}", - collection.getDBusPath(), - unlockFailure.error() - ); + LOG.warn("Failed to unlock collection {}", + collection.getDBusPath(), + unlockFailure.error()); } var itemProps = Item.createProperties( LABEL_FOR_SECRET_IN_KEYRING, withKeyAndName(key, displayName) ); - var secret = session.encrypt(passphrase); var created = collection.createItem(itemProps, secret, false); switch (created) { case Success> successful -> - LOG.debug( - "Created item {} on collection {}", - successful.value().a.getPath(), - collection.getDBusPath() - ); - + LOG.debug("Created item {} on collection {}", + successful.value().a.getPath(), + collection.getDBusPath()); case Failure> failure -> throw new KeychainAccessException( - "Storing password failed", + "Storing password failed for collection " + + collection.getDBusPath(), failure.error() ); } @@ -112,13 +102,15 @@ public void storePassphrase(String key, String displayName, CharSequence passphr changePassphrase(key, displayName, passphrase); } } - case DBusMessageHandler.DBusResult.Failure> failure -> throw new KeychainAccessException( - "Storing password failed", + "Storing password failed for collection " + + collection.getDBusPath(), failure.error() ); } + } catch (KeychainAccessException e) { + throw e; } catch (Exception e) { throw new KeychainAccessException( "Storing password failed for collection " @@ -154,6 +146,8 @@ public char[] loadPassphrase(String key) throws KeychainAccessException { failure.error() ); } + } catch (KeychainAccessException e) { + throw e; } catch (Exception e) { throw new KeychainAccessException( "Loading password failed for collection " @@ -167,40 +161,29 @@ public char[] loadPassphrase(String key) throws KeychainAccessException { public void deletePassphrase(String key) throws KeychainAccessException { try { var call = collection.searchItems(withKey(key)); - switch (call) { case Success> success -> { if (success.value().isEmpty()) { - LOG.debug( - "Deleting entry with {}={} failed: No such item found", + LOG.debug("Deleting entry with {}={} failed: No such item found", ID_KEY, - key - ); + key); return; } var path = success.value().getFirst(); - session.getService().ensureUnlocked(path); - var item = new Item(path); switch (item.delete()) { case Success _ -> - LOG.debug( - "Deleted item {} from collection {}", - path.getPath(), - collection.getDBusPath() - ); - + LOG.debug("Deleted item {} from collection {}", + path.getPath(), + collection.getDBusPath()); case Failure failure -> { - LOG.warn( - "Failed to delete item {} from collection {}", + LOG.warn("Failed to delete item {} from collection {}", path.getPath(), collection.getDBusPath(), - failure.error() - ); - + failure.error()); throw new KeychainAccessException( "Deleting password failed for collection " + collection.getDBusPath(), @@ -209,7 +192,6 @@ public void deletePassphrase(String key) throws KeychainAccessException { } } } - case Failure> failure -> throw new KeychainAccessException( "Deleting password failed for collection " @@ -217,6 +199,8 @@ public void deletePassphrase(String key) throws KeychainAccessException { failure.error() ); } + } catch (KeychainAccessException e) { + throw e; } catch (Exception e) { throw new KeychainAccessException( "Deleting password failed for collection " @@ -229,10 +213,8 @@ public void deletePassphrase(String key) throws KeychainAccessException { @Override public void changePassphrase(String key, String displayName, CharSequence passphrase) throws KeychainAccessException { - try { var call = collection.searchItems(withKey(key)); - switch (call) { case Success> success -> { if (success.value().isEmpty()) { @@ -241,33 +223,24 @@ public void changePassphrase(String key, String displayName, CharSequence passph } var path = success.value().getFirst(); - session.getService().ensureUnlocked(path); - var secret = session.encrypt(passphrase); var itemProps = Item.createProperties( LABEL_FOR_SECRET_IN_KEYRING, withKeyAndName(key, displayName) ); - var updated = collection.createItem(itemProps, secret, true); switch (updated) { case Success> _ -> - LOG.debug( - "Updated item {} in collection {}", - path.getPath(), - collection.getDBusPath() - ); - + LOG.debug("Updated item {} in collection {}", + path.getPath(), + collection.getDBusPath()); case Failure> failure -> { - LOG.warn( - "Failed to update item {} in collection {}", + LOG.warn("Failed to update item {} in collection {}", path.getPath(), collection.getDBusPath(), - failure.error() - ); - + failure.error()); throw new KeychainAccessException( "Updating password failed for collection " + collection.getDBusPath(), @@ -276,7 +249,6 @@ public void changePassphrase(String key, String displayName, CharSequence passph } } } - case Failure> failure -> throw new KeychainAccessException( "Updating password failed for collection " @@ -284,6 +256,8 @@ public void changePassphrase(String key, String displayName, CharSequence passph failure.error() ); } + } catch (KeychainAccessException e) { + throw e; } catch (Exception e) { throw new KeychainAccessException( "Updating password failed for collection " From 1ca3953028d2f1517ebe1460e8f3cb21d34e9e92 Mon Sep 17 00:00:00 2001 From: Ralph Plawetzki Date: Mon, 10 Aug 2026 18:25:39 +0200 Subject: [PATCH 3/7] Use guarded pattern matching in switch --- .../keychain/SecretServiceKeychainAccess.java | 114 ++++++++++-------- 1 file changed, 61 insertions(+), 53 deletions(-) diff --git a/src/main/java/org/cryptomator/linux/keychain/SecretServiceKeychainAccess.java b/src/main/java/org/cryptomator/linux/keychain/SecretServiceKeychainAccess.java index e88e320..ba2b38e 100644 --- a/src/main/java/org/cryptomator/linux/keychain/SecretServiceKeychainAccess.java +++ b/src/main/java/org/cryptomator/linux/keychain/SecretServiceKeychainAccess.java @@ -16,7 +16,6 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.Objects; @@ -61,47 +60,47 @@ public void storePassphrase(String key, String displayName, CharSequence passphr try { var call = collection.searchItems(withKey(key)); switch (call) { - case DBusMessageHandler.DBusResult.Success> success -> { - if (success.value().isEmpty()) { - List lockable = new ArrayList<>(); - lockable.add(new DBusPath(collection.getDBusPath())); - var unlockResult = session.getService().unlock(lockable); - switch (unlockResult) { - case Success, DBusPath>> unlockSuccess -> { - var prompt = unlockSuccess.value().b; - if (!"/".equals(prompt.getPath())) { - Util.promptAndGetResultAsArrayList(prompt); - } + case DBusMessageHandler.DBusResult.Success> success + when success.value().isEmpty() -> { + List lockable = List.of(new DBusPath(collection.getDBusPath())); + var unlockResult = session.getService().unlock(lockable); + + switch (unlockResult) { + case Success, DBusPath>> unlockSuccess -> { + var prompt = unlockSuccess.value().b; + if (!"/".equals(prompt.getPath())) { + Util.promptAndGetResultAsArrayList(prompt); } - case Failure, DBusPath>> unlockFailure -> - LOG.warn("Failed to unlock collection {}", - collection.getDBusPath(), - unlockFailure.error()); } + case Failure, DBusPath>> unlockFailure -> + LOG.warn("Failed to unlock collection {}", + collection.getDBusPath(), + unlockFailure.error()); + } - var itemProps = Item.createProperties( - LABEL_FOR_SECRET_IN_KEYRING, - withKeyAndName(key, displayName) - ); - var secret = session.encrypt(passphrase); - var created = collection.createItem(itemProps, secret, false); - - switch (created) { - case Success> successful -> - LOG.debug("Created item {} on collection {}", - successful.value().a.getPath(), - collection.getDBusPath()); - case Failure> failure -> - throw new KeychainAccessException( - "Storing password failed for collection " - + collection.getDBusPath(), - failure.error() - ); - } - } else { - changePassphrase(key, displayName, passphrase); + var itemProps = Item.createProperties( + LABEL_FOR_SECRET_IN_KEYRING, + withKeyAndName(key, displayName) + ); + var secret = session.encrypt(passphrase); + var created = collection.createItem(itemProps, secret, false); + + switch (created) { + case Success> successful -> + LOG.debug("Created item {} on collection {}", + successful.value().a.getPath(), + collection.getDBusPath()); + case Failure> failure -> + throw new KeychainAccessException( + "Storing password failed for collection " + + collection.getDBusPath(), + failure.error() + ); } } + case DBusMessageHandler.DBusResult.Success> _ -> + changePassphrase(key, displayName, passphrase); + case DBusMessageHandler.DBusResult.Failure> failure -> throw new KeychainAccessException( "Storing password failed for collection " @@ -126,11 +125,12 @@ public char[] loadPassphrase(String key) throws KeychainAccessException { var call = collection.searchItems(withKey(key)); switch (call) { - case Success> success -> { - if (success.value().isEmpty()) { - return null; - } + case Success> success + when success.value().isEmpty() -> { + return null; + } + case Success> success -> { var path = success.value().getFirst(); session.getService().ensureUnlocked(path); @@ -161,15 +161,15 @@ public char[] loadPassphrase(String key) throws KeychainAccessException { public void deletePassphrase(String key) throws KeychainAccessException { try { var call = collection.searchItems(withKey(key)); + switch (call) { - case Success> success -> { - if (success.value().isEmpty()) { - LOG.debug("Deleting entry with {}={} failed: No such item found", - ID_KEY, - key); - return; - } + case Success> success + when success.value().isEmpty() -> + LOG.debug("Deleting entry with {}={} failed: No such item found", + ID_KEY, + key); + case Success> success -> { var path = success.value().getFirst(); session.getService().ensureUnlocked(path); var item = new Item(path); @@ -179,11 +179,13 @@ public void deletePassphrase(String key) throws KeychainAccessException { LOG.debug("Deleted item {} from collection {}", path.getPath(), collection.getDBusPath()); + case Failure failure -> { LOG.warn("Failed to delete item {} from collection {}", path.getPath(), collection.getDBusPath(), failure.error()); + throw new KeychainAccessException( "Deleting password failed for collection " + collection.getDBusPath(), @@ -192,6 +194,7 @@ public void deletePassphrase(String key) throws KeychainAccessException { } } } + case Failure> failure -> throw new KeychainAccessException( "Deleting password failed for collection " @@ -215,13 +218,15 @@ public void changePassphrase(String key, String displayName, CharSequence passph throws KeychainAccessException { try { var call = collection.searchItems(withKey(key)); + switch (call) { - case Success> success -> { - if (success.value().isEmpty()) { - var message = "Vault " + key + " not found, updating failed"; - throw new KeychainAccessException(message); - } + case Success> success + when success.value().isEmpty() -> { + var message = "Vault " + key + " not found, updating failed"; + throw new KeychainAccessException(message); + } + case Success> success -> { var path = success.value().getFirst(); session.getService().ensureUnlocked(path); var secret = session.encrypt(passphrase); @@ -236,11 +241,13 @@ public void changePassphrase(String key, String displayName, CharSequence passph LOG.debug("Updated item {} in collection {}", path.getPath(), collection.getDBusPath()); + case Failure> failure -> { LOG.warn("Failed to update item {} in collection {}", path.getPath(), collection.getDBusPath(), failure.error()); + throw new KeychainAccessException( "Updating password failed for collection " + collection.getDBusPath(), @@ -249,6 +256,7 @@ public void changePassphrase(String key, String displayName, CharSequence passph } } } + case Failure> failure -> throw new KeychainAccessException( "Updating password failed for collection " From 28c3347eabb9ba26634fa2a0231203a734194ad0 Mon Sep 17 00:00:00 2001 From: Ralph Plawetzki Date: Mon, 10 Aug 2026 18:37:59 +0200 Subject: [PATCH 4/7] Check number of returned search items --- .../linux/keychain/SecretServiceKeychainAccess.java | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/main/java/org/cryptomator/linux/keychain/SecretServiceKeychainAccess.java b/src/main/java/org/cryptomator/linux/keychain/SecretServiceKeychainAccess.java index ba2b38e..16790e1 100644 --- a/src/main/java/org/cryptomator/linux/keychain/SecretServiceKeychainAccess.java +++ b/src/main/java/org/cryptomator/linux/keychain/SecretServiceKeychainAccess.java @@ -131,6 +131,7 @@ public char[] loadPassphrase(String key) throws KeychainAccessException { } case Success> success -> { + assertOnlyOneItem(success); var path = success.value().getFirst(); session.getService().ensureUnlocked(path); @@ -170,6 +171,7 @@ public void deletePassphrase(String key) throws KeychainAccessException { key); case Success> success -> { + assertOnlyOneItem(success); var path = success.value().getFirst(); session.getService().ensureUnlocked(path); var item = new Item(path); @@ -227,6 +229,7 @@ public void changePassphrase(String key, String displayName, CharSequence passph } case Success> success -> { + assertOnlyOneItem(success); var path = success.value().getFirst(); session.getService().ensureUnlocked(path); var secret = session.encrypt(passphrase); @@ -275,6 +278,14 @@ public void changePassphrase(String key, String displayName, CharSequence passph } } + private static void assertOnlyOneItem(Success> success) throws KeychainAccessException { + if (success.value().size() != 1) { + throw new KeychainAccessException( + "Expected exactly one item, but found " + success.value().size() + ); + } + } + @Override public boolean isSupported() { try { From 74a7c13fa7dfe6dfaab64d3fe7f9a9e9b930b79b Mon Sep 17 00:00:00 2001 From: Ralph Plawetzki Date: Mon, 10 Aug 2026 18:45:47 +0200 Subject: [PATCH 5/7] Add explaining comment for DBus prompt --- .../linux/keychain/SecretServiceKeychainAccess.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/main/java/org/cryptomator/linux/keychain/SecretServiceKeychainAccess.java b/src/main/java/org/cryptomator/linux/keychain/SecretServiceKeychainAccess.java index 16790e1..426aec5 100644 --- a/src/main/java/org/cryptomator/linux/keychain/SecretServiceKeychainAccess.java +++ b/src/main/java/org/cryptomator/linux/keychain/SecretServiceKeychainAccess.java @@ -67,6 +67,9 @@ public void storePassphrase(String key, String displayName, CharSequence passphr switch (unlockResult) { case Success, DBusPath>> unlockSuccess -> { + /* The unlockResult variable may contain a prompt: when you call unlock for the collection + in order to be able to store the secret within the collection, depending on whether + a prompt is needed to unlock the collection or not, a prompt needs to be handled or not.*/ var prompt = unlockSuccess.value().b; if (!"/".equals(prompt.getPath())) { Util.promptAndGetResultAsArrayList(prompt); From 2f1889d80d670131bd505f4526ced19ccfb490d0 Mon Sep 17 00:00:00 2001 From: Ralph Plawetzki Date: Wed, 19 Aug 2026 06:37:48 +0200 Subject: [PATCH 6/7] Use more of the guarded pattern matching in switch --- .../keychain/SecretServiceKeychainAccess.java | 63 ++++++++++++------- 1 file changed, 39 insertions(+), 24 deletions(-) diff --git a/src/main/java/org/cryptomator/linux/keychain/SecretServiceKeychainAccess.java b/src/main/java/org/cryptomator/linux/keychain/SecretServiceKeychainAccess.java index 426aec5..795b949 100644 --- a/src/main/java/org/cryptomator/linux/keychain/SecretServiceKeychainAccess.java +++ b/src/main/java/org/cryptomator/linux/keychain/SecretServiceKeychainAccess.java @@ -127,20 +127,26 @@ public char[] loadPassphrase(String key) throws KeychainAccessException { try { var call = collection.searchItems(withKey(key)); - switch (call) { + return switch (call) { + case Success> success - when success.value().isEmpty() -> { - return null; - } + when success.value().isEmpty() -> + null; + + case Success> success + when success.value().size() != 1 -> + throw new KeychainAccessException( + "Expected exactly one item, but found " + + success.value().size() + ); case Success> success -> { - assertOnlyOneItem(success); var path = success.value().getFirst(); session.getService().ensureUnlocked(path); var secret = new Item(path).getSecret(session.getSession()); - return session.decrypt(secret); + yield session.decrypt(secret); } case Failure> failure -> @@ -149,7 +155,8 @@ public char[] loadPassphrase(String key) throws KeychainAccessException { + collection.getDBusPath(), failure.error() ); - } + }; + } catch (KeychainAccessException e) { throw e; } catch (Exception e) { @@ -167,14 +174,21 @@ public void deletePassphrase(String key) throws KeychainAccessException { var call = collection.searchItems(withKey(key)); switch (call) { + case Success> success when success.value().isEmpty() -> - LOG.debug("Deleting entry with {}={} failed: No such item found", - ID_KEY, - key); + LOG.debug("Deleting entry with {}={} failed: No such item found", + ID_KEY, + key); + + case Success> success + when success.value().size() > 1 -> + throw new KeychainAccessException( + "Expected exactly one item, but found " + + success.value().size() + ); case Success> success -> { - assertOnlyOneItem(success); var path = success.value().getFirst(); session.getService().ensureUnlocked(path); var item = new Item(path); @@ -207,6 +221,7 @@ public void deletePassphrase(String key) throws KeychainAccessException { failure.error() ); } + } catch (KeychainAccessException e) { throw e; } catch (Exception e) { @@ -225,14 +240,21 @@ public void changePassphrase(String key, String displayName, CharSequence passph var call = collection.searchItems(withKey(key)); switch (call) { + case Success> success - when success.value().isEmpty() -> { - var message = "Vault " + key + " not found, updating failed"; - throw new KeychainAccessException(message); - } + when success.value().isEmpty() -> + throw new KeychainAccessException( + "Vault " + key + " not found, updating failed" + ); + + case Success> success + when success.value().size() > 1 -> + throw new KeychainAccessException( + "Expected exactly one item, but found " + + success.value().size() + ); case Success> success -> { - assertOnlyOneItem(success); var path = success.value().getFirst(); session.getService().ensureUnlocked(path); var secret = session.encrypt(passphrase); @@ -270,6 +292,7 @@ public void changePassphrase(String key, String displayName, CharSequence passph failure.error() ); } + } catch (KeychainAccessException e) { throw e; } catch (Exception e) { @@ -281,14 +304,6 @@ public void changePassphrase(String key, String displayName, CharSequence passph } } - private static void assertOnlyOneItem(Success> success) throws KeychainAccessException { - if (success.value().size() != 1) { - throw new KeychainAccessException( - "Expected exactly one item, but found " + success.value().size() - ); - } - } - @Override public boolean isSupported() { try { From 52c3e0ec31e7dcaf704f5bd5a04644703e3c512a Mon Sep 17 00:00:00 2001 From: Ralph Plawetzki Date: Wed, 19 Aug 2026 07:15:57 +0200 Subject: [PATCH 7/7] Size check should be the same everywhere --- .../cryptomator/linux/keychain/SecretServiceKeychainAccess.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/cryptomator/linux/keychain/SecretServiceKeychainAccess.java b/src/main/java/org/cryptomator/linux/keychain/SecretServiceKeychainAccess.java index 795b949..cffe27c 100644 --- a/src/main/java/org/cryptomator/linux/keychain/SecretServiceKeychainAccess.java +++ b/src/main/java/org/cryptomator/linux/keychain/SecretServiceKeychainAccess.java @@ -134,7 +134,7 @@ public char[] loadPassphrase(String key) throws KeychainAccessException { null; case Success> success - when success.value().size() != 1 -> + when success.value().size() > 1 -> throw new KeychainAccessException( "Expected exactly one item, but found " + success.value().size()