Skip to content

Commit

Permalink
Merge branch 'release/1.5.5'
Browse files Browse the repository at this point in the history
  • Loading branch information
overheadhunter committed May 27, 2020
2 parents aef33dc + 11e3ee8 commit 570286c
Show file tree
Hide file tree
Showing 98 changed files with 868 additions and 305 deletions.
2 changes: 1 addition & 1 deletion main/buildkit/pom.xml
Expand Up @@ -4,7 +4,7 @@
<parent>
<groupId>org.cryptomator</groupId>
<artifactId>main</artifactId>
<version>1.5.4</version>
<version>1.5.5</version>
</parent>
<artifactId>buildkit</artifactId>
<packaging>pom</packaging>
Expand Down
2 changes: 1 addition & 1 deletion main/commons/pom.xml
Expand Up @@ -4,7 +4,7 @@
<parent>
<groupId>org.cryptomator</groupId>
<artifactId>main</artifactId>
<version>1.5.4</version>
<version>1.5.5</version>
</parent>
<artifactId>commons</artifactId>
<name>Cryptomator Commons</name>
Expand Down
17 changes: 14 additions & 3 deletions main/keychain/pom.xml
Expand Up @@ -4,7 +4,7 @@
<parent>
<groupId>org.cryptomator</groupId>
<artifactId>main</artifactId>
<version>1.5.4</version>
<version>1.5.5</version>
</parent>
<artifactId>keychain</artifactId>
<name>System Keychain Access</name>
Expand All @@ -15,16 +15,27 @@
<artifactId>commons</artifactId>
</dependency>

<!-- JavaFx -->
<dependency>
<groupId>org.openjfx</groupId>
<artifactId>javafx-base</artifactId>
</dependency>
<dependency>
<groupId>org.openjfx</groupId>
<artifactId>javafx-graphics</artifactId>
</dependency>

<!-- Apache -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
</dependency>

<!-- Google -->
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
</dependency>

<!-- Google -->
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
Expand Down

This file was deleted.

Expand Up @@ -5,7 +5,36 @@
*******************************************************************************/
package org.cryptomator.keychain;

