diff --git a/README.md b/README.md index 493bfc3f9..417dfb09a 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -![cryptomator](cryptomator.png) +[![cryptomator](cryptomator.png)](https://cryptomator.org/) [![Build Status](https://travis-ci.org/cryptomator/cryptomator.svg?branch=master)](https://travis-ci.org/cryptomator/cryptomator) [![Known Vulnerabilities](https://snyk.io/test/github/cryptomator/cryptomator/badge.svg?targetFile=main%2Fpom.xml)](https://snyk.io/test/github/cryptomator/cryptomator?targetFile=main%2Fpom.xml) @@ -8,7 +8,22 @@ [![Latest Release](https://img.shields.io/github/release/cryptomator/cryptomator.svg)](https://github.com/cryptomator/cryptomator/releases/latest) [![Community](https://img.shields.io/badge/help-Community-orange.svg)](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 + +[![TheBestVPN](https://cryptomator.org/img/sponsors/thebestvpn.png)](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, ' ');