Browse Source

Add access functionality for KDE kwallets

Ralph Plawetzki 4 years ago
parent
commit
817907c25a

+ 7 - 1
main/keychain/pom.xml

@@ -24,7 +24,7 @@
 			<groupId>org.openjfx</groupId>
 			<artifactId>javafx-graphics</artifactId>
 		</dependency>
-		
+
 		<!-- Apache -->
 		<dependency>
 			<groupId>org.apache.commons</groupId>
@@ -53,6 +53,12 @@
 			<artifactId>secret-service</artifactId>
 		</dependency>
 
+		<!-- kdewallet lib -->
+		<dependency>
+			<groupId>org.purejava</groupId>
+			<artifactId>kdewallet</artifactId>
+		</dependency>
+
 		<!-- Logging -->
 		<dependency>
 			<groupId>org.slf4j</groupId>

+ 1 - 1
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

+ 121 - 0
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);
+        }
+    }
+}

+ 12 - 8
main/keychain/src/main/java/org/cryptomator/keychain/LinuxSecretServiceKeychainAccess.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<KeychainAccessStrategy> delegate;
 
 	@Inject
-	public LinuxSecretServiceKeychainAccess() {
-		this.delegate = constructGnomeKeyringKeychainAccess();
+	public LinuxSystemKeychainAccess() {
+		this.delegate = constructKeychainAccess();
 	}
 
-	private static Optional<KeychainAccessStrategy> constructGnomeKeyringKeychainAccess() {
-		try {
-			Class<?> clazz = Class.forName("org.cryptomator.keychain.LinuxSecretServiceKeychainAccessImpl");
+	private static Optional<KeychainAccessStrategy> 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();

+ 7 - 1
main/pom.xml

@@ -34,6 +34,7 @@
 		<javafx.version>14</javafx.version>
 		<commons-lang3.version>3.10</commons-lang3.version>
 		<secret-service.version>1.0.0</secret-service.version>
+		<kdewallet.version>1.0.0</kdewallet.version>
 		<jwt.version>3.10.3</jwt.version>
 		<easybind.version>1.0.3</easybind.version>
 		<guava.version>29.0-jre</guava.version>
@@ -168,7 +169,12 @@
 				<artifactId>secret-service</artifactId>
 				<version>${secret-service.version}</version>
 			</dependency>
-			
+			<dependency>
+				<groupId>org.purejava</groupId>
+				<artifactId>kdewallet</artifactId>
+				<version>${kdewallet.version}</version>
+			</dependency>
+
 			<!-- JWT -->
 			<dependency>
 				<groupId>com.auth0</groupId>