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

This commit is contained in:
Sebastian Stenzel
2020-11-02 14:03:39 +01:00
parent bfde26be79
commit 82a42ea183
30 changed files with 126 additions and 962 deletions

View File

@@ -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);

View File

@@ -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();

View File

@@ -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.");
}
}

View File

@@ -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());

View File

@@ -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<>();

View File

@@ -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>

View File

@@ -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);
}
}

View File

@@ -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();
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}
}

View File

@@ -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;
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}
}

View File

@@ -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));
}
}
}

View File

@@ -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"));
}
}

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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);

View File

@@ -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) {

View File

@@ -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) {

View File

@@ -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

View File

@@ -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;
}
}

View File

@@ -1,6 +1,7 @@
package org.cryptomator.ui.migration;
import dagger.Lazy;
import org.cryptomator.common.keychain.KeychainManager;
import org.cryptomator.common.vaults.Vault;
import org.cryptomator.common.vaults.VaultState;
import org.cryptomator.cryptofs.FileNameTooLongException;
@@ -9,8 +10,7 @@ import org.cryptomator.cryptofs.migration.Migrators;
import org.cryptomator.cryptofs.migration.api.MigrationContinuationListener;
import org.cryptomator.cryptofs.migration.api.MigrationProgressListener;
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;
@@ -37,7 +37,6 @@ import javafx.scene.Scene;
import javafx.scene.control.ContentDisplay;
import javafx.stage.Stage;
import java.util.Arrays;
import java.util.Optional;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
@@ -55,7 +54,7 @@ public class MigrationRunController implements FxController {
private final Vault vault;
private final ExecutorService executor;
private final ScheduledExecutorService scheduler;
private final Optional<KeychainManager> keychain;
private final KeychainManager keychain;
private final ObjectProperty<FileSystemCapabilityChecker.Capability> missingCapability;
private final ErrorComponent.Builder errorComponent;
private final Lazy<Scene> startScene;
@@ -69,8 +68,7 @@ public class MigrationRunController implements FxController {
public NiceSecurePasswordField passwordField;
@Inject
public MigrationRunController(@MigrationWindow Stage window, @MigrationWindow Vault vault, ExecutorService executor, ScheduledExecutorService scheduler, Optional<KeychainManager> keychain, @Named("capabilityErrorCause") ObjectProperty<FileSystemCapabilityChecker.Capability> missingCapability, @FxmlScene(FxmlFile.MIGRATION_START) Lazy<Scene> startScene, @FxmlScene(FxmlFile.MIGRATION_SUCCESS) Lazy<Scene> successScene, @FxmlScene(FxmlFile.MIGRATION_CAPABILITY_ERROR) Lazy<Scene> capabilityErrorScene, @FxmlScene(FxmlFile.MIGRATION_IMPOSSIBLE) Lazy<Scene> impossibleScene, ErrorComponent.Builder errorComponent) {
public MigrationRunController(@MigrationWindow Stage window, @MigrationWindow Vault vault, ExecutorService executor, ScheduledExecutorService scheduler, KeychainManager keychain, @Named("capabilityErrorCause") ObjectProperty<FileSystemCapabilityChecker.Capability> missingCapability, @FxmlScene(FxmlFile.MIGRATION_START) Lazy<Scene> startScene, @FxmlScene(FxmlFile.MIGRATION_SUCCESS) Lazy<Scene> successScene, @FxmlScene(FxmlFile.MIGRATION_CAPABILITY_ERROR) Lazy<Scene> capabilityErrorScene, @FxmlScene(FxmlFile.MIGRATION_IMPOSSIBLE) Lazy<Scene> impossibleScene, ErrorComponent.Builder errorComponent) {
this.window = window;
this.vault = vault;
this.executor = executor;
@@ -88,7 +86,7 @@ public class MigrationRunController implements FxController {
}
public void initialize() {
if (keychain.isPresent()) {
if (keychain.isSupported()) {
loadStoredPassword();
}
migrationButtonDisabled.bind(vault.stateProperty().isNotEqualTo(VaultState.NEEDS_MIGRATION).or(passwordField.textProperty().isEmpty()));
@@ -167,10 +165,10 @@ public class MigrationRunController implements FxController {
}
private void loadStoredPassword() {
assert keychain.isPresent();
assert keychain.isSupported();
char[] storedPw = null;
try {
storedPw = keychain.get().loadPassphrase(vault.getId());
storedPw = keychain.loadPassphrase(vault.getId());
if (storedPw != null) {
passwordField.setPassword(storedPw);
passwordField.selectRange(storedPw.length, storedPw.length);

View File

@@ -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;

View File

@@ -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();
}
}

View File

@@ -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

View File

@@ -1,14 +1,14 @@
package org.cryptomator.ui.unlock;
import dagger.Lazy;
import org.cryptomator.common.keychain.KeychainManager;
import org.cryptomator.common.mountpoint.InvalidMountPointException;
import org.cryptomator.common.vaults.MountPointRequirement;
import org.cryptomator.common.vaults.Vault;
import org.cryptomator.common.vaults.VaultState;
import org.cryptomator.common.vaults.Volume.VolumeException;
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.FxmlFile;
@@ -53,14 +53,14 @@ public class UnlockWorkflow extends Task<Boolean> {
private final AtomicBoolean savePassword;
private final Optional<char[]> savedPassword;
private final UserInteractionLock<PasswordEntry> passwordEntryLock;
private final Optional<KeychainManager> keychain;
private final KeychainManager keychain;
private final Lazy<Scene> unlockScene;
private final Lazy<Scene> successScene;
private final Lazy<Scene> invalidMountPointScene;
private final ErrorComponent.Builder errorComponent;
@Inject
UnlockWorkflow(@UnlockWindow Stage window, @UnlockWindow Vault vault, VaultService vaultService, AtomicReference<char[]> password, @Named("savePassword") AtomicBoolean savePassword, @Named("savedPassword") Optional<char[]> savedPassword, UserInteractionLock<PasswordEntry> passwordEntryLock, Optional<KeychainManager> keychain, @FxmlScene(FxmlFile.UNLOCK) Lazy<Scene> unlockScene, @FxmlScene(FxmlFile.UNLOCK_SUCCESS) Lazy<Scene> successScene, @FxmlScene(FxmlFile.UNLOCK_INVALID_MOUNT_POINT) Lazy<Scene> invalidMountPointScene, ErrorComponent.Builder errorComponent) {
UnlockWorkflow(@UnlockWindow Stage window, @UnlockWindow Vault vault, VaultService vaultService, AtomicReference<char[]> password, @Named("savePassword") AtomicBoolean savePassword, @Named("savedPassword") Optional<char[]> savedPassword, UserInteractionLock<PasswordEntry> passwordEntryLock, KeychainManager keychain, @FxmlScene(FxmlFile.UNLOCK) Lazy<Scene> unlockScene, @FxmlScene(FxmlFile.UNLOCK_SUCCESS) Lazy<Scene> successScene, @FxmlScene(FxmlFile.UNLOCK_INVALID_MOUNT_POINT) Lazy<Scene> invalidMountPointScene, ErrorComponent.Builder errorComponent) {
this.window = window;
this.vault = vault;
this.vaultService = vaultService;
@@ -150,9 +150,9 @@ public class UnlockWorkflow extends Task<Boolean> {
}
private void savePasswordToSystemkeychain() {
if (keychain.isPresent()) {
if (keychain.isSupported()) {
try {
keychain.get().storePassphrase(vault.getId(), CharBuffer.wrap(password.get()));
keychain.storePassphrase(vault.getId(), CharBuffer.wrap(password.get()));
} catch (KeychainAccessException e) {
LOG.error("Failed to store passphrase in system keychain.", e);
}

View File

@@ -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;
}
}

View File

@@ -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)