Merge pull request #1294 from purejava/access-kde-wallets

This adds the missing part to linux system keychain access: use KDE wallets
This commit is contained in:
Armin Schrenk
2020-08-26 10:03:55 +02:00
committed by GitHub
6 changed files with 149 additions and 12 deletions

View File

@@ -67,7 +67,7 @@
<configuration>
<outputDirectory>${project.build.directory}/libs</outputDirectory>
<excludeClassifiers>linux,mac,win</excludeClassifiers>
<excludeArtifactIds>dbus-java,secret-service,hkdf,java-utils</excludeArtifactIds>
<excludeArtifactIds>dbus-java,secret-service,kdewallet,hkdf,java-utils</excludeArtifactIds>
</configuration>
</execution>
<execution>
@@ -83,14 +83,14 @@
</configuration>
</execution>
<execution>
<id>copy-linux-secret-service</id>
<id>copy-linux-system-keychain-access</id>
<phase>prepare-package</phase>
<goals>
<goal>copy-dependencies</goal>
</goals>
<configuration>
<outputDirectory>${project.build.directory}/linux-libs</outputDirectory>
<includeArtifactIds>dbus-java,secret-service,hkdf,java-utils</includeArtifactIds>
<includeArtifactIds>dbus-java,secret-service,kdewallet,hkdf,java-utils</includeArtifactIds>
</configuration>
</execution>
<execution>

View File

@@ -24,7 +24,7 @@
<groupId>org.openjfx</groupId>
<artifactId>javafx-graphics</artifactId>
</dependency>
<!-- Apache -->
<dependency>
<groupId>org.apache.commons</groupId>
@@ -53,6 +53,12 @@
<artifactId>secret-service</artifactId>
</dependency>
<!-- kdewallet lib -->
<dependency>
<groupId>org.purejava</groupId>
<artifactId>kdewallet</artifactId>
</dependency>
<!-- Logging -->
<dependency>
<groupId>org.slf4j</groupId>

View File

