Browse Source

Removed keychain module. Implemented new KeychainManager in various UI controller classes

Sebastian Stenzel 4 years ago
parent
commit
82a42ea183
30 changed files with 126 additions and 962 deletions
  1. 27 13
      main/keychain/src/main/java/org/cryptomator/keychain/KeychainManager.java
  2. 3 1
      main/commons/src/main/java/org/cryptomator/common/keychain/KeychainModule.java
  3. 13 0
      main/commons/src/main/java/org/cryptomator/common/keychain/NoKeychainAccessProviderException.java
  4. 7 4
      main/keychain/src/test/java/org/cryptomator/keychain/KeychainManagerTest.java
  5. 4 2
      main/keychain/src/test/java/org/cryptomator/keychain/MapKeychainAccess.java
  6. 0 69
      main/keychain/pom.xml
  7. 0 12
      main/keychain/src/main/java/org/cryptomator/keychain/KeychainAccessException.java
  8. 0 46
      main/keychain/src/main/java/org/cryptomator/keychain/KeychainAccessStrategy.java
  9. 0 45
      main/keychain/src/main/java/org/cryptomator/keychain/KeychainModule.java
  10. 0 126
      main/keychain/src/main/java/org/cryptomator/keychain/LinuxKDEWalletKeychainAccessImpl.java
  11. 0 81
      main/keychain/src/main/java/org/cryptomator/keychain/LinuxSecretServiceKeychainAccessImpl.java
  12. 0 106
      main/keychain/src/main/java/org/cryptomator/keychain/LinuxSystemKeychainAccess.java
  13. 0 57
      main/keychain/src/main/java/org/cryptomator/keychain/MacSystemKeychainAccess.java
  14. 0 209
      main/keychain/src/main/java/org/cryptomator/keychain/WindowsProtectedKeychainAccess.java
  15. 0 56
      main/keychain/src/test/java/org/cryptomator/keychain/WindowsProtectedKeychainAccessTest.java
  16. 0 33
      main/keychain/src/test/resources/log4j2.xml
  17. 0 18
      main/pom.xml
  18. 0 4
      main/ui/pom.xml
  19. 6 6
      main/ui/src/main/java/org/cryptomator/ui/changepassword/ChangePasswordController.java
  20. 1 5
      main/ui/src/main/java/org/cryptomator/ui/common/VaultService.java
  21. 6 7
      main/ui/src/main/java/org/cryptomator/ui/forgetPassword/ForgetPasswordController.java
  22. 1 2
      main/ui/src/main/java/org/cryptomator/ui/launcher/UiLauncherModule.java
  23. 8 8
      main/ui/src/main/java/org/cryptomator/ui/mainwindow/VaultDetailLockedController.java
  24. 7 9
      main/ui/src/main/java/org/cryptomator/ui/migration/MigrationRunController.java
  25. 0 5
      main/ui/src/main/java/org/cryptomator/ui/preferences/GeneralPreferencesController.java
  26. 4 4
      main/ui/src/main/java/org/cryptomator/ui/unlock/UnlockController.java
  27. 9 7
      main/ui/src/main/java/org/cryptomator/ui/unlock/UnlockModule.java
  28. 6 6
      main/ui/src/main/java/org/cryptomator/ui/unlock/UnlockWorkflow.java
  29. 23 13
      main/ui/src/main/java/org/cryptomator/ui/vaultoptions/MasterkeyOptionsController.java
  30. 1 8
      main/ui/src/main/resources/license/THIRD-PARTY.txt

+ 27 - 13
main/keychain/src/main/java/org/cryptomator/keychain/KeychainManager.java

@@ -1,60 +1,75 @@
-package org.cryptomator.keychain;
+package org.cryptomator.common.keychain;
 
 import com.google.common.cache.CacheBuilder;
 import com.google.common.cache.CacheLoader;
 import com.google.common.cache.LoadingCache;
+import org.cryptomator.integrations.keychain.KeychainAccessException;
+import org.cryptomator.integrations.keychain.KeychainAccessProvider;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import javax.inject.Inject;
+import javax.inject.Singleton;
 import javafx.application.Platform;
+import javafx.beans.binding.ObjectExpression;
 import javafx.beans.property.BooleanProperty;
 import javafx.beans.property.ReadOnlyBooleanProperty;
 import javafx.beans.property.SimpleBooleanProperty;
 import java.util.Arrays;
 
