Merge branch 'release/1.4.14'

This commit is contained in:
Sebastian Stenzel
2019-08-12 16:43:47 +02:00
20 changed files with 190 additions and 160 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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