mirror of
https://github.com/cryptomator/cryptomator.git
synced 2026-04-20 09:36:55 -04:00
Merge branch 'release/1.4.14'
This commit is contained in:
19
README.md
19
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).
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<groupId>org.cryptomator</groupId>
|
||||
<artifactId>main</artifactId>
|
||||
<version>1.4.13</version>
|
||||
<version>1.4.14</version>
|
||||
</parent>
|
||||
<artifactId>buildkit</artifactId>
|
||||
<packaging>pom</packaging>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<groupId>org.cryptomator</groupId>
|
||||
<artifactId>main</artifactId>
|
||||
<version>1.4.13</version>
|
||||
<version>1.4.14</version>
|
||||
</parent>
|
||||
<artifactId>commons</artifactId>
|
||||
<name>Cryptomator Commons</name>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<groupId>org.cryptomator</groupId>
|
||||
<artifactId>main</artifactId>
|
||||
<version>1.4.13</version>
|
||||
<version>1.4.14</version>
|
||||
</parent>
|
||||
<artifactId>keychain</artifactId>
|
||||
<name>System Keychain Access</name>
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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<String> list = keyring.getItems(createAttributes(key));
|
||||
if (list == null) {
|
||||
keyring.createItem("Cryptomator", passphrase, createAttributes(key));
|
||||
}
|
||||
}
|
||||
|
||||
public char[] loadPassword(String key) {
|
||||
List<String> list = keyring.getItems(createAttributes(key));
|
||||
if (list != null) {
|
||||
return keyring.getSecret(list.get(0));
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public void deletePassword(String key) {
|
||||
List<String> list = keyring.getItems(createAttributes(key));
|
||||
if (list != null) {
|
||||
keyring.deleteItem(list.get(0));
|
||||
}
|
||||
}
|
||||
|
||||
private Map<String, String> createAttributes(String key) {
|
||||
Map<String, String> attributes = new HashMap();
|
||||
attributes.put("Vault", key);
|
||||
return attributes;
|
||||
}
|
||||
}
|
||||
@@ -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 <code>null</code> 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;
|
||||
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -31,7 +31,7 @@ public class KeychainModule {
|
||||
|
||||
@Provides
|
||||
@ElementsIntoSet
|
||||
Set<KeychainAccessStrategy> provideKeychainAccessStrategies(MacSystemKeychainAccess macKeychain, WindowsProtectedKeychainAccess winKeychain, LinuxSecretServiceAccess linKeychain) {
|
||||
Set<KeychainAccessStrategy> provideKeychainAccessStrategies(MacSystemKeychainAccess macKeychain, WindowsProtectedKeychainAccess winKeychain, LinuxSecretServiceKeychainAccess linKeychain) {
|
||||
return Sets.newHashSet(macKeychain, winKeychain, linKeychain);
|
||||
}
|
||||
|
||||
|
||||
@@ -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<GnomeKeyringAccess> 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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<GnomeKeyringAccess> 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);
|
||||
}
|
||||
}
|
||||
@@ -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<KeychainAccessStrategy> delegate;
|
||||
|
||||
@Inject
|
||||
public LinuxSecretServiceKeychainAccess() {
|
||||
this.delegate = constructGnomeKeyringKeychainAccess();
|
||||
}
|
||||
|
||||
private static Optional<KeychainAccessStrategy> 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);
|
||||
}
|
||||
}
|
||||
@@ -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<String> 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<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);
|
||||
}
|
||||
}
|
||||
|
||||
private Map<String, String> createAttributes(String key) {
|
||||
Map<String, String> attributes = new HashMap();
|
||||
attributes.put("Vault", key);
|
||||
return attributes;
|
||||
}
|
||||
}
|
||||
@@ -13,7 +13,7 @@ import java.util.Optional;
|
||||
public class KeychainModuleTest {
|
||||
|
||||
@Test
|
||||
public void testGetKeychain() {
|
||||
public void testGetKeychain() throws KeychainAccessException {
|
||||
Optional<KeychainAccess> keychainAccess = DaggerTestKeychainComponent.builder().keychainModule(new TestKeychainModule()).build().keychainAccess();
|
||||
Assertions.assertTrue(keychainAccess.isPresent());
|
||||
Assertions.assertTrue(keychainAccess.get() instanceof MapKeychainAccess);
|
||||
|
||||
@@ -10,7 +10,7 @@ import java.util.Set;
|
||||
public class TestKeychainModule extends KeychainModule {
|
||||
|
||||
@Override
|
||||
Set<KeychainAccessStrategy> provideKeychainAccessStrategies(MacSystemKeychainAccess macKeychain, WindowsProtectedKeychainAccess winKeychain, LinuxSecretServiceAccess linKeychain) {
|
||||
Set<KeychainAccessStrategy> provideKeychainAccessStrategies(MacSystemKeychainAccess macKeychain, WindowsProtectedKeychainAccess winKeychain, LinuxSecretServiceKeychainAccess linKeychain) {
|
||||
return Set.of(new MapKeychainAccess());
|
||||
}
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<groupId>org.cryptomator</groupId>
|
||||
<artifactId>main</artifactId>
|
||||
<version>1.4.13</version>
|
||||
<version>1.4.14</version>
|
||||
</parent>
|
||||
<artifactId>launcher</artifactId>
|
||||
<name>Cryptomator Launcher</name>
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<groupId>org.cryptomator</groupId>
|
||||
<artifactId>main</artifactId>
|
||||
<version>1.4.13</version>
|
||||
<version>1.4.14</version>
|
||||
<packaging>pom</packaging>
|
||||
<name>Cryptomator</name>
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<groupId>org.cryptomator</groupId>
|
||||
<artifactId>main</artifactId>
|
||||
<version>1.4.13</version>
|
||||
<version>1.4.14</version>
|
||||
</parent>
|
||||
<artifactId>ui</artifactId>
|
||||
<name>Cryptomator GUI</name>
|
||||
|
||||
@@ -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<ButtonType> 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;
|
||||
}
|
||||
|
||||
// ****************************************
|
||||
|
||||
@@ -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, ' ');
|
||||
|
||||
Reference in New Issue
Block a user