From 817907c25aee7e8b967f9578736a35aaf6c8566e Mon Sep 17 00:00:00 2001 From: Ralph Plawetzki Date: Fri, 7 Aug 2020 18:32:54 +0200 Subject: [PATCH 1/4] Add access functionality for KDE kwallets --- main/keychain/pom.xml | 8 +- .../cryptomator/keychain/KeychainModule.java | 2 +- .../LinuxKDEWalletKeychainAccessImpl.java | 121 ++++++++++++++++++ ...ss.java => LinuxSystemKeychainAccess.java} | 20 +-- main/pom.xml | 8 +- 5 files changed, 148 insertions(+), 11 deletions(-) create mode 100644 main/keychain/src/main/java/org/cryptomator/keychain/LinuxKDEWalletKeychainAccessImpl.java rename main/keychain/src/main/java/org/cryptomator/keychain/{LinuxSecretServiceKeychainAccess.java => LinuxSystemKeychainAccess.java} (64%) diff --git a/main/keychain/pom.xml b/main/keychain/pom.xml index fd2b26c3a..3eaa5c2e7 100644 --- a/main/keychain/pom.xml +++ b/main/keychain/pom.xml @@ -24,7 +24,7 @@ org.openjfx javafx-graphics - + org.apache.commons @@ -53,6 +53,12 @@ secret-service + + + org.purejava + kdewallet + + org.slf4j diff --git a/main/keychain/src/main/java/org/cryptomator/keychain/KeychainModule.java b/main/keychain/src/main/java/org/cryptomator/keychain/KeychainModule.java index 1db94fec6..a6054be2d 100644 --- a/main/keychain/src/main/java/org/cryptomator/keychain/KeychainModule.java +++ b/main/keychain/src/main/java/org/cryptomator/keychain/KeychainModule.java @@ -28,7 +28,7 @@ public abstract class KeychainModule { @Binds @IntoSet - abstract KeychainAccessStrategy bindLinuxSecretServiceKeychainAccess(LinuxSecretServiceKeychainAccess keychainAccessStrategy); + abstract KeychainAccessStrategy bindLinuxSystemKeychainAccess(LinuxSystemKeychainAccess keychainAccessStrategy); @Provides @Singleton diff --git a/main/keychain/src/main/java/org/cryptomator/keychain/LinuxKDEWalletKeychainAccessImpl.java b/main/keychain/src/main/java/org/cryptomator/keychain/LinuxKDEWalletKeychainAccessImpl.java new file mode 100644 index 000000000..120bef435 --- /dev/null +++ b/main/keychain/src/main/java/org/cryptomator/keychain/LinuxKDEWalletKeychainAccessImpl.java @@ -0,0 +1,121 @@ +package org.cryptomator.keychain; + +import org.freedesktop.dbus.connections.impl.DBusConnection; +import org.freedesktop.dbus.exceptions.DBusException; +import org.kde.KWallet; +import org.kde.Static; +import org.purejava.KDEWallet; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class LinuxKDEWalletKeychainAccessImpl implements KeychainAccessStrategy { + + private final Logger log = LoggerFactory.getLogger(LinuxKDEWalletKeychainAccessImpl.class); + + private final String FOLDER_NAME = "Cryptomator"; + private final String APP_NAME = "Cryptomator"; + private DBusConnection connection; + private KDEWallet wallet; + private int handle = -1; + + public LinuxKDEWalletKeychainAccessImpl() { + try { + connection = DBusConnection.getConnection(DBusConnection.DBusBusType.SESSION); + } catch (DBusException e) { + e.printStackTrace(); + } + } + + @Override + public boolean isSupported() { + try { + wallet = new KDEWallet(connection); + return wallet.isEnabled(); + } catch (Exception e) { + e.printStackTrace(); + return false; + } + } + + @Override + public void storePassphrase(String key, CharSequence passphrase) throws KeychainAccessException { + try { + if (walletIsOpen() && + !(wallet.hasEntry(handle, FOLDER_NAME, key, APP_NAME) + && wallet.entryType(handle, FOLDER_NAME, key, APP_NAME) == 1) + && wallet.writePassword(handle, FOLDER_NAME, key, passphrase.toString(), APP_NAME) == 0) { + log.debug("Passphrase successfully stored."); + } else { + log.debug("Passphrase was not stored."); + } + } catch (Exception e) { + log.error(e.toString(), e.getCause()); + throw new KeychainAccessException(e); + } + } + + @Override + public char[] loadPassphrase(String key) throws KeychainAccessException { + String password = ""; + try { + if (walletIsOpen()) { + password = wallet.readPassword(handle, FOLDER_NAME, key, APP_NAME); + log.debug("loadPassphrase: wallet is open."); + } else { + log.debug("loadPassphrase: wallet is closed."); + } + return (password.equals("")) ? null : password.toCharArray(); + } catch (Exception e) { + throw new KeychainAccessException(e); + } + } + + @Override + public void deletePassphrase(String key) throws KeychainAccessException { + try { + if (walletIsOpen() + && wallet.hasEntry(handle, FOLDER_NAME, key, APP_NAME) + && wallet.entryType(handle, FOLDER_NAME, key, APP_NAME) == 1 + && wallet.removeEntry(handle, FOLDER_NAME, key, APP_NAME) == 0) { + log.debug("Passphrase successfully deleted."); + } else { + log.debug("Passphrase was not deleted."); + } + } catch (Exception e) { + throw new KeychainAccessException(e); + } + } + + @Override + public void changePassphrase(String key, CharSequence passphrase) throws KeychainAccessException { + try { + if (walletIsOpen() + && wallet.hasEntry(handle, FOLDER_NAME, key, APP_NAME) + && wallet.entryType(handle, FOLDER_NAME, key, APP_NAME) == 1 + && wallet.writePassword(handle, FOLDER_NAME, key, passphrase.toString(), APP_NAME) == 0) { + log.debug("Passphrase successfully changed."); + } else { + log.debug("Passphrase could not be changed."); + } + } catch (Exception e) { + throw new KeychainAccessException(e); + } + } + + private boolean walletIsOpen() throws KeychainAccessException { + try { + if (wallet.isOpen(Static.DEFAULT_WALLET)) { + // This is needed due to KeechainManager loading the passphase directly + if (handle == -1) handle = wallet.open(Static.DEFAULT_WALLET, 0, APP_NAME); + return true; + } + wallet.openAsync(Static.DEFAULT_WALLET, 0, APP_NAME, false); + wallet.getSignalHandler().await(KWallet.walletAsyncOpened.class, Static.ObjectPaths.KWALLETD5, () -> null); + handle = wallet.getSignalHandler().getLastHandledSignal(KWallet.walletAsyncOpened.class, Static.ObjectPaths.KWALLETD5).handle; + log.debug("Wallet successfully initialized."); + return handle != -1; + } catch (Exception e) { + throw new KeychainAccessException(e); + } + } +} diff --git a/main/keychain/src/main/java/org/cryptomator/keychain/LinuxSecretServiceKeychainAccess.java b/main/keychain/src/main/java/org/cryptomator/keychain/LinuxSystemKeychainAccess.java similarity index 64% rename from main/keychain/src/main/java/org/cryptomator/keychain/LinuxSecretServiceKeychainAccess.java rename to main/keychain/src/main/java/org/cryptomator/keychain/LinuxSystemKeychainAccess.java index f11bdbd45..cb2e5ce77 100644 --- a/main/keychain/src/main/java/org/cryptomator/keychain/LinuxSecretServiceKeychainAccess.java +++ b/main/keychain/src/main/java/org/cryptomator/keychain/LinuxSystemKeychainAccess.java @@ -7,24 +7,28 @@ import javax.inject.Singleton; import java.util.Optional; /** - * A facade to LinuxSecretServiceKeychainAccessImpl that doesn't depend on libraries that are unavailable on Mac and Windows. + * A facade to LinuxSecretServiceKeychainAccessImpl and LinuxKDEWalletKeychainAccessImpl + * that depend on libraries that are unavailable on Mac and Windows. */ @Singleton -public class LinuxSecretServiceKeychainAccess implements KeychainAccessStrategy { +public class LinuxSystemKeychainAccess implements KeychainAccessStrategy { - // the actual implementation is hidden in this delegate object which is loaded via reflection, + // the actual implementation is hidden in this delegate objects which are loaded via reflection, // as it depends on libraries that aren't necessarily available: private final Optional delegate; @Inject - public LinuxSecretServiceKeychainAccess() { - this.delegate = constructGnomeKeyringKeychainAccess(); + public LinuxSystemKeychainAccess() { + this.delegate = constructKeychainAccess(); } - private static Optional constructGnomeKeyringKeychainAccess() { - try { - Class clazz = Class.forName("org.cryptomator.keychain.LinuxSecretServiceKeychainAccessImpl"); + private static Optional constructKeychainAccess() { + try { // is kwallet or gnome-keyring installed? + Class clazz = Class.forName("org.cryptomator.keychain.LinuxKDEWalletKeychainAccessImpl"); KeychainAccessStrategy instance = (KeychainAccessStrategy) clazz.getDeclaredConstructor().newInstance(); + if (instance.isSupported()) return Optional.of(instance); + clazz = Class.forName("org.cryptomator.keychain.LinuxSecretServiceKeychainAccessImpl"); + instance = (KeychainAccessStrategy) clazz.getDeclaredConstructor().newInstance(); return Optional.of(instance); } catch (Exception e) { return Optional.empty(); diff --git a/main/pom.xml b/main/pom.xml index 5f0d7e5ea..fdea0eb14 100644 --- a/main/pom.xml +++ b/main/pom.xml @@ -34,6 +34,7 @@ 14 3.10 1.0.0 + 1.0.0 3.10.3 1.0.3 29.0-jre @@ -168,7 +169,12 @@ secret-service ${secret-service.version} - + + org.purejava + kdewallet + ${kdewallet.version} + + com.auth0 From 100b83697990ed7f4b4b5557285cede953bebf8e Mon Sep 17 00:00:00 2001 From: Ralph Plawetzki Date: Fri, 7 Aug 2020 18:49:07 +0200 Subject: [PATCH 2/4] Adjust buildkit to exclude kdewallet for Windows and macOS --- main/buildkit/pom.xml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/main/buildkit/pom.xml b/main/buildkit/pom.xml index c86927d33..705138204 100644 --- a/main/buildkit/pom.xml +++ b/main/buildkit/pom.xml @@ -67,7 +67,7 @@ ${project.build.directory}/libs linux,mac,win - dbus-java,secret-service,hkdf,java-utils + dbus-java,secret-service,kdewallet,hkdf,java-utils @@ -83,14 +83,14 @@ - copy-linux-secret-service + copy-linux-system-keychain-access prepare-package copy-dependencies ${project.build.directory}/linux-libs - dbus-java,secret-service,hkdf,java-utils + dbus-java,secret-service,kdewallet,hkdf,java-utils From b1c66b181d441dfdd3c996b06dc2defb1d830b92 Mon Sep 17 00:00:00 2001 From: Ralph Plawetzki Date: Sun, 9 Aug 2020 11:18:17 +0200 Subject: [PATCH 3/4] Depend on kdewallet 1.0.1, hence dbus-java 3.2.3 that fixes issues when dealing with multiple signals of the same name but different signatures --- main/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main/pom.xml b/main/pom.xml index fdea0eb14..d4fe8da87 100644 --- a/main/pom.xml +++ b/main/pom.xml @@ -34,7 +34,7 @@ 14 3.10 1.0.0 - 1.0.0 + 1.0.1 3.10.3 1.0.3 29.0-jre From 3bf2b499a7c31022863fcd34e1619aa4f5d18a3f Mon Sep 17 00:00:00 2001 From: Ralph Plawetzki Date: Fri, 14 Aug 2020 07:40:46 +0200 Subject: [PATCH 4/4] Reverse order to initialize backend The GNOME keyring feature was implemented first and we don't want to confuse users who used it before --- .../org/cryptomator/keychain/LinuxSystemKeychainAccess.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/main/keychain/src/main/java/org/cryptomator/keychain/LinuxSystemKeychainAccess.java b/main/keychain/src/main/java/org/cryptomator/keychain/LinuxSystemKeychainAccess.java index cb2e5ce77..e75d387cb 100644 --- a/main/keychain/src/main/java/org/cryptomator/keychain/LinuxSystemKeychainAccess.java +++ b/main/keychain/src/main/java/org/cryptomator/keychain/LinuxSystemKeychainAccess.java @@ -23,11 +23,11 @@ public class LinuxSystemKeychainAccess implements KeychainAccessStrategy { } private static Optional constructKeychainAccess() { - try { // is kwallet or gnome-keyring installed? - Class clazz = Class.forName("org.cryptomator.keychain.LinuxKDEWalletKeychainAccessImpl"); + try { // is gnome-keyring or kwallet installed? + Class clazz = Class.forName("org.cryptomator.keychain.LinuxSecretServiceKeychainAccessImpl"); KeychainAccessStrategy instance = (KeychainAccessStrategy) clazz.getDeclaredConstructor().newInstance(); if (instance.isSupported()) return Optional.of(instance); - clazz = Class.forName("org.cryptomator.keychain.LinuxSecretServiceKeychainAccessImpl"); + clazz = Class.forName("org.cryptomator.keychain.LinuxKDEWalletKeychainAccessImpl"); instance = (KeychainAccessStrategy) clazz.getDeclaredConstructor().newInstance(); return Optional.of(instance); } catch (Exception e) {