diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 8009aeb06..b847d0b4b 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -7,7 +7,6 @@ jobs:
build:
name: Build and Test
runs-on: ubuntu-latest
- #This check is case insensitive
if: "!contains(github.event.head_commit.message, '[ci skip]') && !contains(github.event.head_commit.message, '[skip ci]')"
steps:
- uses: actions/checkout@v2
@@ -29,28 +28,31 @@ jobs:
run: |
curl -o ~/codacy-coverage-reporter.jar https://repo.maven.apache.org/maven2/com/codacy/codacy-coverage-reporter/7.1.0/codacy-coverage-reporter-7.1.0-assembly.jar
$JAVA_HOME/bin/java -jar ~/codacy-coverage-reporter.jar report -l Java -r main/commons/target/site/jacoco/jacoco.xml --partial
- $JAVA_HOME/bin/java -jar ~/codacy-coverage-reporter.jar report -l Java -r main/keychain/target/site/jacoco/jacoco.xml --partial
$JAVA_HOME/bin/java -jar ~/codacy-coverage-reporter.jar report -l Java -r main/ui/target/site/jacoco/jacoco.xml --partial
$JAVA_HOME/bin/java -jar ~/codacy-coverage-reporter.jar report -l Java -r main/launcher/target/site/jacoco/jacoco.xml --partial
$JAVA_HOME/bin/java -jar ~/codacy-coverage-reporter.jar final
env:
CODACY_PROJECT_TOKEN: ${{ secrets.CODACY_PROJECT_TOKEN }}
- - name: Assemble Buildkit
- run: mvn -B package -DskipTests --file main/pom.xml --resume-from=buildkit -Prelease
+ - name: Assemble buildkit-linux.zip
+ run: mvn -B clean package -DskipTests --file main/pom.xml --resume-from=buildkit -Prelease,linux
- name: Upload buildkit-linux.zip
uses: actions/upload-artifact@v1
with:
- name: buildkit-linux.zip
+ name: buildkit-linux
path: main/buildkit/target/buildkit-linux.zip
+ - name: Assemble buildkit-mac.zip
+ run: mvn -B clean package -DskipTests --file main/pom.xml --resume-from=buildkit -Prelease,mac
- name: Upload buildkit-mac.zip
uses: actions/upload-artifact@v1
with:
- name: buildkit-mac.zip
+ name: buildkit-mac
path: main/buildkit/target/buildkit-mac.zip
+ - name: Assemble buildkit-win.zip
+ run: mvn -B clean package -DskipTests --file main/pom.xml --resume-from=buildkit -Prelease,windows
- name: Upload buildkit-win.zip
uses: actions/upload-artifact@v1
with:
- name: buildkit-win.zip
+ name: buildkit-win
path: main/buildkit/target/buildkit-win.zip
release:
diff --git a/README.md b/README.md
index 091b6af8c..e07ab285f 100644
--- a/README.md
+++ b/README.md
@@ -73,7 +73,9 @@ For more information on the security details visit [cryptomator.org](https://doc
```
cd main
-mvn clean install -Prelease
+mvn clean install -Prelease,windows
+# or mvn clean install -Prelease,mac
+# or mvn clean install -Prelease,linux
```
This will build all the jars and bundle them together with their OS-specific dependencies under `main/buildkit/target`. This can now be used to build native packages.
diff --git a/main/buildkit/assembly-linux.xml b/main/buildkit/assembly-linux.xml
index 5d8bfc785..1e16da9e2 100644
--- a/main/buildkit/assembly-linux.xml
+++ b/main/buildkit/assembly-linux.xml
@@ -14,13 +14,6 @@
libs
-
- target/
-
- ffi-version.txt
-
- libs
-
target/
@@ -43,12 +36,5 @@
libs
-
- target/linux-libs
-
- *.jar
-
- libs
-
\ No newline at end of file
diff --git a/main/buildkit/assembly-mac.xml b/main/buildkit/assembly-mac.xml
index c9057dcdd..4a01f4067 100644
--- a/main/buildkit/assembly-mac.xml
+++ b/main/buildkit/assembly-mac.xml
@@ -14,13 +14,6 @@
libs
-
- target/
-
- ffi-version.txt
-
- libs
-
target/
@@ -43,12 +36,5 @@
libs
-
- target/mac-libs
-
- *.jar
-
- libs
-
\ No newline at end of file
diff --git a/main/buildkit/assembly-win.xml b/main/buildkit/assembly-win.xml
index 2e81782b3..0297f3ec4 100644
--- a/main/buildkit/assembly-win.xml
+++ b/main/buildkit/assembly-win.xml
@@ -14,13 +14,6 @@
libs
-
- target/
-
- ffi-version.txt
-
- libs
-
target/
@@ -43,12 +36,5 @@
libs
-
- target/win-libs
-
- *.jar
-
- libs
-
\ No newline at end of file
diff --git a/main/buildkit/pom.xml b/main/buildkit/pom.xml
index 23059eb4a..4dde902b9 100644
--- a/main/buildkit/pom.xml
+++ b/main/buildkit/pom.xml
@@ -24,7 +24,6 @@
org.apache.maven.plugins
maven-resources-plugin
- 3.1.0
copy-resources
@@ -55,8 +54,8 @@
+ org.apache.maven.plugins
maven-dependency-plugin
- 3.1.1
copy-libs
@@ -65,110 +64,153 @@
copy-dependencies
+ runtime
${project.build.directory}/libs
linux,mac,win
- dbus-java,secret-service,kdewallet,hkdf,java-utils
-
-
-
- copy-linux-libs
- prepare-package
-
- copy-dependencies
-
-
- ${project.build.directory}/linux-libs
- org.openjfx
- linux
-
-
-
- copy-linux-system-keychain-access
- prepare-package
-
- copy-dependencies
-
-
- ${project.build.directory}/linux-libs
- dbus-java,secret-service,kdewallet,hkdf,java-utils
-
-
-
- copy-mac-libs
- prepare-package
-
- copy-dependencies
-
-
- ${project.build.directory}/mac-libs
- org.openjfx
- mac
-
-
-
- copy-win-libs
- prepare-package
-
- copy-dependencies
-
-
- ${project.build.directory}/win-libs
- org.openjfx
- win
-
-
-
-
-
-
-
- maven-assembly-plugin
- 3.1.1
-
-
- assemble-linux
- package
-
- single
-
-
-
- assembly-linux.xml
-
- false
- buildkit-linux
-
-
-
- assemble-mac
- package
-
- single
-
-
-
- assembly-mac.xml
-
- false
- buildkit-mac
-
-
-
- assemble-win
- package
-
- single
-
-
-
- assembly-win.xml
-
- false
- buildkit-win
+
+
+
+ linux
+
+
+
+ org.apache.maven.plugins
+ maven-assembly-plugin
+
+
+ assemble-linux
+ package
+
+ single
+
+
+
+ assembly-linux.xml
+
+ false
+ buildkit-linux
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-dependency-plugin
+
+
+ copy-linux-libs
+ prepare-package
+
+ copy-dependencies
+
+
+ ${project.build.directory}/libs
+ org.openjfx
+ linux
+
+
+
+
+
+
+
+
+
+ mac
+
+
+
+ org.apache.maven.plugins
+ maven-assembly-plugin
+
+
+ assemble-mac
+ package
+
+ single
+
+
+
+ assembly-mac.xml
+
+ false
+ buildkit-mac
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-dependency-plugin
+
+
+ copy-mac-libs
+ prepare-package
+
+ copy-dependencies
+
+
+ ${project.build.directory}/libs
+ org.openjfx
+ mac
+
+
+
+
+
+
+
+
+
+ windows
+
+
+
+ org.apache.maven.plugins
+ maven-assembly-plugin
+
+
+ assemble-win
+ package
+
+ single
+
+
+
+ assembly-win.xml
+
+ false
+ buildkit-win
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-dependency-plugin
+
+
+ copy-win-libs
+ prepare-package
+
+ copy-dependencies
+
+
+ ${project.build.directory}/libs
+ org.openjfx
+ win
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/main/buildkit/src/main/resources/ffi-version.txt b/main/buildkit/src/main/resources/ffi-version.txt
deleted file mode 100644
index 1853c270c..000000000
--- a/main/buildkit/src/main/resources/ffi-version.txt
+++ /dev/null
@@ -1 +0,0 @@
-${cryptomator.jni.version}
\ No newline at end of file
diff --git a/main/commons/pom.xml b/main/commons/pom.xml
index 1927a4889..28a42ec07 100644
--- a/main/commons/pom.xml
+++ b/main/commons/pom.xml
@@ -29,7 +29,7 @@
org.cryptomator
- jni
+ integrations-api
diff --git a/main/commons/src/main/java/org/cryptomator/common/CommonsModule.java b/main/commons/src/main/java/org/cryptomator/common/CommonsModule.java
index 5ace6a3e2..ed278e1ab 100644
--- a/main/commons/src/main/java/org/cryptomator/common/CommonsModule.java
+++ b/main/commons/src/main/java/org/cryptomator/common/CommonsModule.java
@@ -9,6 +9,7 @@ import com.tobiasdiez.easybind.EasyBind;
import dagger.Module;
import dagger.Provides;
import org.apache.commons.lang3.SystemUtils;
+import org.cryptomator.common.keychain.KeychainModule;
import org.cryptomator.common.settings.Settings;
import org.cryptomator.common.settings.SettingsProvider;
import org.cryptomator.common.vaults.Vault;
@@ -33,7 +34,7 @@ import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
-@Module(subcomponents = {VaultComponent.class})
+@Module(subcomponents = {VaultComponent.class}, includes = {KeychainModule.class})
public abstract class CommonsModule {
private static final Logger LOG = LoggerFactory.getLogger(CommonsModule.class);
diff --git a/main/commons/src/main/java/org/cryptomator/common/JniModule.java b/main/commons/src/main/java/org/cryptomator/common/JniModule.java
deleted file mode 100644
index 180736710..000000000
--- a/main/commons/src/main/java/org/cryptomator/common/JniModule.java
+++ /dev/null
@@ -1,32 +0,0 @@
-/*******************************************************************************
- * Copyright (c) 2019 Skymatic GmbH.
- * All rights reserved. This program and the accompanying materials
- * are made available under the terms of the accompanying LICENSE file.
- *******************************************************************************/
-package org.cryptomator.common;
-
-import dagger.Module;
-import dagger.Provides;
-import org.cryptomator.jni.JniFunctions;
-import org.cryptomator.jni.MacFunctions;
-import org.cryptomator.jni.WinFunctions;
-
-import javax.inject.Singleton;
-import java.util.Optional;
-
-@Module
-public class JniModule {
-
- @Provides
- @Singleton
- Optional provideOptionalMacFunctions() {
- return JniFunctions.macFunctions();
- }
-
- @Provides
- @Singleton
- Optional provideOptionalWinFunctions() {
- return JniFunctions.winFunctions();
- }
-
-}
diff --git a/main/keychain/src/main/java/org/cryptomator/keychain/KeychainManager.java b/main/commons/src/main/java/org/cryptomator/common/keychain/KeychainManager.java
similarity index 70%
rename from main/keychain/src/main/java/org/cryptomator/keychain/KeychainManager.java
rename to main/commons/src/main/java/org/cryptomator/common/keychain/KeychainManager.java
index 515195232..537b83577 100644
--- a/main/keychain/src/main/java/org/cryptomator/keychain/KeychainManager.java
+++ b/main/commons/src/main/java/org/cryptomator/common/keychain/KeychainManager.java
@@ -1,60 +1,71 @@
-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.slf4j.Logger;
-import org.slf4j.LoggerFactory;
+import org.cryptomator.integrations.keychain.KeychainAccessException;
+import org.cryptomator.integrations.keychain.KeychainAccessProvider;
+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 ObjectExpression keychain;
+ private final LoadingCache passphraseStoredProperties;
- private final KeychainAccessStrategy keychain;
- private LoadingCache passphraseStoredProperties;
-
- KeychainManager(KeychainAccessStrategy keychain) {
- assert keychain.isSupported();
- this.keychain = keychain;
+ @Inject
+ KeychainManager(ObjectExpression 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 +80,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) {
@@ -84,7 +95,6 @@ public class KeychainManager implements KeychainAccessStrategy {
if (Platform.isFxApplicationThread()) {
property.set(value);
} else {
- LOG.warn("");
Platform.runLater(() -> property.set(value));
}
}
@@ -107,7 +117,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);
diff --git a/main/commons/src/main/java/org/cryptomator/common/keychain/KeychainModule.java b/main/commons/src/main/java/org/cryptomator/common/keychain/KeychainModule.java
new file mode 100644
index 000000000..9ac343d36
--- /dev/null
+++ b/main/commons/src/main/java/org/cryptomator/common/keychain/KeychainModule.java
@@ -0,0 +1,44 @@
+package org.cryptomator.common.keychain;
+
+import dagger.Module;
+import dagger.Provides;
+import org.cryptomator.common.settings.Settings;
+import org.cryptomator.integrations.keychain.KeychainAccessProvider;
+
+import javax.inject.Singleton;
+import javafx.beans.binding.Bindings;
+import javafx.beans.binding.ObjectExpression;
+import java.util.ServiceLoader;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+@Module
+public class KeychainModule {
+
+ @Provides
+ @Singleton
+ static Set> provideAvailableKeychainAccessProviderFactories() {
+ return ServiceLoader.load(KeychainAccessProvider.class).stream().collect(Collectors.toUnmodifiableSet());
+ }
+
+ @Provides
+ @Singleton
+ static Set provideSupportedKeychainAccessProviders(Set> availableFactories) {
+ return availableFactories.stream() //
+ .map(ServiceLoader.Provider::get) //
+ .filter(KeychainAccessProvider::isSupported) //
+ .collect(Collectors.toUnmodifiableSet());
+ }
+
+ @Provides
+ @Singleton
+ static ObjectExpression provideKeychainAccessProvider(Settings settings, Set providers) {
+ return Bindings.createObjectBinding(() -> {
+ var selectedProviderClass = settings.keychainBackend().get().getProviderClass();
+ var selectedProvider = providers.stream().filter(provider -> provider.getClass().getName().equals(selectedProviderClass)).findAny();
+ var fallbackProvider = providers.stream().findAny().orElse(null);
+ return selectedProvider.orElse(fallbackProvider);
+ }, settings.keychainBackend());
+ }
+
+}
diff --git a/main/commons/src/main/java/org/cryptomator/common/keychain/NoKeychainAccessProviderException.java b/main/commons/src/main/java/org/cryptomator/common/keychain/NoKeychainAccessProviderException.java
new file mode 100644
index 000000000..c14b81076
--- /dev/null
+++ b/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.");
+ }
+}
diff --git a/main/commons/src/main/java/org/cryptomator/common/settings/KeychainBackend.java b/main/commons/src/main/java/org/cryptomator/common/settings/KeychainBackend.java
index a54c81b08..65f869a12 100644
--- a/main/commons/src/main/java/org/cryptomator/common/settings/KeychainBackend.java
+++ b/main/commons/src/main/java/org/cryptomator/common/settings/KeychainBackend.java
@@ -1,39 +1,19 @@
package org.cryptomator.common.settings;
-import org.apache.commons.lang3.SystemUtils;
-
-import java.util.Arrays;
-
public enum KeychainBackend {
- GNOME("preferences.general.keychainBackend.gnome", SystemUtils.IS_OS_LINUX), //
- KDE("preferences.general.keychainBackend.kde", SystemUtils.IS_OS_LINUX), //
- MAC_SYSTEM_KEYCHAIN("preferences.general.keychainBackend.macSystemKeychain", SystemUtils.IS_OS_MAC), //
- WIN_SYSTEM_KEYCHAIN("preferences.general.keychainBackend.winSystemKeychain", SystemUtils.IS_OS_WINDOWS);
+ GNOME("org.cryptomator.linux.keychain.SecretServiceKeychainAccess"),
+ KDE("org.cryptomator.linux.keychain.KDEWalletKeychainAccess"),
+ MAC_SYSTEM_KEYCHAIN("org.cryptomator.macos.keychain.MacSystemKeychainAccess"),
+ WIN_SYSTEM_KEYCHAIN("org.cryptomator.windows.keychain.WindowsProtectedKeychainAccess");
- public static KeychainBackend[] supportedBackends() {
- return Arrays.stream(values()).filter(KeychainBackend::isSupported).toArray(KeychainBackend[]::new);
+ private final String providerClass;
+
+ KeychainBackend(String providerClass) {
+ this.providerClass = providerClass;
}
- public static KeychainBackend defaultBackend() {
- if (SystemUtils.IS_OS_LINUX) {
- return KeychainBackend.GNOME;
- } else { // SystemUtils.IS_OS_MAC || SystemUtils.IS_OS_WINDOWS
- return Arrays.stream(KeychainBackend.supportedBackends()).findFirst().orElseThrow(IllegalStateException::new);
- }
+ public String getProviderClass() {
+ return providerClass;
}
- private final String configName;
- private final boolean isSupported;
-
- KeychainBackend(String configName, boolean isSupported) {
- this.configName = configName;
- this.isSupported = isSupported;
- }
-
- public String getDisplayName() {
- return configName;
- }
-
- public boolean isSupported() { return isSupported; }
-
}
diff --git a/main/commons/src/main/java/org/cryptomator/common/settings/Settings.java b/main/commons/src/main/java/org/cryptomator/common/settings/Settings.java
index 22b42628d..e50391d2d 100644
--- a/main/commons/src/main/java/org/cryptomator/common/settings/Settings.java
+++ b/main/commons/src/main/java/org/cryptomator/common/settings/Settings.java
@@ -8,6 +8,8 @@
******************************************************************************/
package org.cryptomator.common.settings;
+import org.apache.commons.lang3.SystemUtils;
+
import javafx.beans.Observable;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.IntegerProperty;
@@ -33,9 +35,9 @@ public class Settings {
public static final int DEFAULT_NUM_TRAY_NOTIFICATIONS = 3;
public static final WebDavUrlScheme DEFAULT_GVFS_SCHEME = WebDavUrlScheme.DAV;
public static final boolean DEFAULT_DEBUG_MODE = false;
- public static final VolumeImpl DEFAULT_PREFERRED_VOLUME_IMPL = System.getProperty("os.name").toLowerCase().contains("windows") ? VolumeImpl.DOKANY : VolumeImpl.FUSE;
+ public static final VolumeImpl DEFAULT_PREFERRED_VOLUME_IMPL = SystemUtils.IS_OS_WINDOWS ? VolumeImpl.DOKANY : VolumeImpl.FUSE;
public static final UiTheme DEFAULT_THEME = UiTheme.LIGHT;
- public static final KeychainBackend DEFAULT_KEYCHAIN_BACKEND = KeychainBackend.defaultBackend();
+ public static final KeychainBackend DEFAULT_KEYCHAIN_BACKEND = SystemUtils.IS_OS_WINDOWS ? KeychainBackend.WIN_SYSTEM_KEYCHAIN : SystemUtils.IS_OS_MAC ? KeychainBackend.MAC_SYSTEM_KEYCHAIN : KeychainBackend.GNOME;
public static final NodeOrientation DEFAULT_USER_INTERFACE_ORIENTATION = NodeOrientation.LEFT_TO_RIGHT;
private static final String DEFAULT_LICENSE_KEY = "";
diff --git a/main/keychain/src/test/java/org/cryptomator/keychain/KeychainManagerTest.java b/main/commons/src/test/java/org/cryptomator/common/keychain/KeychainManagerTest.java
similarity index 76%
rename from main/keychain/src/test/java/org/cryptomator/keychain/KeychainManagerTest.java
rename to main/commons/src/test/java/org/cryptomator/common/keychain/KeychainManagerTest.java
index 8577b0977..e82e67e2d 100644
--- a/main/keychain/src/test/java/org/cryptomator/keychain/KeychainManagerTest.java
+++ b/main/commons/src/test/java/org/cryptomator/common/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());
diff --git a/main/keychain/src/test/java/org/cryptomator/keychain/MapKeychainAccess.java b/main/commons/src/test/java/org/cryptomator/common/keychain/MapKeychainAccess.java
similarity index 86%
rename from main/keychain/src/test/java/org/cryptomator/keychain/MapKeychainAccess.java
rename to main/commons/src/test/java/org/cryptomator/common/keychain/MapKeychainAccess.java
index 26b301377..c571ad716 100644
--- a/main/keychain/src/test/java/org/cryptomator/keychain/MapKeychainAccess.java
+++ b/main/commons/src/test/java/org/cryptomator/common/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 map = new HashMap<>();
diff --git a/main/keychain/pom.xml b/main/keychain/pom.xml
deleted file mode 100644
index c70d7cacb..000000000
--- a/main/keychain/pom.xml
+++ /dev/null
@@ -1,69 +0,0 @@
-
-
- 4.0.0
-
- org.cryptomator
- main
- 1.6.0-SNAPSHOT
-
- keychain
- System Keychain Access
-
-
-
- org.cryptomator
- commons
-
-
-
-
- org.openjfx
- javafx-base
-
-
- org.openjfx
- javafx-graphics
-
-
-
-
- org.apache.commons
- commons-lang3
-
-
-
-
- com.google.code.gson
- gson
-
-
- com.google.guava
- guava
-
-
-
-
- com.google.dagger
- dagger
-
-
-
-
- de.swiesend
- secret-service
-
-
-
-
- org.purejava
- kdewallet
-
-
-
-
- org.slf4j
- slf4j-simple
- test
-
-
-
\ No newline at end of file
diff --git a/main/keychain/src/main/java/org/cryptomator/keychain/KeychainAccessException.java b/main/keychain/src/main/java/org/cryptomator/keychain/KeychainAccessException.java
deleted file mode 100644
index b4f0cf5c8..000000000
--- a/main/keychain/src/main/java/org/cryptomator/keychain/KeychainAccessException.java
+++ /dev/null
@@ -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);
- }
-
-}
diff --git a/main/keychain/src/main/java/org/cryptomator/keychain/KeychainAccessStrategy.java b/main/keychain/src/main/java/org/cryptomator/keychain/KeychainAccessStrategy.java
deleted file mode 100644
index 553ce4eab..000000000
--- a/main/keychain/src/main/java/org/cryptomator/keychain/KeychainAccessStrategy.java
+++ /dev/null
@@ -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 null 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 true if this KeychainAccessStrategy works on the current machine.
- * @implNote This method must not throw any exceptions and should fail fast
- * returning false if it can't determine availability of the checked strategy
- */
- boolean isSupported();
-
-}
diff --git a/main/keychain/src/main/java/org/cryptomator/keychain/KeychainModule.java b/main/keychain/src/main/java/org/cryptomator/keychain/KeychainModule.java
deleted file mode 100644
index 791eb1dd0..000000000
--- a/main/keychain/src/main/java/org/cryptomator/keychain/KeychainModule.java
+++ /dev/null
@@ -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 provideSupportedKeychain(Set keychainAccessStrategies) {
- return keychainAccessStrategies.stream().filter(KeychainAccessStrategy::isSupported).findFirst();
- }
-
- @Provides
- @Singleton
- public static Optional provideKeychainManager(Optional keychainAccess) {
- return keychainAccess.map(KeychainManager::new);
- }
-
-}
diff --git a/main/keychain/src/main/java/org/cryptomator/keychain/LinuxKDEWalletKeychainAccessImpl.java b/main/keychain/src/main/java/org/cryptomator/keychain/LinuxKDEWalletKeychainAccessImpl.java
deleted file mode 100644
index 1f74bd18d..000000000
--- a/main/keychain/src/main/java/org/cryptomator/keychain/LinuxKDEWalletKeychainAccessImpl.java
+++ /dev/null
@@ -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);
- }
- }
-}
diff --git a/main/keychain/src/main/java/org/cryptomator/keychain/LinuxSecretServiceKeychainAccessImpl.java b/main/keychain/src/main/java/org/cryptomator/keychain/LinuxSecretServiceKeychainAccessImpl.java
deleted file mode 100644
index e422fff2e..000000000
--- a/main/keychain/src/main/java/org/cryptomator/keychain/LinuxSecretServiceKeychainAccessImpl.java
+++ /dev/null
@@ -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 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 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 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 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 createAttributes(String key) {
- Map attributes = new HashMap();
- attributes.put("Vault", key);
- return attributes;
- }
-}
diff --git a/main/keychain/src/main/java/org/cryptomator/keychain/LinuxSystemKeychainAccess.java b/main/keychain/src/main/java/org/cryptomator/keychain/LinuxSystemKeychainAccess.java
deleted file mode 100644
index f49ea5d1d..000000000
--- a/main/keychain/src/main/java/org/cryptomator/keychain/LinuxSystemKeychainAccess.java
+++ /dev/null
@@ -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 delegate;
- private final Settings settings;
- private static EnumSet 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 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 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 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);
- }
-}
diff --git a/main/keychain/src/main/java/org/cryptomator/keychain/MacSystemKeychainAccess.java b/main/keychain/src/main/java/org/cryptomator/keychain/MacSystemKeychainAccess.java
deleted file mode 100644
index 39a6cd756..000000000
--- a/main/keychain/src/main/java/org/cryptomator/keychain/MacSystemKeychainAccess.java
+++ /dev/null
@@ -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;
-
- @Inject
- public MacSystemKeychainAccess(Optional 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);
- }
- }
-
-}
diff --git a/main/keychain/src/main/java/org/cryptomator/keychain/WindowsProtectedKeychainAccess.java b/main/keychain/src/main/java/org/cryptomator/keychain/WindowsProtectedKeychainAccess.java
deleted file mode 100644
index 6668104a4..000000000
--- a/main/keychain/src/main/java/org/cryptomator/keychain/WindowsProtectedKeychainAccess.java
+++ /dev/null
@@ -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;
- private final List keychainPaths;
- private Map keychainEntries;
-
- @Inject
- public WindowsProtectedKeychainAccess(Optional 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