@@ -28,7 +28,7 @@ public abstract class KeychainModule {
@Binds
@IntoSet
abstract KeychainAccessStrategy bindLinuxSecretServiceKeychainAccess(LinuxSecretServiceKeychainAccess keychainAccessStrategy);
abstract KeychainAccessStrategy bindLinuxSystemKeychainAccess(LinuxSystemKeychainAccess keychainAccessStrategy);
@Provides
@Singleton

View File

@@ -0,0 +1,121 @@
package org.cryptomator.keychain;
import org.freedesktop.dbus.connections.impl.DBusConnection;
import org.freedesktop.dbus.exceptions.DBusException;
import org.kde.KWallet;
import org.kde.Static;
import org.purejava.KDEWallet;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class LinuxKDEWalletKeychainAccessImpl implements KeychainAccessStrategy {
private final Logger log = LoggerFactory.getLogger(LinuxKDEWalletKeychainAccessImpl.class);
private final String FOLDER_NAME = "Cryptomator";
private final String APP_NAME = "Cryptomator";
private DBusConnection connection;
private KDEWallet wallet;
private int handle = -1;
public LinuxKDEWalletKeychainAccessImpl() {
try {
connection = DBusConnection.getConnection(DBusConnection.DBusBusType.SESSION);
} catch (DBusException e) {
e.printStackTrace();
}
}
@Override
public boolean isSupported() {
try {
wallet = new KDEWallet(connection);
return wallet.isEnabled();
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
@Override
public void storePassphrase(String key, CharSequence passphrase) throws KeychainAccessException {
try {
if (walletIsOpen() &&
!(wallet.hasEntry(handle, FOLDER_NAME, key, APP_NAME)
&& wallet.entryType(handle, FOLDER_NAME, key, APP_NAME) == 1)
&& wallet.writePassword(handle, FOLDER_NAME, key, passphrase.toString(), APP_NAME) == 0) {
log.debug("Passphrase successfully stored.");
} else {
log.debug("Passphrase was not stored.");
}
} catch (Exception e) {
log.error(e.toString(), e.getCause());
throw new KeychainAccessException(e);
}
}
@Override
public char[] loadPassphrase(String key) throws KeychainAccessException {
String password = "";
try {
if (walletIsOpen()) {
password = wallet.readPassword(handle, FOLDER_NAME, key, APP_NAME);
log.debug("loadPassphrase: wallet is open.");
} else {
log.debug("loadPassphrase: wallet is closed.");
}
return (password.equals("")) ? null : password.toCharArray();
} catch (Exception e) {
throw new KeychainAccessException(e);
}
}
@Override
public void deletePassphrase(String key) throws KeychainAccessException {
try {
if (walletIsOpen()
&& wallet.hasEntry(handle, FOLDER_NAME, key, APP_NAME)
&& wallet.entryType(handle, FOLDER_NAME, key, APP_NAME) == 1
&& wallet.removeEntry(handle, FOLDER_NAME, key, APP_NAME) == 0) {
log.debug("Passphrase successfully deleted.");
} else {
log.debug("Passphrase was not deleted.");
}
} catch (Exception e) {
throw new KeychainAccessException(e);
}
}
@Override
public void changePassphrase(String key, CharSequence passphrase) throws KeychainAccessException {
try {
if (walletIsOpen()
&& wallet.hasEntry(handle, FOLDER_NAME, key, APP_NAME)
&& wallet.entryType(handle, FOLDER_NAME, key, APP_NAME) == 1
&& wallet.writePassword(handle, FOLDER_NAME, key, passphrase.toString(), APP_NAME) == 0) {
log.debug("Passphrase successfully changed.");
} else {
log.debug("Passphrase could not be changed.");
}
} catch (Exception e) {
throw new KeychainAccessException(e);
}
}
private boolean walletIsOpen() throws KeychainAccessException {
try {
if (wallet.isOpen(Static.DEFAULT_WALLET)) {
// This is needed due to KeechainManager loading the passphase directly
if (handle == -1) handle = wallet.open(Static.DEFAULT_WALLET, 0, APP_NAME);
return true;
}
wallet.openAsync(Static.DEFAULT_WALLET, 0, APP_NAME, false);
wallet.getSignalHandler().await(KWallet.walletAsyncOpened.class, Static.ObjectPaths.KWALLETD5, () -> null);
handle = wallet.getSignalHandler().getLastHandledSignal(KWallet.walletAsyncOpened.class, Static.ObjectPaths.KWALLETD5).handle;
log.debug("Wallet successfully initialized.");
return handle != -1;
} catch (Exception e) {
throw new KeychainAccessException(e);
}
}
}

View File

@@ -7,24 +7,28 @@ 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.
* A facade to LinuxSecretServiceKeychainAccessImpl and LinuxKDEWalletKeychainAccessImpl
* that depend on libraries that are unavailable on Mac and Windows.
*/
@Singleton
public class LinuxSecretServiceKeychainAccess implements KeychainAccessStrategy {
public class LinuxSystemKeychainAccess implements KeychainAccessStrategy {
// the actual implementation is hidden in this delegate object which is loaded via reflection,
// the actual implementation is hidden in this delegate objects which are loaded via reflection,
// as it depends on libraries that aren't necessarily available:
private final Optional<KeychainAccessStrategy> delegate;
@Inject
public LinuxSecretServiceKeychainAccess() {
this.delegate = constructGnomeKeyringKeychainAccess();
public LinuxSystemKeychainAccess() {
this.delegate = constructKeychainAccess();
}
private static Optional<KeychainAccessStrategy> constructGnomeKeyringKeychainAccess() {
try {
private static Optional<KeychainAccessStrategy> constructKeychainAccess() {
try { // is gnome-keyring or kwallet installed?
Class<?> clazz = Class.forName("org.cryptomator.keychain.LinuxSecretServiceKeychainAccessImpl");
KeychainAccessStrategy instance = (KeychainAccessStrategy) clazz.getDeclaredConstructor().newInstance();
if (instance.isSupported()) return Optional.of(instance);
clazz = Class.forName("org.cryptomator.keychain.LinuxKDEWalletKeychainAccessImpl");
instance = (KeychainAccessStrategy) clazz.getDeclaredConstructor().newInstance();
return Optional.of(instance);
} catch (Exception e) {
return Optional.empty();

View File

@@ -34,6 +34,7 @@
<javafx.version>14</javafx.version>
<commons-lang3.version>3.11</commons-lang3.version>
<secret-service.version>1.1.0</secret-service.version>
<kdewallet.version>1.0.1</kdewallet.version>
<jwt.version>3.10.3</jwt.version>
<easybind.version>2.1.0</easybind.version>
<guava.version>29.0-jre</guava.version>
@@ -168,6 +169,11 @@
<artifactId>secret-service</artifactId>
<version>${secret-service.version}</version>
</dependency>
<dependency>
<groupId>org.purejava</groupId>
<artifactId>kdewallet</artifactId>
<version>${kdewallet.version}</version>
</dependency>
<!-- JWT -->
<dependency>