diff --git a/README.md b/README.md
index 493bfc3f9..417dfb09a 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,4 @@
-
+[](https://cryptomator.org/)
[](https://travis-ci.org/cryptomator/cryptomator)
[](https://snyk.io/test/github/cryptomator/cryptomator?targetFile=main%2Fpom.xml)
@@ -8,7 +8,22 @@
[](https://github.com/cryptomator/cryptomator/releases/latest)
[](https://community.cryptomator.org)
-Multi-platform transparent client-side encryption of your files in the cloud.
+## Supporting Cryptomator
+
+Cryptomator is provided free of charge as an open-source project despite the high development effort and is therefore dependent on donations. If you are also interested in further development, we offer you the opportunity to support us:
+
+- [One-time or recurring donation via Cryptomator's website.](https://cryptomator.org/#donate)
+- [Become a sponsor via Cryptomator's sponsors website.](https://cryptomator.org/sponsors/)
+
+### Silver Sponsors
+
+[](https://thebestvpn.com/)
+
+---
+
+## Introduction
+
+Cryptomator offers multi-platform transparent client-side encryption of your files in the cloud.
Download native binaries of Cryptomator on [cryptomator.org](https://cryptomator.org/) or clone and build Cryptomator using Maven (instructions below).
diff --git a/main/buildkit/pom.xml b/main/buildkit/pom.xml
index c8dd5bb00..9fbf1b50d 100644
--- a/main/buildkit/pom.xml
+++ b/main/buildkit/pom.xml
@@ -4,7 +4,7 @@
org.cryptomator
main
- 1.4.13
+ 1.4.14
buildkit
pom
diff --git a/main/commons/pom.xml b/main/commons/pom.xml
index 5c550f5e7..84179b093 100644
--- a/main/commons/pom.xml
+++ b/main/commons/pom.xml
@@ -4,7 +4,7 @@
org.cryptomator
main
- 1.4.13
+ 1.4.14
commons
Cryptomator Commons
diff --git a/main/keychain/pom.xml b/main/keychain/pom.xml
index 198f9244d..4807248c2 100644
--- a/main/keychain/pom.xml
+++ b/main/keychain/pom.xml
@@ -4,7 +4,7 @@
org.cryptomator
main
- 1.4.13
+ 1.4.14
keychain
System Keychain Access
diff --git a/main/keychain/src/main/java/org/cryptomator/keychain/GnomeKeyringAccess.java b/main/keychain/src/main/java/org/cryptomator/keychain/GnomeKeyringAccess.java
deleted file mode 100644
index 998fdfab7..000000000
--- a/main/keychain/src/main/java/org/cryptomator/keychain/GnomeKeyringAccess.java
+++ /dev/null
@@ -1,9 +0,0 @@
-package org.cryptomator.keychain;
-
-public interface GnomeKeyringAccess {
- public void storePassword(String key, CharSequence passphrase);
-
- public char[] loadPassword(String key);
-
- public void deletePassword(String key);
-}
diff --git a/main/keychain/src/main/java/org/cryptomator/keychain/GnomeKeyringAccessImpl.java b/main/keychain/src/main/java/org/cryptomator/keychain/GnomeKeyringAccessImpl.java
deleted file mode 100644
index 34dcdb5fa..000000000
--- a/main/keychain/src/main/java/org/cryptomator/keychain/GnomeKeyringAccessImpl.java
+++ /dev/null
@@ -1,52 +0,0 @@
-package org.cryptomator.keychain;
-
-import org.freedesktop.secret.simple.SimpleCollection;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import java.io.IOException;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
-public class GnomeKeyringAccessImpl implements GnomeKeyringAccess {
- private static final Logger LOG = LoggerFactory.getLogger(GnomeKeyringAccessImpl.class);
- private SimpleCollection keyring;
-
- public GnomeKeyringAccessImpl() {
- try {
- keyring = new SimpleCollection();
- } catch (IOException e) {
- LOG.error("D-Bus reports a problem.", e);
- }
- }
-
- public void storePassword(String key, CharSequence passphrase) {
- List list = keyring.getItems(createAttributes(key));
- if (list == null) {
- keyring.createItem("Cryptomator", passphrase, createAttributes(key));
- }
- }
-
- public char[] loadPassword(String key) {
- List list = keyring.getItems(createAttributes(key));
- if (list != null) {
- return keyring.getSecret(list.get(0));
- } else {
- return null;
- }
- }
-
- public void deletePassword(String key) {
- List list = keyring.getItems(createAttributes(key));
- if (list != null) {
- keyring.deleteItem(list.get(0));
- }
- }
-
- 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/KeychainAccess.java b/main/keychain/src/main/java/org/cryptomator/keychain/KeychainAccess.java
index bb9f26f8b..6c20ba50f 100644
--- a/main/keychain/src/main/java/org/cryptomator/keychain/KeychainAccess.java
+++ b/main/keychain/src/main/java/org/cryptomator/keychain/KeychainAccess.java
@@ -13,19 +13,19 @@ public interface KeychainAccess {
* @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);
+ 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);
+ 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);
+ void deletePassphrase(String key) throws KeychainAccessException;
}
diff --git a/main/keychain/src/main/java/org/cryptomator/keychain/KeychainAccessException.java b/main/keychain/src/main/java/org/cryptomator/keychain/KeychainAccessException.java
new file mode 100644
index 000000000..430b17b29
--- /dev/null
+++ b/main/keychain/src/main/java/org/cryptomator/keychain/KeychainAccessException.java
@@ -0,0 +1,12 @@
+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/KeychainModule.java b/main/keychain/src/main/java/org/cryptomator/keychain/KeychainModule.java
index f05a90f1f..90b975b7d 100644
--- a/main/keychain/src/main/java/org/cryptomator/keychain/KeychainModule.java
+++ b/main/keychain/src/main/java/org/cryptomator/keychain/KeychainModule.java
@@ -31,7 +31,7 @@ public class KeychainModule {
@Provides
@ElementsIntoSet
- Set provideKeychainAccessStrategies(MacSystemKeychainAccess macKeychain, WindowsProtectedKeychainAccess winKeychain, LinuxSecretServiceAccess linKeychain) {
+ Set provideKeychainAccessStrategies(MacSystemKeychainAccess macKeychain, WindowsProtectedKeychainAccess winKeychain, LinuxSecretServiceKeychainAccess linKeychain) {
return Sets.newHashSet(macKeychain, winKeychain, linKeychain);
}
diff --git a/main/keychain/src/main/java/org/cryptomator/keychain/LinuxKeychainTester.java b/main/keychain/src/main/java/org/cryptomator/keychain/LinuxKeychainTester.java
deleted file mode 100644
index 67d1300c9..000000000
--- a/main/keychain/src/main/java/org/cryptomator/keychain/LinuxKeychainTester.java
+++ /dev/null
@@ -1,28 +0,0 @@
-package org.cryptomator.keychain;
-
-import org.apache.commons.lang3.SystemUtils;
-
-import java.util.Optional;
-
-public class LinuxKeychainTester {
-
- public static boolean secretServiceIsAvailable() {
- try {
- Class.forName("org.freedesktop.secret.simple.SimpleCollection");
- return SystemUtils.IS_OS_LINUX; // even if the classes could be loaded, secretService is only available on linux
- } catch (ClassNotFoundException e) {
- return false;
- }
- }
-
- public static Optional getSecretService() {
- if (!secretServiceIsAvailable()) return Optional.empty();
- try {
- Class clazz = Class.forName("org.cryptomator.keychain.GnomeKeyringAccessImpl");
- GnomeKeyringAccess keyring = (GnomeKeyringAccess) clazz.getDeclaredConstructor().newInstance();
- return Optional.of(keyring);
- } catch (Exception e) {
- return Optional.empty();
- }
- }
-}
diff --git a/main/keychain/src/main/java/org/cryptomator/keychain/LinuxSecretServiceAccess.java b/main/keychain/src/main/java/org/cryptomator/keychain/LinuxSecretServiceAccess.java
deleted file mode 100644
index ef8dcb092..000000000
--- a/main/keychain/src/main/java/org/cryptomator/keychain/LinuxSecretServiceAccess.java
+++ /dev/null
@@ -1,39 +0,0 @@
-package org.cryptomator.keychain;
-
-import com.google.common.base.Preconditions;
-import org.apache.commons.lang3.SystemUtils;
-
-import javax.inject.Inject;
-import java.util.Optional;
-
-public class LinuxSecretServiceAccess implements KeychainAccessStrategy {
- private final Optional gnomeLoginKeyring;
-
- @Inject
- public LinuxSecretServiceAccess() {
- gnomeLoginKeyring = LinuxKeychainTester.getSecretService();
- }
-
- @Override
- public boolean isSupported() {
- return SystemUtils.IS_OS_LINUX && LinuxKeychainTester.getSecretService().isPresent();
- }
-
- @Override
- public void storePassphrase(String key, CharSequence passphrase) {
- Preconditions.checkState(gnomeLoginKeyring.isPresent());
- gnomeLoginKeyring.get().storePassword(key, passphrase);
- }
-
- @Override
- public char[] loadPassphrase(String key) {
- Preconditions.checkState(gnomeLoginKeyring.isPresent());
- return gnomeLoginKeyring.get().loadPassword(key);
- }
-
- @Override
- public void deletePassphrase(String key) {
- Preconditions.checkState(gnomeLoginKeyring.isPresent());
- gnomeLoginKeyring.get().deletePassword(key);
- }
-}
diff --git a/main/keychain/src/main/java/org/cryptomator/keychain/LinuxSecretServiceKeychainAccess.java b/main/keychain/src/main/java/org/cryptomator/keychain/LinuxSecretServiceKeychainAccess.java
new file mode 100644
index 000000000..1387bf0eb
--- /dev/null
+++ b/main/keychain/src/main/java/org/cryptomator/keychain/LinuxSecretServiceKeychainAccess.java
@@ -0,0 +1,51 @@
+package org.cryptomator.keychain;
+
+import org.apache.commons.lang3.SystemUtils;
+
+import javax.inject.Inject;
+import java.util.Optional;
+
+/**
+ * A facade to LinuxSecretServiceKeychainAccessImpl that doesn't depend on libraries that are unavailable on Mac and Windows.
+ */
+public class LinuxSecretServiceKeychainAccess implements KeychainAccessStrategy {
+
+ // the actual implementation is hidden in this delegate object which is loaded via reflection,
+ // as it depends on libraries that aren't necessarily available:
+ private final Optional delegate;
+
+ @Inject
+ public LinuxSecretServiceKeychainAccess() {
+ this.delegate = constructGnomeKeyringKeychainAccess();
+ }
+
+ private static Optional constructGnomeKeyringKeychainAccess() {
+ try {
+ Class> clazz = Class.forName("org.cryptomator.keychain.LinuxSecretServiceKeychainAccessImpl");
+ KeychainAccessStrategy instance = (KeychainAccessStrategy) clazz.getDeclaredConstructor().newInstance();
+ return Optional.of(instance);
+ } catch (Exception e) {
+ return Optional.empty();
+ }
+ }
+
+ @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);
+ }
+}
diff --git a/main/keychain/src/main/java/org/cryptomator/keychain/LinuxSecretServiceKeychainAccessImpl.java b/main/keychain/src/main/java/org/cryptomator/keychain/LinuxSecretServiceKeychainAccessImpl.java
new file mode 100644
index 000000000..542c98c42
--- /dev/null
+++ b/main/keychain/src/main/java/org/cryptomator/keychain/LinuxSecretServiceKeychainAccessImpl.java
@@ -0,0 +1,65 @@
+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 {
+
+ @Override
+ public boolean isSupported() {
+ try (@SuppressWarnings("unused") SimpleCollection keyring = new SimpleCollection()) {
+ // seems like we're able to access the keyring.
+ return true;
+ } catch (IOException 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("Cryptomator", passphrase, createAttributes(key));
+ }
+ } 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);
+ }
+ }
+
+ private Map createAttributes(String key) {
+ Map attributes = new HashMap();
+ attributes.put("Vault", key);
+ return attributes;
+ }
+}
diff --git a/main/keychain/src/test/java/org/cryptomator/keychain/KeychainModuleTest.java b/main/keychain/src/test/java/org/cryptomator/keychain/KeychainModuleTest.java
index c177060a3..7b3c59899 100644
--- a/main/keychain/src/test/java/org/cryptomator/keychain/KeychainModuleTest.java
+++ b/main/keychain/src/test/java/org/cryptomator/keychain/KeychainModuleTest.java
@@ -13,7 +13,7 @@ import java.util.Optional;
public class KeychainModuleTest {
@Test
- public void testGetKeychain() {
+ public void testGetKeychain() throws KeychainAccessException {
Optional keychainAccess = DaggerTestKeychainComponent.builder().keychainModule(new TestKeychainModule()).build().keychainAccess();
Assertions.assertTrue(keychainAccess.isPresent());
Assertions.assertTrue(keychainAccess.get() instanceof MapKeychainAccess);
diff --git a/main/keychain/src/test/java/org/cryptomator/keychain/TestKeychainModule.java b/main/keychain/src/test/java/org/cryptomator/keychain/TestKeychainModule.java
index 80c4e6aa3..d6e183a22 100644
--- a/main/keychain/src/test/java/org/cryptomator/keychain/TestKeychainModule.java
+++ b/main/keychain/src/test/java/org/cryptomator/keychain/TestKeychainModule.java
@@ -10,7 +10,7 @@ import java.util.Set;
public class TestKeychainModule extends KeychainModule {
@Override
- Set provideKeychainAccessStrategies(MacSystemKeychainAccess macKeychain, WindowsProtectedKeychainAccess winKeychain, LinuxSecretServiceAccess linKeychain) {
+ Set provideKeychainAccessStrategies(MacSystemKeychainAccess macKeychain, WindowsProtectedKeychainAccess winKeychain, LinuxSecretServiceKeychainAccess linKeychain) {
return Set.of(new MapKeychainAccess());
}
diff --git a/main/launcher/pom.xml b/main/launcher/pom.xml
index 98a8f1f1b..e08c39769 100644
--- a/main/launcher/pom.xml
+++ b/main/launcher/pom.xml
@@ -4,7 +4,7 @@
org.cryptomator
main
- 1.4.13
+ 1.4.14
launcher
Cryptomator Launcher
diff --git a/main/pom.xml b/main/pom.xml
index 41492efbf..a077719f1 100644
--- a/main/pom.xml
+++ b/main/pom.xml
@@ -3,7 +3,7 @@
4.0.0
org.cryptomator
main
- 1.4.13
+ 1.4.14
pom
Cryptomator
diff --git a/main/ui/pom.xml b/main/ui/pom.xml
index 6861bcd2f..1e70c802a 100644
--- a/main/ui/pom.xml
+++ b/main/ui/pom.xml
@@ -4,7 +4,7 @@
org.cryptomator
main
- 1.4.13
+ 1.4.14
ui
Cryptomator GUI
diff --git a/main/ui/src/main/java/org/cryptomator/ui/controllers/UnlockController.java b/main/ui/src/main/java/org/cryptomator/ui/controllers/UnlockController.java
index 404bb00e1..25b37edff 100644
--- a/main/ui/src/main/java/org/cryptomator/ui/controllers/UnlockController.java
+++ b/main/ui/src/main/java/org/cryptomator/ui/controllers/UnlockController.java
@@ -40,6 +40,7 @@ import org.cryptomator.common.settings.VolumeImpl;
import org.cryptomator.cryptolib.api.InvalidPassphraseException;
import org.cryptomator.cryptolib.api.UnsupportedVaultFormatException;
import org.cryptomator.keychain.KeychainAccess;
+import org.cryptomator.keychain.KeychainAccessException;
import org.cryptomator.ui.controls.SecPasswordField;
import org.cryptomator.ui.l10n.Localization;
import org.cryptomator.ui.model.Vault;
@@ -215,12 +216,16 @@ public class UnlockController implements ViewController {
savePassword.setSelected(false);
// auto-fill pw from keychain:
if (keychainAccess.isPresent()) {
- char[] storedPw = keychainAccess.get().loadPassphrase(vault.getId());
- if (storedPw != null) {
- savePassword.setSelected(true);
- passwordField.setPassword(storedPw);
- passwordField.selectRange(storedPw.length, storedPw.length);
- Arrays.fill(storedPw, ' ');
+ try {
+ char[] storedPw = keychainAccess.get().loadPassphrase(vault.getId());
+ if (storedPw != null) {
+ savePassword.setSelected(true);
+ passwordField.setPassword(storedPw);
+ passwordField.selectRange(storedPw.length, storedPw.length);
+ Arrays.fill(storedPw, ' ');
+ }
+ } catch (KeychainAccessException e) {
+ LOG.error("Failed to load stored password from system keychain.", e);
}
}
VaultSettings vaultSettings = vault.getVaultSettings();
@@ -450,7 +455,11 @@ public class UnlockController implements ViewController {
Optional choice = confirmDialog.showAndWait();
if (ButtonType.OK.equals(choice.get())) {
- keychainAccess.get().deletePassphrase(vault.getId());
+ try {
+ keychainAccess.get().deletePassphrase(vault.getId());
+ } catch (KeychainAccessException e) {
+ LOG.error("Failed to remove entry from system keychain.", e);
+ }
} else if (ButtonType.CANCEL.equals(choice.get())) {
savePassword.setSelected(true);
}
@@ -458,12 +467,16 @@ public class UnlockController implements ViewController {
}
private boolean hasStoredPassword() {
- char[] storedPw = keychainAccess.get().loadPassphrase(vault.getId());
- boolean hasPw = (storedPw != null);
- if (storedPw != null) {
- Arrays.fill(storedPw, ' ');
+ try {
+ char[] storedPw = keychainAccess.get().loadPassphrase(vault.getId());
+ boolean hasPw = (storedPw != null);
+ if (storedPw != null) {
+ Arrays.fill(storedPw, ' ');
+ }
+ return hasPw;
+ } catch (KeychainAccessException e) {
+ return false;
}
- return hasPw;
}
// ****************************************
diff --git a/main/ui/src/main/java/org/cryptomator/ui/model/AutoUnlocker.java b/main/ui/src/main/java/org/cryptomator/ui/model/AutoUnlocker.java
index b2909cb84..119e683c0 100644
--- a/main/ui/src/main/java/org/cryptomator/ui/model/AutoUnlocker.java
+++ b/main/ui/src/main/java/org/cryptomator/ui/model/AutoUnlocker.java
@@ -8,6 +8,7 @@ package org.cryptomator.ui.model;
import org.cryptomator.common.FxApplicationScoped;
import org.cryptomator.cryptolib.api.CryptoException;
import org.cryptomator.keychain.KeychainAccess;
+import org.cryptomator.keychain.KeychainAccessException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -65,15 +66,16 @@ public class AutoUnlocker {
}
private void unlockSilently(Vault vault) {
- char[] storedPw = keychainAccess.get().loadPassphrase(vault.getId());
- if (storedPw == null) {
- LOG.warn("No passphrase stored in keychain for vault registered for auto unlocking: {}", vault.getPath());
- return;
- }
+ char[] storedPw = new char[0];
try {
+ storedPw = keychainAccess.get().loadPassphrase(vault.getId());
+ if (storedPw == null) {
+ LOG.warn("No passphrase stored in keychain for vault registered for auto unlocking: {}", vault.getPath());
+ return;
+ }
vault.unlock(CharBuffer.wrap(storedPw));
revealSilently(vault);
- } catch (IOException | CryptoException | Volume.VolumeException e) {
+ } catch (IOException | CryptoException | Volume.VolumeException | KeychainAccessException e) {
LOG.error("Auto unlock failed.", e);
} finally {
Arrays.fill(storedPw, ' ');