-public class KeychainManager implements KeychainAccessStrategy {
+@Singleton
+public class KeychainManager implements KeychainAccessProvider {
 
 	private static final Logger LOG = LoggerFactory.getLogger(KeychainManager.class);
 
-	private final KeychainAccessStrategy keychain;
+	private final ObjectExpression<KeychainAccessProvider> keychain;
 	private LoadingCache<String, BooleanProperty> passphraseStoredProperties;
 
-	KeychainManager(KeychainAccessStrategy keychain) {
-		assert keychain.isSupported();
-		this.keychain = keychain;
+	@Inject
+	KeychainManager(ObjectExpression<KeychainAccessProvider> selectedKeychain) {
+		this.keychain = selectedKeychain;
 		this.passphraseStoredProperties = CacheBuilder.newBuilder() //
 				.weakValues() //
 				.build(CacheLoader.from(this::createStoredPassphraseProperty));
+		keychain.addListener(ignored -> passphraseStoredProperties.invalidateAll());
+	}
+
+	private KeychainAccessProvider getKeychainOrFail() throws KeychainAccessException {
+		var result = keychain.getValue();
+		if (result == null) {
+			throw new NoKeychainAccessProviderException();
+		}
+		return result;
 	}
 
 	@Override
 	public void storePassphrase(String key, CharSequence passphrase) throws KeychainAccessException {
-		keychain.storePassphrase(key, passphrase);
+		getKeychainOrFail().storePassphrase(key, passphrase);
 		setPassphraseStored(key, true);
 	}
 
 	@Override
 	public char[] loadPassphrase(String key) throws KeychainAccessException {
-		char[] passphrase = keychain.loadPassphrase(key);
+		char[] passphrase = getKeychainOrFail().loadPassphrase(key);
 		setPassphraseStored(key, passphrase != null);
 		return passphrase;
 	}
 
 	@Override
 	public void deletePassphrase(String key) throws KeychainAccessException {
-		keychain.deletePassphrase(key);
+		getKeychainOrFail().deletePassphrase(key);
 		setPassphraseStored(key, false);
 	}
 
 	@Override
 	public void changePassphrase(String key, CharSequence passphrase) throws KeychainAccessException {
-		keychain.changePassphrase(key, passphrase);
+		getKeychainOrFail().changePassphrase(key, passphrase);
 		setPassphraseStored(key, true);
 	}
 
 	@Override
 	public boolean isSupported() {
-		return true;
+		return keychain.getValue() != null;
 	}
 
 	/**
@@ -69,7 +84,7 @@ public class KeychainManager implements KeychainAccessStrategy {
 	public boolean isPassphraseStored(String key) throws KeychainAccessException {
 		char[] storedPw = null;
 		try {
-			storedPw = keychain.loadPassphrase(key);
+			storedPw = getKeychainOrFail().loadPassphrase(key);
 			return storedPw != null;
 		} finally {
 			if (storedPw != null) {
@@ -107,7 +122,6 @@ public class KeychainManager implements KeychainAccessStrategy {
 
 	private BooleanProperty createStoredPassphraseProperty(String key) {
 		try {
-			LOG.warn("LOAD"); // TODO remove
 			return new SimpleBooleanProperty(isPassphraseStored(key));
 		} catch (KeychainAccessException e) {
 			return new SimpleBooleanProperty(false);

+ 3 - 1
main/commons/src/main/java/org/cryptomator/common/keychain/KeychainModule.java

@@ -8,6 +8,8 @@ import org.cryptomator.integrations.keychain.KeychainAccessProvider;
 import javax.inject.Singleton;
 import javafx.beans.binding.Bindings;
 import javafx.beans.binding.ObjectBinding;
+import javafx.beans.binding.ObjectExpression;
+import javafx.beans.value.ObservableValue;
 import java.util.ServiceLoader;
 import java.util.Set;
 import java.util.stream.Collectors;
@@ -29,7 +31,7 @@ public class KeychainModule {
 
 	@Provides
 	@Singleton
-	static ObjectBinding<KeychainAccessProvider> provideKeychainAccessProvider(Settings settings, Set<KeychainAccessProvider> providers) {
+	static ObjectExpression<KeychainAccessProvider> provideKeychainAccessProvider(Settings settings, Set<KeychainAccessProvider> providers) {
 		return Bindings.createObjectBinding(() -> {
 			var selectedProviderClass = settings.keychainBackend().get().getProviderClass();
 			var selectedProvider = providers.stream().filter(provider -> provider.getClass().getName().equals(selectedProviderClass)).findAny();

+ 13 - 0
main/commons/src/main/java/org/cryptomator/common/keychain/NoKeychainAccessProviderException.java

@@ -0,0 +1,13 @@
+package org.cryptomator.common.keychain;
+
+import org.cryptomator.integrations.keychain.KeychainAccessException;
+
+/**
+ * Thrown by {@link KeychainManager} if attempted to access a keychain despite no supported keychain access provider being available.
+ */
+public class NoKeychainAccessProviderException extends KeychainAccessException {
+
+	public NoKeychainAccessProviderException() {
+		super("Did not find any supported keychain access provider.");
+	}
+}

+ 7 - 4
main/keychain/src/test/java/org/cryptomator/keychain/KeychainManagerTest.java

@@ -1,23 +1,26 @@
-package org.cryptomator.keychain;
+package org.cryptomator.common.keychain;
 
 
+import org.cryptomator.integrations.keychain.KeychainAccessException;
 import org.junit.jupiter.api.Assertions;
 import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Nested;
 import org.junit.jupiter.api.Test;
 
 import javafx.application.Platform;
 import javafx.beans.property.ReadOnlyBooleanProperty;
+import javafx.beans.property.SimpleObjectProperty;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicBoolean;
 
 
-class KeychainManagerTest {
+public class KeychainManagerTest {
 
 	@Test
 	public void testStoreAndLoad() throws KeychainAccessException {
-		KeychainManager keychainManager = new KeychainManager(new MapKeychainAccess());
+		KeychainManager keychainManager = new KeychainManager(new SimpleObjectProperty<>(new MapKeychainAccess()));
 		keychainManager.storePassphrase("test", "asd");
 		Assertions.assertArrayEquals("asd".toCharArray(), keychainManager.loadPassphrase("test"));
 	}
@@ -34,7 +37,7 @@ class KeychainManagerTest {
 
 		@Test
 		public void testPropertyChangesWhenStoringPassword() throws KeychainAccessException, InterruptedException {
-			KeychainManager keychainManager = new KeychainManager(new MapKeychainAccess());
+			KeychainManager keychainManager = new KeychainManager(new SimpleObjectProperty<>(new MapKeychainAccess()));
 			ReadOnlyBooleanProperty property = keychainManager.getPassphraseStoredProperty("test");
 			Assertions.assertEquals(false, property.get());
 

+ 4 - 2
main/keychain/src/test/java/org/cryptomator/keychain/MapKeychainAccess.java

@@ -3,12 +3,14 @@
  * All rights reserved. This program and the accompanying materials
  * are made available under the terms of the accompanying LICENSE file.
  *******************************************************************************/
-package org.cryptomator.keychain;
+package org.cryptomator.common.keychain;
+
+import org.cryptomator.integrations.keychain.KeychainAccessProvider;
 
 import java.util.HashMap;
 import java.util.Map;
 
-class MapKeychainAccess implements KeychainAccessStrategy {
+class MapKeychainAccess implements KeychainAccessProvider {
 
 	private final Map<String, char[]> map = new HashMap<>();
 

+ 0 - 69
main/keychain/pom.xml

@@ -1,69 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
-	<modelVersion>4.0.0</modelVersion>
-	<parent>
-		<groupId>org.cryptomator</groupId>
-		<artifactId>main</artifactId>
-		<version>1.6.0-SNAPSHOT</version>
-	</parent>
-	<artifactId>keychain</artifactId>
-	<name>System Keychain Access</name>
-
-	<dependencies>
-		<dependency>
-			<groupId>org.cryptomator</groupId>
-			<artifactId>commons</artifactId>
-		</dependency>
-
-		<!-- JavaFx -->
-		<dependency>
-			<groupId>org.openjfx</groupId>
-			<artifactId>javafx-base</artifactId>
-		</dependency>
-		<dependency>
-			<groupId>org.openjfx</groupId>
-			<artifactId>javafx-graphics</artifactId>
-		</dependency>
-
-		<!-- Apache -->
-		<dependency>
-			<groupId>org.apache.commons</groupId>
-			<artifactId>commons-lang3</artifactId>
-		</dependency>
-
-		<!-- Google -->
-		<dependency>
-			<groupId>com.google.code.gson</groupId>
-			<artifactId>gson</artifactId>
-		</dependency>
-		<dependency>
-			<groupId>com.google.guava</groupId>
-			<artifactId>guava</artifactId>
-		</dependency>
-
-		<!-- DI -->
-		<dependency>
-			<groupId>com.google.dagger</groupId>
-			<artifactId>dagger</artifactId>
-		</dependency>
-
-		<!-- secret-service lib -->
-		<dependency>
-			<groupId>de.swiesend</groupId>
-			<artifactId>secret-service</artifactId>
-		</dependency>
-
-		<!-- kdewallet lib -->
-		<dependency>
-			<groupId>org.purejava</groupId>
-			<artifactId>kdewallet</artifactId>
-		</dependency>
-
-		<!-- Logging -->
-		<dependency>
-			<groupId>org.slf4j</groupId>
-			<artifactId>slf4j-simple</artifactId>
-			<scope>test</scope>
-		</dependency>
-	</dependencies>
-</project>

+ 0 - 12
main/keychain/src/main/java/org/cryptomator/keychain/KeychainAccessException.java

@@ -1,12 +0,0 @@
-package org.cryptomator.keychain;
-
-/**
- * Indicates an error during communication with the operating system's keychain.
- */
-public class KeychainAccessException extends Exception {
-
-	KeychainAccessException(Throwable cause) {
-		super(cause);
-	}
-
-}

+ 0 - 46
main/keychain/src/main/java/org/cryptomator/keychain/KeychainAccessStrategy.java

@@ -1,46 +0,0 @@
-/*******************************************************************************
- * Copyright (c) 2017 Skymatic UG (haftungsbeschränkt).
- * All rights reserved. This program and the accompanying materials
- * are made available under the terms of the accompanying LICENSE file.
- *******************************************************************************/
-package org.cryptomator.keychain;
-
-public interface KeychainAccessStrategy {
-
-	/**
-	 * Associates a passphrase with a given key.
-	 *
-	 * @param key Key used to retrieve the passphrase via {@link #loadPassphrase(String)}.
-	 * @param passphrase The secret to store in this keychain.
-	 */
-	void storePassphrase(String key, CharSequence passphrase) throws KeychainAccessException;
-
-	/**
-	 * @param key Unique key previously used while {@link #storePassphrase(String, CharSequence) storing a passphrase}.
-	 * @return The stored passphrase for the given key or <code>null</code> if no value for the given key could be found.
-	 */
-	char[] loadPassphrase(String key) throws KeychainAccessException;
-
-	/**
-	 * Deletes a passphrase with a given key.
-	 *
-	 * @param key Unique key previously used while {@link #storePassphrase(String, CharSequence) storing a passphrase}.
-	 */
-	void deletePassphrase(String key) throws KeychainAccessException;
-
-	/**
-	 * Updates a passphrase with a given key. Noop, if there is no item for the given key.
-	 *
-	 * @param key Unique key previously used while {@link #storePassphrase(String, CharSequence) storing a passphrase}.
-	 * @param passphrase The secret to be updated in this keychain.
-	 */
-	void changePassphrase(String key, CharSequence passphrase) throws KeychainAccessException;
-
-	/**
-	 * @return <code>true</code> if this KeychainAccessStrategy works on the current machine.
-	 * @implNote This method must not throw any exceptions and should fail fast
-	 * returning <code>false</code> if it can't determine availability of the checked strategy
-	 */
-	boolean isSupported();
-
-}

+ 0 - 45
main/keychain/src/main/java/org/cryptomator/keychain/KeychainModule.java

@@ -1,45 +0,0 @@
-/*******************************************************************************
- * Copyright (c) 2017 Skymatic UG (haftungsbeschränkt).
- * All rights reserved. This program and the accompanying materials
- * are made available under the terms of the accompanying LICENSE file.
- *******************************************************************************/
-package org.cryptomator.keychain;
-
-import dagger.Binds;
-import dagger.Module;
-import dagger.Provides;
-import dagger.multibindings.IntoSet;
-import org.cryptomator.common.JniModule;
-
-import javax.inject.Singleton;
-import java.util.Optional;
-import java.util.Set;
-
-@Module(includes = {JniModule.class})
-public abstract class KeychainModule {
-
-	@Binds
-	@IntoSet
-	abstract KeychainAccessStrategy bindMacSystemKeychainAccess(MacSystemKeychainAccess keychainAccessStrategy);
-
-	@Binds
-	@IntoSet
-	abstract KeychainAccessStrategy bindWindowsProtectedKeychainAccess(WindowsProtectedKeychainAccess keychainAccessStrategy);
-
-	@Binds
-	@IntoSet
-	abstract KeychainAccessStrategy bindLinuxSystemKeychainAccess(LinuxSystemKeychainAccess keychainAccessStrategy);
-
-	@Provides
-	@Singleton
-	static Optional<KeychainAccessStrategy> provideSupportedKeychain(Set<KeychainAccessStrategy> keychainAccessStrategies) {
-		return keychainAccessStrategies.stream().filter(KeychainAccessStrategy::isSupported).findFirst();
-	}
-
-	@Provides
-	@Singleton
-	public static Optional<KeychainManager> provideKeychainManager(Optional<KeychainAccessStrategy> keychainAccess) {
-		return keychainAccess.map(KeychainManager::new);
-	}
-
-}

+ 0 - 126
main/keychain/src/main/java/org/cryptomator/keychain/LinuxKDEWalletKeychainAccessImpl.java

@@ -1,126 +0,0 @@
-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 static 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() throws KeychainAccessException {
-		try {
-			connection = DBusConnection.getConnection(DBusConnection.DBusBusType.SESSION);
-		} catch (DBusException e) {
-			LOG.error("Connecting to D-Bus failed:", e);
-			throw new KeychainAccessException(e);
-		}
-	}
-
-	@Override
-	public boolean isSupported() {
-		try {
-			wallet = new KDEWallet(connection);
-			return wallet.isEnabled();
-		} catch (Exception e) {
-			LOG.error("A KDEWallet could not be created:", e);
-			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("Storing the passphrase failed:", e);
-			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) {
-			LOG.error("Loading the passphrase failed:", 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) {
-			LOG.error("Deleting the passphrase failed:", 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) {
-			LOG.error("Changing the passphrase failed:", 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) {
-			LOG.error("Asynchronous opening the wallet failed:", e);
-			throw new KeychainAccessException(e);
-		}
-	}
-}

+ 0 - 81
main/keychain/src/main/java/org/cryptomator/keychain/LinuxSecretServiceKeychainAccessImpl.java

@@ -1,81 +0,0 @@
-package org.cryptomator.keychain;
-
-import org.freedesktop.secret.simple.SimpleCollection;
-
-import java.io.IOException;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
-class LinuxSecretServiceKeychainAccessImpl implements KeychainAccessStrategy {
-
-	private final String LABEL_FOR_SECRET_IN_KEYRING = "Cryptomator";
-
-	@Override
-	public boolean isSupported() {
-		try (@SuppressWarnings("unused") SimpleCollection keyring = new SimpleCollection()) {
-			// seems like we're able to access the keyring.
-			return true;
-		} catch (IOException | RuntimeException e) {
-			return false;
-		}
-	}
-
-	@Override
-	public void storePassphrase(String key, CharSequence passphrase) throws KeychainAccessException {
-		try (SimpleCollection keyring = new SimpleCollection()) {
-			List<String> list = keyring.getItems(createAttributes(key));
-			if (list == null) {
-				keyring.createItem(LABEL_FOR_SECRET_IN_KEYRING, passphrase, createAttributes(key));
-			} else {
-				changePassphrase(key, passphrase);
-			}
-		} catch (IOException e) {
-			throw new KeychainAccessException(e);
-		}
-	}
-
-	@Override
-	public char[] loadPassphrase(String key) throws KeychainAccessException {
-		try (SimpleCollection keyring = new SimpleCollection()) {
-			List<String> list = keyring.getItems(createAttributes(key));
-			if (list != null) {
-				return keyring.getSecret(list.get(0));
-			} else {
-				return null;
-			}
-		} catch (IOException e) {
-			throw new KeychainAccessException(e);
-		}
-	}
-
-	@Override
-	public void deletePassphrase(String key) throws KeychainAccessException {
-		try (SimpleCollection keyring = new SimpleCollection()) {
-			List<String> list = keyring.getItems(createAttributes(key));
-			if (list != null) {
-				keyring.deleteItem(list.get(0));
-			}
-		} catch (IOException e) {
-			throw new KeychainAccessException(e);
-		}
-	}
-
-	@Override
-	public void changePassphrase(String key, CharSequence passphrase) throws KeychainAccessException {
-		try (SimpleCollection keyring = new SimpleCollection()) {
-			List<String> list = keyring.getItems(createAttributes(key));
-			if (list != null) {
-				keyring.updateItem(list.get(0), LABEL_FOR_SECRET_IN_KEYRING, passphrase, createAttributes(key));
-			}
-		} catch (IOException e) {
-			throw new KeychainAccessException(e);
-		}
-	}
-
-	private Map<String, String> createAttributes(String key) {
-		Map<String, String> attributes = new HashMap();
-		attributes.put("Vault", key);
-		return attributes;
-	}
-}

+ 0 - 106
main/keychain/src/main/java/org/cryptomator/keychain/LinuxSystemKeychainAccess.java

@@ -1,106 +0,0 @@
-package org.cryptomator.keychain;
-
-import javafx.beans.property.ObjectProperty;
-import org.apache.commons.lang3.SystemUtils;
-import org.cryptomator.common.settings.KeychainBackend;
-import org.cryptomator.common.settings.Settings;
-
-import javax.inject.Inject;
-import javax.inject.Singleton;
-import java.util.EnumSet;
-import java.util.Optional;
-
-/**
- * A facade to LinuxSecretServiceKeychainAccessImpl and LinuxKDEWalletKeychainAccessImpl
- * that depend on libraries that are unavailable on Mac and Windows.
- */
-@Singleton
-public class LinuxSystemKeychainAccess implements KeychainAccessStrategy {
-
-	// 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;
-	private final Settings settings;
-	private static EnumSet<KeychainBackend> availableKeychainBackends = EnumSet.noneOf(KeychainBackend.class);
-	private static KeychainBackend backendActivated = null;
-	private static boolean isGnomeKeyringAvailable;
-	private static boolean isKdeWalletAvailable;
-
-	@Inject
-	public LinuxSystemKeychainAccess(Settings settings) {
-		this.settings = settings;
-		this.delegate = constructKeychainAccess();
-	}
-
-	private Optional<KeychainAccessStrategy> constructKeychainAccess() {
-		try { // find out which backends are available
-			Class<?> clazz = Class.forName("org.cryptomator.keychain.LinuxSecretServiceKeychainAccessImpl");
-			KeychainAccessStrategy gnomeKeyring = (KeychainAccessStrategy) clazz.getDeclaredConstructor().newInstance();
-			if (gnomeKeyring.isSupported()) {
-				LinuxSystemKeychainAccess.availableKeychainBackends.add(KeychainBackend.GNOME);
-				LinuxSystemKeychainAccess.isGnomeKeyringAvailable = true;
-			}
-			clazz = Class.forName("org.cryptomator.keychain.LinuxKDEWalletKeychainAccessImpl");
-			KeychainAccessStrategy kdeWallet = (KeychainAccessStrategy) clazz.getDeclaredConstructor().newInstance();
-			if (kdeWallet.isSupported()) {
-				LinuxSystemKeychainAccess.availableKeychainBackends.add(KeychainBackend.KDE);
-				LinuxSystemKeychainAccess.isKdeWalletAvailable = true;
-			}
-
-			// load password backend setting as the preferred backend
-			ObjectProperty<KeychainBackend> pwSetting =  settings.keychainBackend();
-
-			// check for GNOME keyring first, as this gets precedence over
-			// KDE wallet as the former was implemented first
-			if (isGnomeKeyringAvailable && pwSetting.get().equals(KeychainBackend.GNOME)) {
-					pwSetting.setValue(KeychainBackend.GNOME);
-					LinuxSystemKeychainAccess.backendActivated = KeychainBackend.GNOME;
-					return Optional.of(gnomeKeyring);
-			}
-
-			if (isKdeWalletAvailable && pwSetting.get().equals(KeychainBackend.KDE)) {
-					pwSetting.setValue(KeychainBackend.KDE);
-					LinuxSystemKeychainAccess.backendActivated = KeychainBackend.KDE;
-					return Optional.of(kdeWallet);
-			}
-			return Optional.empty();
-		} catch (Exception e) {
-			return Optional.empty();
-		}
-	}
-
-	/* Getter/Setter */
-
-	public static EnumSet<KeychainBackend> getAvailableKeychainBackends() {
-		return availableKeychainBackends;
-	}
-
-	public static KeychainBackend getBackendActivated() {
-		return backendActivated;
-	}
-
-	@Override
-	public boolean isSupported() {
-		return SystemUtils.IS_OS_LINUX && delegate.map(KeychainAccessStrategy::isSupported).orElse(false);
-	}
-
-	@Override
-	public void storePassphrase(String key, CharSequence passphrase) throws KeychainAccessException {
-		delegate.orElseThrow(IllegalStateException::new).storePassphrase(key, passphrase);
-	}
-
-	@Override
-	public char[] loadPassphrase(String key) throws KeychainAccessException {
-		return delegate.orElseThrow(IllegalStateException::new).loadPassphrase(key);
-	}
-
-	@Override
-	public void deletePassphrase(String key) throws KeychainAccessException {
-		delegate.orElseThrow(IllegalStateException::new).deletePassphrase(key);
-	}
-
-	@Override
-	public void changePassphrase(String key, CharSequence passphrase) throws KeychainAccessException {
-		delegate.orElseThrow(IllegalStateException::new).changePassphrase(key, passphrase);
-	}
-}

+ 0 - 57
main/keychain/src/main/java/org/cryptomator/keychain/MacSystemKeychainAccess.java

@@ -1,57 +0,0 @@
-/*******************************************************************************
- * Copyright (c) 2017 Skymatic UG (haftungsbeschränkt).
- * All rights reserved. This program and the accompanying materials
- * are made available under the terms of the accompanying LICENSE file.
- *******************************************************************************/
-package org.cryptomator.keychain;
-
-import org.apache.commons.lang3.SystemUtils;
-import org.cryptomator.jni.MacFunctions;
-import org.cryptomator.jni.MacKeychainAccess;
-
-import javax.inject.Inject;
-import javax.inject.Singleton;
-import java.util.Optional;
-
-@Singleton
-class MacSystemKeychainAccess implements KeychainAccessStrategy {
-
-	private final Optional<MacFunctions> macFunctions;
-
-	@Inject
-	public MacSystemKeychainAccess(Optional<MacFunctions> macFunctions) {
-		this.macFunctions = macFunctions;
-	}
-
-	private MacKeychainAccess keychain() {
-		return macFunctions.orElseThrow(IllegalStateException::new).keychainAccess();
-	}
-
-	@Override
-	public void storePassphrase(String key, CharSequence passphrase) {
-		keychain().storePassword(key, passphrase);
-	}
-
-	@Override
-	public char[] loadPassphrase(String key) {
-		return keychain().loadPassword(key);
-	}
-
-	@Override
-	public boolean isSupported() {
-		return SystemUtils.IS_OS_MAC_OSX && macFunctions.isPresent();
-	}
-
-	@Override
-	public void deletePassphrase(String key) {
-		keychain().deletePassword(key);
-	}
-
-	@Override
-	public void changePassphrase(String key, CharSequence passphrase) {
-		if (keychain().deletePassword(key)) {
-			keychain().storePassword(key, passphrase);
-		}
-	}
-
-}

+ 0 - 209
main/keychain/src/main/java/org/cryptomator/keychain/WindowsProtectedKeychainAccess.java

@@ -1,209 +0,0 @@
-/*******************************************************************************
- * Copyright (c) 2017 Skymatic UG (haftungsbeschränkt).
- * All rights reserved. This program and the accompanying materials
- * are made available under the terms of the accompanying LICENSE file.
- *******************************************************************************/
-package org.cryptomator.keychain;
-
-import com.google.common.io.BaseEncoding;
-import com.google.gson.Gson;
-import com.google.gson.GsonBuilder;
-import com.google.gson.JsonDeserializationContext;
-import com.google.gson.JsonDeserializer;
-import com.google.gson.JsonElement;
-import com.google.gson.JsonParseException;
-import com.google.gson.JsonPrimitive;
-import com.google.gson.JsonSerializationContext;
-import com.google.gson.JsonSerializer;
-import com.google.gson.annotations.SerializedName;
-import com.google.gson.reflect.TypeToken;
-import org.apache.commons.lang3.SystemUtils;
-import org.cryptomator.common.Environment;
-import org.cryptomator.jni.WinDataProtection;
-import org.cryptomator.jni.WinFunctions;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import javax.inject.Inject;
-import javax.inject.Singleton;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.InputStreamReader;
-import java.io.OutputStream;
-import java.io.OutputStreamWriter;
-import java.io.Reader;
-import java.io.UncheckedIOException;
-import java.io.Writer;
-import java.lang.reflect.Type;
-import java.nio.ByteBuffer;
-import java.nio.CharBuffer;
-import java.nio.file.Files;
-import java.nio.file.NoSuchFileException;
-import java.nio.file.Path;
-import java.nio.file.StandardOpenOption;
-import java.util.Arrays;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Optional;
-import java.util.UUID;
-import java.util.stream.Collectors;
-
-import static java.nio.charset.StandardCharsets.UTF_8;
-
-@Singleton
-class WindowsProtectedKeychainAccess implements KeychainAccessStrategy {
-
-	private static final Logger LOG = LoggerFactory.getLogger(WindowsProtectedKeychainAccess.class);
-	private static final Gson GSON = new GsonBuilder().setPrettyPrinting() //
-			.registerTypeHierarchyAdapter(byte[].class, new ByteArrayJsonAdapter()) //
-			.disableHtmlEscaping().create();
-
-	private final Optional<WinFunctions> winFunctions;
-	private final List<Path> keychainPaths;
-	private Map<String, KeychainEntry> keychainEntries;
-
-	@Inject
-	public WindowsProtectedKeychainAccess(Optional<WinFunctions> winFunctions, Environment environment) {
-		this.winFunctions = winFunctions;
-		this.keychainPaths = environment.getKeychainPath().collect(Collectors.toList());
-	}
-
-	private WinDataProtection dataProtection() {
-		return winFunctions.orElseThrow(IllegalStateException::new).dataProtection();
-	}
-
-	@Override
-	public void storePassphrase(String key, CharSequence passphrase) {
-		loadKeychainEntriesIfNeeded();
-		ByteBuffer buf = UTF_8.encode(CharBuffer.wrap(passphrase));
-		byte[] cleartext = new byte[buf.remaining()];
-		buf.get(cleartext);
-		KeychainEntry entry = new KeychainEntry();
-		entry.salt = generateSalt();
-		entry.ciphertext = dataProtection().protect(cleartext, entry.salt);
-		Arrays.fill(buf.array(), (byte) 0x00);
-		Arrays.fill(cleartext, (byte) 0x00);
-		keychainEntries.put(key, entry);
-		saveKeychainEntries();
-	}
-
-	@Override
-	public char[] loadPassphrase(String key) {
-		loadKeychainEntriesIfNeeded();
-		KeychainEntry entry = keychainEntries.get(key);
-		if (entry == null) {
-			return null;
-		}
-		byte[] cleartext = dataProtection().unprotect(entry.ciphertext, entry.salt);
-		if (cleartext == null) {
-			return null;
-		}
-		CharBuffer buf = UTF_8.decode(ByteBuffer.wrap(cleartext));
-		char[] passphrase = new char[buf.remaining()];
-		buf.get(passphrase);
-		Arrays.fill(cleartext, (byte) 0x00);
-		Arrays.fill(buf.array(), (char) 0x00);
-		return passphrase;
-	}
-
-	@Override
-	public void deletePassphrase(String key) {
-		loadKeychainEntriesIfNeeded();
-		keychainEntries.remove(key);
-		saveKeychainEntries();
-	}
-
-	@Override
-	public void changePassphrase(String key, CharSequence passphrase) {
-		loadKeychainEntriesIfNeeded();
-		if (keychainEntries.remove(key) != null) {
-			storePassphrase(key, passphrase);
-		}
-	}
-
-	@Override
-	public boolean isSupported() {
-		return SystemUtils.IS_OS_WINDOWS && winFunctions.isPresent() && !keychainPaths.isEmpty();
-	}
-
-	private byte[] generateSalt() {
-		byte[] result = new byte[2 * Long.BYTES];
-		UUID uuid = UUID.randomUUID();
-		ByteBuffer buf = ByteBuffer.wrap(result);
-		buf.putLong(uuid.getMostSignificantBits());
-		buf.putLong(uuid.getLeastSignificantBits());
-		return result;
-	}
-
-	private void loadKeychainEntriesIfNeeded() {
-		if (keychainEntries == null) {
-			for (Path keychainPath : keychainPaths) {
-				Optional<Map<String, KeychainEntry>> keychain = loadKeychainEntries(keychainPath);
-				if (keychain.isPresent()) {
-					keychainEntries = keychain.get();
-					break;
-				}
-			}
-		}
-		if (keychainEntries == null) {
-			LOG.info("Unable to load existing keychain file, creating new keychain.");
-			keychainEntries = new HashMap<>();
-		}
-	}
-
-	private Optional<Map<String, KeychainEntry>> loadKeychainEntries(Path keychainPath) {
-		LOG.debug("Attempting to load keychain from {}", keychainPath);
-		Type type = new TypeToken<Map<String, KeychainEntry>>() {
-		}.getType();
-		try (InputStream in = Files.newInputStream(keychainPath, StandardOpenOption.READ); //
-			 Reader reader = new InputStreamReader(in, UTF_8)) {
-			return Optional.of(GSON.fromJson(reader, type));
-		} catch (NoSuchFileException | JsonParseException e) {
-			return Optional.empty();
-		} catch (IOException e) {
-			throw new UncheckedIOException("Could not read keychain from path " + keychainPath, e);
-		}
-	}
-
-	private void saveKeychainEntries() {
-		if (keychainPaths.isEmpty()) {
-			throw new IllegalStateException("Can't save keychain if no keychain path is specified.");
-		}
-		saveKeychainEntries(keychainPaths.get(0));
-	}
-
-	private void saveKeychainEntries(Path keychainPath) {
-		try (OutputStream out = Files.newOutputStream(keychainPath, StandardOpenOption.WRITE, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING); //
-			 Writer writer = new OutputStreamWriter(out, UTF_8)) {
-			GSON.toJson(keychainEntries, writer);
-		} catch (IOException e) {
-			throw new UncheckedIOException("Could not read keychain from path " + keychainPath, e);
-		}
-	}
-
-	private static class KeychainEntry {
-
-		@SerializedName("ciphertext")
-		byte[] ciphertext;
-		@SerializedName("salt")
-		byte[] salt;
-	}
-
-	private static class ByteArrayJsonAdapter implements JsonSerializer<byte[]>, JsonDeserializer<byte[]> {
-
-		private static final BaseEncoding BASE64 = BaseEncoding.base64();
-
-		@Override
-		public byte[] deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
-			return BASE64.decode(json.getAsString());
-		}
-
-		@Override
-		public JsonElement serialize(byte[] src, Type typeOfSrc, JsonSerializationContext context) {
-			return new JsonPrimitive(BASE64.encode(src));
-		}
-
-	}
-
-}

+ 0 - 56
main/keychain/src/test/java/org/cryptomator/keychain/WindowsProtectedKeychainAccessTest.java

@@ -1,56 +0,0 @@
-/*******************************************************************************
- * Copyright (c) 2017 Skymatic UG (haftungsbeschränkt).
- * All rights reserved. This program and the accompanying materials
- * are made available under the terms of the accompanying LICENSE file.
- *******************************************************************************/
-package org.cryptomator.keychain;
-
-import org.cryptomator.common.Environment;
-import org.cryptomator.jni.WinDataProtection;
-import org.cryptomator.jni.WinFunctions;
-import org.junit.jupiter.api.Assertions;
-import org.junit.jupiter.api.BeforeEach;
-import org.junit.jupiter.api.Test;
-import org.junit.jupiter.api.io.TempDir;
-import org.mockito.Mockito;
-import org.mockito.stubbing.Answer;
-
-import java.nio.file.Path;
-import java.util.Optional;
-import java.util.stream.Stream;
-
-public class WindowsProtectedKeychainAccessTest {
-
-	private WindowsProtectedKeychainAccess keychain;
-
-	@BeforeEach
-	public void setup(@TempDir Path tempDir) {
-		Path keychainPath = tempDir.resolve("keychainfile.tmp");
-		Environment env = Mockito.mock(Environment.class);
-		Mockito.when(env.getKeychainPath()).thenReturn(Stream.of(keychainPath));
-		WinFunctions winFunctions = Mockito.mock(WinFunctions.class);
-		WinDataProtection winDataProtection = Mockito.mock(WinDataProtection.class);
-		Mockito.when(winFunctions.dataProtection()).thenReturn(winDataProtection);
-		Answer<byte[]> answerReturningFirstArg = invocation -> ((byte[]) invocation.getArgument(0)).clone();
-		Mockito.when(winDataProtection.protect(Mockito.any(), Mockito.any())).thenAnswer(answerReturningFirstArg);
-		Mockito.when(winDataProtection.unprotect(Mockito.any(), Mockito.any())).thenAnswer(answerReturningFirstArg);
-		keychain = new WindowsProtectedKeychainAccess(Optional.of(winFunctions), env);
-	}
-
-	@Test
-	public void testStoreAndLoad() {
-		String storedPw1 = "topSecret";
-		String storedPw2 = "bottomSecret";
-		keychain.storePassphrase("myPassword", storedPw1);
-		keychain.storePassphrase("myOtherPassword", storedPw2);
-		String loadedPw1 = new String(keychain.loadPassphrase("myPassword"));
-		String loadedPw2 = new String(keychain.loadPassphrase("myOtherPassword"));
-		Assertions.assertEquals(storedPw1, loadedPw1);
-		Assertions.assertEquals(storedPw2, loadedPw2);
-		keychain.deletePassphrase("myPassword");
-		Assertions.assertNull(keychain.loadPassphrase("myPassword"));
-		Assertions.assertNotNull(keychain.loadPassphrase("myOtherPassword"));
-		Assertions.assertNull(keychain.loadPassphrase("nonExistingPassword"));
-	}
-
-}

+ 0 - 33
main/keychain/src/test/resources/log4j2.xml

@@ -1,33 +0,0 @@
-<?xml version="1.0" encoding="UTF-8" ?>
-<!--
-  Copyright (c) 2014 Markus Kreusch
-  This file is licensed under the terms of the MIT license.
-  See the LICENSE.txt file for more info.
-  
-  Contributors:
-      Sebastian Stenzel - log4j config for WebDAV unit tests
--->
-<Configuration status="WARN">
-
-	<Appenders>
-		<Console name="Console" target="SYSTEM_OUT">
-			<PatternLayout pattern="%16d %-5p [%c{1}:%L] %m%n"/>
-			<ThresholdFilter level="WARN" onMatch="DENY" onMismatch="ACCEPT"/>
-		</Console>
-		<Console name="StdErr" target="SYSTEM_ERR">
-			<PatternLayout pattern="%16d %-5p [%c{1}:%L] %m%n"/>
-			<ThresholdFilter level="WARN" onMatch="ACCEPT" onMismatch="DENY"/>
-		</Console>
-	</Appenders>
-
-	<Loggers>
-		<!-- show our own debug messages: -->
-		<Logger name="org.cryptomator" level="DEBUG"/>
-		<!-- mute dependencies: -->
-		<Root level="INFO">
-			<AppenderRef ref="Console"/>
-			<AppenderRef ref="StdErr"/>
-		</Root>
-	</Loggers>
-
-</Configuration>

+ 0 - 18
main/pom.xml

@@ -68,11 +68,6 @@
 				<artifactId>commons</artifactId>
 				<version>${project.version}</version>
 			</dependency>
-			<dependency>
-				<groupId>org.cryptomator</groupId>
-				<artifactId>keychain</artifactId>
-				<version>${project.version}</version>
-			</dependency>
 			<dependency>
 				<groupId>org.cryptomator</groupId>
 				<artifactId>ui</artifactId>
@@ -187,18 +182,6 @@
 				<version>${commons-lang3.version}</version>
 			</dependency>
 
-			<!-- Linux System Keychain -->
-			<dependency> <!-- deprecated: will be replaced by integrations-api -->
-				<groupId>de.swiesend</groupId>
-				<artifactId>secret-service</artifactId>
-				<version>${secret-service.version}</version>
-			</dependency>
-			<dependency> <!-- deprecated: will be replaced by integrations-api -->
-				<groupId>org.purejava</groupId>
-				<artifactId>kdewallet</artifactId>
-				<version>${kdewallet.version}</version>
-			</dependency>
-
 			<!-- JWT -->
 			<dependency>
 				<groupId>com.auth0</groupId>
@@ -280,7 +263,6 @@
 
 	<modules>
 		<module>commons</module>
-		<module>keychain</module>
 		<module>ui</module>
 		<module>launcher</module>
 	</modules>

+ 0 - 4
main/ui/pom.xml

@@ -10,10 +10,6 @@
 	<name>Cryptomator GUI</name>
 
 	<dependencies>
-		<dependency>
-			<groupId>org.cryptomator</groupId>
-			<artifactId>keychain</artifactId>
-		</dependency>
 		<dependency>
 			<groupId>org.cryptomator</groupId>
 			<artifactId>commons</artifactId>

+ 6 - 6
main/ui/src/main/java/org/cryptomator/ui/changepassword/ChangePasswordController.java

@@ -1,10 +1,10 @@
 package org.cryptomator.ui.changepassword;
 
+import org.cryptomator.common.keychain.KeychainManager;
 import org.cryptomator.common.vaults.Vault;
 import org.cryptomator.cryptofs.CryptoFileSystemProvider;
 import org.cryptomator.cryptolib.api.InvalidPassphraseException;
-import org.cryptomator.keychain.KeychainAccessException;
-import org.cryptomator.keychain.KeychainManager;
+import org.cryptomator.integrations.keychain.KeychainAccessException;
 import org.cryptomator.ui.common.Animations;
 import org.cryptomator.ui.common.ErrorComponent;
 import org.cryptomator.ui.common.FxController;
@@ -36,14 +36,14 @@ public class ChangePasswordController implements FxController {
 	private final Vault vault;
 	private final ObjectProperty<CharSequence> newPassword;
 	private final ErrorComponent.Builder errorComponent;
-	private final Optional<KeychainManager> keychain;
+	private final KeychainManager keychain;
 
 	public NiceSecurePasswordField oldPasswordField;
 	public CheckBox finalConfirmationCheckbox;
 	public Button finishButton;
 
 	@Inject
-	public ChangePasswordController(@ChangePasswordWindow Stage window, @ChangePasswordWindow Vault vault, @Named("newPassword") ObjectProperty<CharSequence> newPassword, ErrorComponent.Builder errorComponent, Optional<KeychainManager> keychain) {
+	public ChangePasswordController(@ChangePasswordWindow Stage window, @ChangePasswordWindow Vault vault, @Named("newPassword") ObjectProperty<CharSequence> newPassword, ErrorComponent.Builder errorComponent, KeychainManager keychain) {
 		this.window = window;
 		this.vault = vault;
 		this.newPassword = newPassword;
@@ -82,9 +82,9 @@ public class ChangePasswordController implements FxController {
 	}
 
 	private void updatePasswordInSystemkeychain() {
-		if (keychain.isPresent()) {
+		if (keychain.isSupported()) {
 			try {
-				keychain.get().changePassphrase(vault.getId(), CharBuffer.wrap(newPassword.get()));
+				keychain.changePassphrase(vault.getId(), CharBuffer.wrap(newPassword.get()));
 				LOG.info("Successfully updated password in system keychain for {}", vault.getDisplayName());
 			} catch (KeychainAccessException e) {
 				LOG.error("Failed to update password in system keychain.", e);

+ 1 - 5
main/ui/src/main/java/org/cryptomator/ui/common/VaultService.java

@@ -3,7 +3,6 @@ package org.cryptomator.ui.common;
 import org.cryptomator.common.vaults.Vault;
 import org.cryptomator.common.vaults.VaultState;
 import org.cryptomator.common.vaults.Volume;
-import org.cryptomator.keychain.KeychainManager;
 import org.cryptomator.ui.fxapp.FxApplicationScoped;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -14,7 +13,6 @@ import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Iterator;
 import java.util.List;
-import java.util.Optional;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.ExecutorService;
 import java.util.stream.Collectors;
@@ -25,12 +23,10 @@ public class VaultService {
 	private static final Logger LOG = LoggerFactory.getLogger(VaultService.class);
 
 	private final ExecutorService executorService;
-	private final Optional<KeychainManager> keychain;
 
 	@Inject
-	public VaultService(ExecutorService executorService, Optional<KeychainManager> keychain) {
+	public VaultService(ExecutorService executorService) {
 		this.executorService = executorService;
-		this.keychain = keychain;
 	}
 
 	public void reveal(Vault vault) {

+ 6 - 7
main/ui/src/main/java/org/cryptomator/ui/forgetPassword/ForgetPasswordController.java

@@ -1,8 +1,8 @@
 package org.cryptomator.ui.forgetPassword;
 
+import org.cryptomator.common.keychain.KeychainManager;
 import org.cryptomator.common.vaults.Vault;
-import org.cryptomator.keychain.KeychainAccessException;
-import org.cryptomator.keychain.KeychainManager;
+import org.cryptomator.integrations.keychain.KeychainAccessException;
 import org.cryptomator.ui.common.FxController;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -11,7 +11,6 @@ import javax.inject.Inject;
 import javafx.beans.property.BooleanProperty;
 import javafx.fxml.FXML;
 import javafx.stage.Stage;
-import java.util.Optional;
 
 @ForgetPasswordScoped
 public class ForgetPasswordController implements FxController {
@@ -20,11 +19,11 @@ public class ForgetPasswordController implements FxController {
 
 	private final Stage window;
 	private final Vault vault;
-	private final Optional<KeychainManager> keychain;
+	private final KeychainManager keychain;
 	private final BooleanProperty confirmedResult;
 
 	@Inject
-	public ForgetPasswordController(@ForgetPasswordWindow Stage window, @ForgetPasswordWindow Vault vault, Optional<KeychainManager> keychain, @ForgetPasswordWindow BooleanProperty confirmedResult) {
+	public ForgetPasswordController(@ForgetPasswordWindow Stage window, @ForgetPasswordWindow Vault vault, KeychainManager keychain, @ForgetPasswordWindow BooleanProperty confirmedResult) {
 		this.window = window;
 		this.vault = vault;
 		this.keychain = keychain;
@@ -38,9 +37,9 @@ public class ForgetPasswordController implements FxController {
 
 	@FXML
 	public void finish() {
-		if (keychain.isPresent()) {
+		if (keychain.isSupported()) {
 			try {
-				keychain.get().deletePassphrase(vault.getId());
+				keychain.deletePassphrase(vault.getId());
 				LOG.debug("Forgot password for vault {}.", vault.getDisplayName());
 				confirmedResult.setValue(true);
 			} catch (KeychainAccessException e) {

+ 1 - 2
main/ui/src/main/java/org/cryptomator/ui/launcher/UiLauncherModule.java

@@ -3,7 +3,6 @@ package org.cryptomator.ui.launcher;
 import dagger.Module;
 import dagger.Provides;
 import org.cryptomator.common.JniModule;
-import org.cryptomator.keychain.KeychainModule;
 import org.cryptomator.ui.fxapp.FxApplicationComponent;
 import org.cryptomator.ui.traymenu.TrayMenuComponent;
 
@@ -13,7 +12,7 @@ import java.util.ResourceBundle;
 import java.util.concurrent.ArrayBlockingQueue;
 import java.util.concurrent.BlockingQueue;
 
-@Module(includes = {JniModule.class, KeychainModule.class}, subcomponents = {TrayMenuComponent.class, FxApplicationComponent.class})
+@Module(includes = {JniModule.class}, subcomponents = {TrayMenuComponent.class, FxApplicationComponent.class})
 public abstract class UiLauncherModule {
 
 	@Provides

+ 8 - 8
main/ui/src/main/java/org/cryptomator/ui/mainwindow/VaultDetailLockedController.java

@@ -1,8 +1,8 @@
 package org.cryptomator.ui.mainwindow;
 
 import com.tobiasdiez.easybind.EasyBind;
+import org.cryptomator.common.keychain.KeychainManager;
 import org.cryptomator.common.vaults.Vault;
-import org.cryptomator.keychain.KeychainManager;
 import org.cryptomator.ui.common.FxController;
 import org.cryptomator.ui.fxapp.FxApplication;
 import org.cryptomator.ui.vaultoptions.VaultOptionsComponent;
@@ -22,19 +22,19 @@ public class VaultDetailLockedController implements FxController {
 	private final ReadOnlyObjectProperty<Vault> vault;
 	private final FxApplication application;
 	private final VaultOptionsComponent.Builder vaultOptionsWindow;
-	private final Optional<KeychainManager> keychainManagerOptional;
+	private final KeychainManager keychain;
 	private final Stage mainWindow;
 	private final BooleanExpression passwordSaved;
 
 	@Inject
-	VaultDetailLockedController(ObjectProperty<Vault> vault, FxApplication application, VaultOptionsComponent.Builder vaultOptionsWindow, Optional<KeychainManager> keychainManagerOptional, @MainWindow Stage mainWindow) {
+	VaultDetailLockedController(ObjectProperty<Vault> vault, FxApplication application, VaultOptionsComponent.Builder vaultOptionsWindow, KeychainManager keychain, @MainWindow Stage mainWindow) {
 		this.vault = vault;
 		this.application = application;
 		this.vaultOptionsWindow = vaultOptionsWindow;
-		this.keychainManagerOptional = keychainManagerOptional;
+		this.keychain = keychain;
 		this.mainWindow = mainWindow;
-		if (keychainManagerOptional.isPresent()) {
-			this.passwordSaved = BooleanExpression.booleanExpression(EasyBind.select(vault).selectObject(v -> keychainManagerOptional.get().getPassphraseStoredProperty(v.getId())));
+		if (keychain.isSupported()) {
+			this.passwordSaved = BooleanExpression.booleanExpression(EasyBind.select(vault).selectObject(v -> keychain.getPassphraseStoredProperty(v.getId())));
 		} else {
 			this.passwordSaved = new SimpleBooleanProperty(false);
 		}
@@ -65,8 +65,8 @@ public class VaultDetailLockedController implements FxController {
 	}
 
 	public boolean isPasswordSaved() {
-		if (keychainManagerOptional.isPresent() && vault.get() != null) {
-			return keychainManagerOptional.get().getPassphraseStoredProperty(vault.get().getId()).get();
+		if (keychain.isSupported() && vault.get() != null) {
+			return keychain.getPassphraseStoredProperty(vault.get().getId()).get();
 		} else return false;
 	}
 }

File diff suppressed because it is too large
+ 7 - 9
main/ui/src/main/java/org/cryptomator/ui/migration/MigrationRunController.java


+ 0 - 5
main/ui/src/main/java/org/cryptomator/ui/preferences/GeneralPreferencesController.java

@@ -1,14 +1,11 @@
 package org.cryptomator.ui.preferences;
 
-import org.apache.commons.lang3.SystemUtils;
 import org.cryptomator.common.Environment;
 import org.cryptomator.common.LicenseHolder;
 import org.cryptomator.common.settings.KeychainBackend;
 import org.cryptomator.common.settings.Settings;
 import org.cryptomator.common.settings.UiTheme;
 import org.cryptomator.integrations.keychain.KeychainAccessProvider;
-import org.cryptomator.keychain.KeychainAccessStrategy;
-import org.cryptomator.keychain.LinuxSystemKeychainAccess;
 import org.cryptomator.ui.common.FxController;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -28,9 +25,7 @@ import javafx.scene.control.RadioButton;
 import javafx.scene.control.Toggle;
 import javafx.scene.control.ToggleGroup;
 import javafx.util.StringConverter;
-
 import java.util.Arrays;
-import java.util.EnumSet;
 import java.util.Optional;
 import java.util.ResourceBundle;
 import java.util.Set;

+ 4 - 4
main/ui/src/main/java/org/cryptomator/ui/unlock/UnlockController.java

@@ -1,7 +1,7 @@
 package org.cryptomator.ui.unlock;
 
+import org.cryptomator.common.keychain.KeychainManager;
 import org.cryptomator.common.vaults.Vault;
-import org.cryptomator.keychain.KeychainManager;
 import org.cryptomator.ui.common.FxController;
 import org.cryptomator.ui.common.UserInteractionLock;
 import org.cryptomator.ui.common.WeakBindings;
@@ -49,7 +49,7 @@ public class UnlockController implements FxController {
 	private final Optional<char[]> savedPassword;
 	private final UserInteractionLock<UnlockModule.PasswordEntry> passwordEntryLock;
 	private final ForgetPasswordComponent.Builder forgetPassword;
-	private final Optional<KeychainManager> keychain;
+	private final KeychainManager keychain;
 	private final ObjectBinding<ContentDisplay> unlockButtonContentDisplay;
 	private final BooleanBinding userInteractionDisabled;
 	private final BooleanProperty unlockButtonDisabled;
@@ -65,7 +65,7 @@ public class UnlockController implements FxController {
 	public Animation unlockAnimation;
 
 	@Inject
-	public UnlockController(@UnlockWindow Stage window, @UnlockWindow Vault vault, AtomicReference<char[]> password, @Named("savePassword") AtomicBoolean savePassword, @Named("savedPassword") Optional<char[]> savedPassword, UserInteractionLock<UnlockModule.PasswordEntry> passwordEntryLock, ForgetPasswordComponent.Builder forgetPassword, Optional<KeychainManager> keychain) {
+	public UnlockController(@UnlockWindow Stage window, @UnlockWindow Vault vault, AtomicReference<char[]> password, @Named("savePassword") AtomicBoolean savePassword, @Named("savedPassword") Optional<char[]> savedPassword, UserInteractionLock<UnlockModule.PasswordEntry> passwordEntryLock, ForgetPasswordComponent.Builder forgetPassword, KeychainManager keychain) {
 		this.window = window;
 		this.vault = vault;
 		this.password = password;
@@ -214,6 +214,6 @@ public class UnlockController implements FxController {
 	}
 
 	public boolean isKeychainAccessAvailable() {
-		return keychain.isPresent();
+		return keychain.isSupported();
 	}
 }

+ 9 - 7
main/ui/src/main/java/org/cryptomator/ui/unlock/UnlockModule.java

@@ -4,9 +4,9 @@ import dagger.Binds;
 import dagger.Module;
 import dagger.Provides;
 import dagger.multibindings.IntoMap;
+import org.cryptomator.common.keychain.KeychainManager;
 import org.cryptomator.common.vaults.Vault;
-import org.cryptomator.keychain.KeychainAccessException;
-import org.cryptomator.keychain.KeychainManager;
+import org.cryptomator.integrations.keychain.KeychainAccessException;
 import org.cryptomator.ui.common.DefaultSceneFactory;
 import org.cryptomator.ui.common.FXMLLoaderFactory;
 import org.cryptomator.ui.common.FxController;
@@ -49,15 +49,17 @@ abstract class UnlockModule {
 	@Provides
 	@Named("savedPassword")
 	@UnlockScoped
-	static Optional<char[]> provideStoredPassword(Optional<KeychainManager> keychain, @UnlockWindow Vault vault) {
-		return keychain.map(k -> {
+	static Optional<char[]> provideStoredPassword(KeychainManager keychain, @UnlockWindow Vault vault) {
+		if (keychain.isSupported()) {
+			return Optional.empty();
+		} else {
 			try {
-				return k.loadPassphrase(vault.getId());
+				return Optional.ofNullable(keychain.loadPassphrase(vault.getId()));
 			} catch (KeychainAccessException e) {
 				LOG.error("Failed to load entry from system keychain.", e);
-				return null;
+				return Optional.empty();
 			}
-		});
+		}
 	}
 
 	@Provides

File diff suppressed because it is too large
+ 6 - 6
main/ui/src/main/java/org/cryptomator/ui/unlock/UnlockWorkflow.java


+ 23 - 13
main/ui/src/main/java/org/cryptomator/ui/vaultoptions/MasterkeyOptionsController.java

@@ -1,11 +1,13 @@
 package org.cryptomator.ui.vaultoptions;
 
+import org.cryptomator.common.keychain.KeychainManager;
 import org.cryptomator.common.vaults.Vault;
-import org.cryptomator.keychain.KeychainAccessException;
-import org.cryptomator.keychain.KeychainManager;
+import org.cryptomator.integrations.keychain.KeychainAccessException;
 import org.cryptomator.ui.changepassword.ChangePasswordComponent;
 import org.cryptomator.ui.common.FxController;
 import org.cryptomator.ui.recoverykey.RecoveryKeyComponent;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 import javax.inject.Inject;
 import javafx.beans.binding.Bindings;
@@ -13,29 +15,32 @@ import javafx.beans.binding.BooleanExpression;
 import javafx.beans.property.SimpleBooleanProperty;
 import javafx.fxml.FXML;
 import javafx.stage.Stage;
-import java.util.Optional;
 
 @VaultOptionsScoped
 public class MasterkeyOptionsController implements FxController {
 
+	private static final Logger LOG = LoggerFactory.getLogger(MasterkeyOptionsController.class);
+
 	private final Vault vault;
 	private final Stage window;
 	private final ChangePasswordComponent.Builder changePasswordWindow;
 	private final RecoveryKeyComponent.Builder recoveryKeyWindow;
-	private final Optional<KeychainManager> keychainManagerOptional;
+	private final KeychainManager keychain;
 	private final BooleanExpression passwordSaved;
 
 
 	@Inject
-	MasterkeyOptionsController(@VaultOptionsWindow Vault vault, @VaultOptionsWindow Stage window, ChangePasswordComponent.Builder changePasswordWindow, RecoveryKeyComponent.Builder recoveryKeyWindow, Optional<KeychainManager> keychainManagerOptional) {
+	MasterkeyOptionsController(@VaultOptionsWindow Vault vault, @VaultOptionsWindow Stage window, ChangePasswordComponent.Builder changePasswordWindow, RecoveryKeyComponent.Builder recoveryKeyWindow, KeychainManager keychain) {
 		this.vault = vault;
 		this.window = window;
 		this.changePasswordWindow = changePasswordWindow;
 		this.recoveryKeyWindow = recoveryKeyWindow;
-		this.keychainManagerOptional = keychainManagerOptional;
-		if (keychainManagerOptional.isPresent()) {
-			this.passwordSaved = Bindings.createBooleanBinding(this::isPasswordSaved, keychainManagerOptional.get().getPassphraseStoredProperty(vault.getId()));
-		} else this.passwordSaved = new SimpleBooleanProperty(false);
+		this.keychain = keychain;
+		if (keychain.isSupported()) {
+			this.passwordSaved = Bindings.createBooleanBinding(this::isPasswordSaved, keychain.getPassphraseStoredProperty(vault.getId()));
+		} else {
+			this.passwordSaved = new SimpleBooleanProperty(false);
+		}
 	}
 
 	@FXML
@@ -54,8 +59,13 @@ public class MasterkeyOptionsController implements FxController {
 	}
 
 	@FXML
-	public void removePasswordFromKeychain() throws KeychainAccessException {
-		keychainManagerOptional.get().deletePassphrase(vault.getId());
+	public void removePasswordFromKeychain() {
+		assert keychain.isSupported();
+		try {
+			keychain.deletePassphrase(vault.getId());
+		} catch (KeychainAccessException e) {
+			LOG.error("Failed to delete passphrase from system keychain.", e);
+		}
 		window.close();
 	}
 
@@ -64,8 +74,8 @@ public class MasterkeyOptionsController implements FxController {
 	}
 
 	public boolean isPasswordSaved() {
-		if (keychainManagerOptional.isPresent() && vault != null) {
-			return keychainManagerOptional.get().getPassphraseStoredProperty(vault.getId()).get();
+		if (keychain.isSupported() && vault != null) {
+			return keychain.getPassphraseStoredProperty(vault.getId()).get();
 		} else return false;
 	}
 }

+ 1 - 8
main/ui/src/main/resources/license/THIRD-PARTY.txt

@@ -11,15 +11,12 @@ GNU General Public License for more details.
 You should have received a copy of the GNU General Public License
 along with this program.  If not, see http://www.gnu.org/licenses/.
 
-Cryptomator uses 53 third-party dependencies under the following licenses:
+Cryptomator uses 46 third-party dependencies under the following licenses:
         Apache License v2.0:
-			- HKDF-RFC5869 (at.favre.lib:hkdf:1.1.0 - https://github.com/patrickfav/hkdf)
 			- jffi (com.github.jnr:jffi:1.2.23 - http://github.com/jnr/jffi)
 			- jnr-a64asm (com.github.jnr:jnr-a64asm:1.0.0 - http://nexus.sonatype.org/oss-repository-hosting.html/jnr-a64asm)
 			- jnr-constants (com.github.jnr:jnr-constants:0.9.15 - http://github.com/jnr/jnr-constants)
-			- jnr-enxio (com.github.jnr:jnr-enxio:0.28 - http://github.com/jnr/jnr-enxio)
 			- jnr-ffi (com.github.jnr:jnr-ffi:2.1.12 - http://github.com/jnr/jnr-ffi)
-			- jnr-unixsocket (com.github.jnr:jnr-unixsocket:0.33 - http://github.com/jnr/jnr-unixsocket)
 			- FindBugs-jsr305 (com.google.code.findbugs:jsr305:3.0.2 - http://findbugs.sourceforge.net/)
 			- Gson (com.google.code.gson:gson:2.8.6 - https://github.com/google/gson/gson)
 			- Dagger (com.google.dagger:dagger:2.22 - https://github.com/google/dagger)
@@ -70,19 +67,15 @@ Cryptomator uses 53 third-party dependencies under the following licenses:
 			- javafx-fxml (org.openjfx:javafx-fxml:14 - https://openjdk.java.net/projects/openjfx/javafx-fxml/)
 			- javafx-graphics (org.openjfx:javafx-graphics:14 - https://openjdk.java.net/projects/openjfx/javafx-graphics/)
         LGPL 2.1:
-			- dbus-java (com.github.hypfvieh:dbus-java:3.2.3 - https://github.com/hypfvieh/dbus-java/dbus-java)
 			- jnr-posix (com.github.jnr:jnr-posix:3.0.54 - http://nexus.sonatype.org/oss-repository-hosting.html/jnr-posix)
 			- Java Native Access (net.java.dev.jna:jna:5.1.0 - https://github.com/java-native-access/jna)
 			- Java Native Access Platform (net.java.dev.jna:jna-platform:5.1.0 - https://github.com/java-native-access/jna)
         MIT License:
 			- java jwt (com.auth0:java-jwt:3.10.3 - https://github.com/auth0/java-jwt)
-			- java-utils (com.github.hypfvieh:java-utils:1.0.6 - https://github.com/hypfvieh/java-utils)
 			- jnr-x86asm (com.github.jnr:jnr-x86asm:1.0.2 - http://github.com/jnr/jnr-x86asm)
 			- jnr-fuse (com.github.serceman:jnr-fuse:0.5.4 - no url defined)
 			- zxcvbn4j (com.nulab-inc:zxcvbn:1.3.0 - https://github.com/nulab/zxcvbn4j)
-			- secret-service (de.swiesend:secret-service:1.1.0 - https://github.com/swiesend/secret-service)
 			- Checker Qual (org.checkerframework:checker-qual:3.5.0 - https://checkerframework.org)
-			- kdewallet (org.purejava:kdewallet:1.1.1 - https://github.com/purejava/kdewallet)
 			- SLF4J API Module (org.slf4j:slf4j-api:1.7.30 - http://www.slf4j.org)
         The BSD 2-Clause License:
 			- EasyBind (com.tobiasdiez:easybind:2.1.0 - https://github.com/tobiasdiez/EasyBind)