interface KeychainAccessStrategy extends KeychainAccess {
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 <code>null</code> 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 <code>true</code> if this KeychainAccessStrategy works on the current machine.
Expand Down
@@ -0,0 +1,117 @@
package org.cryptomator.keychain;

import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import javafx.application.Platform;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.ReadOnlyBooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.Arrays;

public class KeychainManager implements KeychainAccessStrategy {

private static final Logger LOG = LoggerFactory.getLogger(KeychainManager.class);

private final KeychainAccessStrategy keychain;
private LoadingCache<String, BooleanProperty> passphraseStoredProperties;

KeychainManager(KeychainAccessStrategy keychain) {
assert keychain.isSupported();
this.keychain = keychain;
this.passphraseStoredProperties = CacheBuilder.newBuilder() //
.weakValues() //
.build(CacheLoader.from(this::createStoredPassphraseProperty));
}

@Override
public void storePassphrase(String key, CharSequence passphrase) throws KeychainAccessException {
keychain.storePassphrase(key, passphrase);
setPassphraseStored(key, true);
}

@Override
public char[] loadPassphrase(String key) throws KeychainAccessException {
char[] passphrase = keychain.loadPassphrase(key);
setPassphraseStored(key, passphrase != null);
return passphrase;
}

@Override
public void deletePassphrase(String key) throws KeychainAccessException {
keychain.deletePassphrase(key);
setPassphraseStored(key, false);
}

@Override
public void changePassphrase(String key, CharSequence passphrase) throws KeychainAccessException {
keychain.changePassphrase(key, passphrase);
setPassphraseStored(key, true);
}

@Override
public boolean isSupported() {
return true;
}

/**
* Checks if the keychain knows a passphrase for the given key.
* <p>
* Expensive operation. If possible, use {@link #getPassphraseStoredProperty(String)} instead.
*
* @param key The key to look up
* @return <code>true</code> if a password for <code>key</code> is stored.
* @throws KeychainAccessException
*/
public boolean isPassphraseStored(String key) throws KeychainAccessException {
char[] storedPw = null;
try {
storedPw = keychain.loadPassphrase(key);
return storedPw != null;
} finally {
if (storedPw != null) {
Arrays.fill(storedPw, ' ');
}
}
}

private void setPassphraseStored(String key, boolean value) {
BooleanProperty property = passphraseStoredProperties.getIfPresent(key);
if (property != null) {
if (Platform.isFxApplicationThread()) {
property.set(value);
} else {
LOG.warn("");
Platform.runLater(() -> property.set(value));
}
}
}

/**
* Returns an observable property for use in the UI that tells whether a passphrase is stored for the given key.
* <p>
* Assuming that this process is the only process modifying Cryptomator-related items in the system keychain, this
* property stays in memory in an attempt to avoid unnecessary calls to the system keychain. Note that due to this
* fact the value stored in the returned property is not 100% reliable. Code defensively!
*
* @param key The key to look up
* @return An observable property which is <code>true</code> when it almost certain that a password for <code>key</code> is stored.
* @see #isPassphraseStored(String)
*/
public ReadOnlyBooleanProperty getPassphraseStoredProperty(String key) {
return passphraseStoredProperties.getUnchecked(key);
}

private BooleanProperty createStoredPassphraseProperty(String key) {
try {
LOG.warn("LOAD"); // TODO remove
return new SimpleBooleanProperty(isPassphraseStored(key));
} catch (KeychainAccessException e) {
return new SimpleBooleanProperty(false);
}
}

}
Expand Up @@ -5,29 +5,41 @@
*******************************************************************************/
package org.cryptomator.keychain;

import com.google.common.collect.Sets;
import dagger.Binds;
import dagger.Module;
import dagger.Provides;
import dagger.multibindings.ElementsIntoSet;
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 class KeychainModule {
public abstract class KeychainModule {

@Binds
@IntoSet
abstract KeychainAccessStrategy bindMacSystemKeychainAccess(MacSystemKeychainAccess keychainAccessStrategy);

@Binds
@IntoSet
abstract KeychainAccessStrategy bindWindowsProtectedKeychainAccess(WindowsProtectedKeychainAccess keychainAccessStrategy);

@Binds
@IntoSet
abstract KeychainAccessStrategy bindLinuxSecretServiceKeychainAccess(LinuxSecretServiceKeychainAccess keychainAccessStrategy);

@Provides
@ElementsIntoSet
Set<KeychainAccessStrategy> provideKeychainAccessStrategies(MacSystemKeychainAccess macKeychain, WindowsProtectedKeychainAccess winKeychain, LinuxSecretServiceKeychainAccess linKeychain) {
return Sets.newHashSet(macKeychain, winKeychain, linKeychain);
@Singleton
static Optional<KeychainAccessStrategy> provideSupportedKeychain(Set<KeychainAccessStrategy> keychainAccessStrategies) {
return keychainAccessStrategies.stream().filter(KeychainAccessStrategy::isSupported).findFirst();
}

@Provides
@Singleton
public Optional<KeychainAccess> provideSupportedKeychain(Set<KeychainAccessStrategy> keychainAccessStrategies) {
return keychainAccessStrategies.stream().filter(KeychainAccessStrategy::isSupported).map(KeychainAccess.class::cast).findFirst();
public static Optional<KeychainManager> provideKeychainManager(Optional<KeychainAccessStrategy> keychainAccess) {
return keychainAccess.map(KeychainManager::new);
}

}
Expand Up @@ -3,11 +3,13 @@
import org.apache.commons.lang3.SystemUtils;

import javax.inject.Inject;
import javax.inject.Singleton;
import java.util.Optional;

/**
* A facade to LinuxSecretServiceKeychainAccessImpl that doesn't depend on libraries that are unavailable on Mac and Windows.
*/
@Singleton
public class LinuxSecretServiceKeychainAccess implements KeychainAccessStrategy {

// the actual implementation is hidden in this delegate object which is loaded via reflection,
Expand Down
Expand Up @@ -8,11 +8,13 @@
import java.util.Optional;

import javax.inject.Inject;
import javax.inject.Singleton;

import org.apache.commons.lang3.SystemUtils;
import org.cryptomator.jni.MacFunctions;
import org.cryptomator.jni.MacKeychainAccess;

@Singleton
class MacSystemKeychainAccess implements KeychainAccessStrategy {

private final Optional<MacFunctions> macFunctions;
Expand Down Expand Up @@ -47,8 +49,10 @@ public void deletePassphrase(String key) {
}

@Override
public void changePassphrase(String key, CharSequence passphrase) throws KeychainAccessException {
storePassphrase(key, passphrase);
public void changePassphrase(String key, CharSequence passphrase) {
if (keychain().deletePassword(key)) {
keychain().storePassword(key, passphrase);
}
}

}
Expand Up @@ -25,6 +25,7 @@
import org.slf4j.LoggerFactory;

import javax.inject.Inject;
import javax.inject.Singleton;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
Expand All @@ -50,6 +51,7 @@

import static java.nio.charset.StandardCharsets.UTF_8;

@Singleton
class WindowsProtectedKeychainAccess implements KeychainAccessStrategy {

private static final Logger LOG = LoggerFactory.getLogger(WindowsProtectedKeychainAccess.class);
Expand Down Expand Up @@ -113,8 +115,11 @@ public void deletePassphrase(String key) {
}

@Override
public void changePassphrase(String key, CharSequence passphrase) throws KeychainAccessException {
storePassphrase(key, passphrase);
public void changePassphrase(String key, CharSequence passphrase) {
loadKeychainEntriesIfNeeded();
if (keychainEntries.remove(key) != null) {
storePassphrase(key, passphrase);
}
}

@Override
Expand Down

0 comments on commit 570286c

Please sign in to comment.