Skip to content

Commit b9c6eb1

Browse files
committed
Get back GnomeKeyringKeychainAccess, KDEWalletKeychainAccess
1 parent 161ea15 commit b9c6eb1

File tree

7 files changed

+457
-4
lines changed

7 files changed

+457
-4
lines changed

pom.xml

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,9 @@
4141
<!-- runtime dependencies -->
4242

4343
<api.version>1.7.0</api.version>
44-
<secret-service.version>1.0.0</secret-service.version>
44+
<secret-service.version>2.0.1-alpha</secret-service.version>
45+
<kdewallet.version>1.4.0</kdewallet.version>
46+
<secret-service-02.version>1.0.0</secret-service-02.version>
4547
<slf4j.version>2.0.17</slf4j.version>
4648
<appindicator.version>1.4.2</appindicator.version>
4749

@@ -72,10 +74,20 @@
7274
<version>${slf4j.version}</version>
7375
</dependency>
7476
<dependency>
75-
<groupId>org.purejava</groupId>
77+
<groupId>de.swiesend</groupId>
7678
<artifactId>secret-service</artifactId>
7779
<version>${secret-service.version}</version>
7880
</dependency>
81+
<dependency>
82+
<groupId>org.purejava</groupId>
83+
<artifactId>kdewallet</artifactId>
84+
<version>${kdewallet.version}</version>
85+
</dependency>
86+
<dependency>
87+
<groupId>org.purejava</groupId>
88+
<artifactId>secret-service</artifactId>
89+
<version>${secret-service-02.version}</version>
90+
</dependency>
7991
<!-- Java bindings for appindicator -->
8092
<dependency>
8193
<groupId>org.purejava</groupId>

src/main/java/module-info.java

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
import org.cryptomator.integrations.revealpath.RevealPathService;
55
import org.cryptomator.integrations.tray.TrayMenuController;
66
import org.cryptomator.linux.autostart.FreedesktopAutoStartService;
7+
import org.cryptomator.linux.keychain.GnomeKeyringKeychainAccess;
8+
import org.cryptomator.linux.keychain.KDEWalletKeychainAccess;
79
import org.cryptomator.linux.keychain.SecretServiceKeychainAccess;
810
import org.cryptomator.linux.quickaccess.DolphinPlaces;
911
import org.cryptomator.linux.quickaccess.NautilusBookmarks;
@@ -15,11 +17,14 @@
1517
requires org.slf4j;
1618
requires org.freedesktop.dbus;
1719
requires org.purejava.appindicator;
20+
requires org.purejava.kwallet;
21+
requires de.swiesend.secretservice;
1822
requires org.purejava.secret;
1923
requires java.xml;
24+
requires org.cryptomator.integrations.linux;
2025

2126
provides AutoStartProvider with FreedesktopAutoStartService;
22-
provides KeychainAccessProvider with SecretServiceKeychainAccess;
27+
provides KeychainAccessProvider with SecretServiceKeychainAccess, GnomeKeyringKeychainAccess, KDEWalletKeychainAccess;
2328
provides RevealPathService with DBusSendRevealPathService;
2429
provides TrayMenuController with AppindicatorTrayMenuController;
2530
provides QuickAccessService with NautilusBookmarks, DolphinPlaces;
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
package org.cryptomator.linux.keychain;
2+
3+
import de.swiesend.secretservice.simple.SimpleCollection;
4+
import org.cryptomator.integrations.common.DisplayName;
5+
import org.cryptomator.integrations.common.OperatingSystem;
6+
import org.cryptomator.integrations.common.Priority;
7+
import org.cryptomator.integrations.keychain.KeychainAccessException;
8+
import org.cryptomator.integrations.keychain.KeychainAccessProvider;
9+
import org.slf4j.Logger;
10+
import org.slf4j.LoggerFactory;
11+
12+
import java.io.IOException;
13+
import java.util.List;
14+
import java.util.Map;
15+
16+
@Priority(900)
17+
@OperatingSystem(OperatingSystem.Value.LINUX)
18+
@DisplayName("GNOME Keyring")
19+
public class GnomeKeyringKeychainAccess implements KeychainAccessProvider {
20+
21+
private static final Logger LOG = LoggerFactory.getLogger(GnomeKeyringKeychainAccess.class);
22+
23+
private final String LABEL_FOR_SECRET_IN_KEYRING = "Cryptomator";
24+
25+
@Override
26+
public boolean isSupported() {
27+
try {
28+
return SimpleCollection.isGnomeKeyringAvailable();
29+
} catch (RuntimeException e) {
30+
LOG.warn("Initializing secret service keychain access failed", e);
31+
return false;
32+
} catch (ExceptionInInitializerError err) {
33+
LOG.warn("Initializing secret service keychain access failed", err.getException());
34+
return false;
35+
}
36+
}
37+
38+
@Override
39+
public boolean isLocked() {
40+
try (SimpleCollection keyring = new SimpleCollection()) {
41+
return keyring.isLocked();
42+
} catch (IOException e) {
43+
return true;
44+
}
45+
}
46+
47+
@Override
48+
public void storePassphrase(String key, String displayName, CharSequence passphrase) throws KeychainAccessException {
49+
try (SimpleCollection keyring = new SimpleCollection()) {
50+
List<String> list = keyring.getItems(createAttributes(key));
51+
if (list == null || list.isEmpty()) {
52+
keyring.createItem(LABEL_FOR_SECRET_IN_KEYRING, passphrase, createAttributes(key));
53+
} else {
54+
changePassphrase(key, displayName, passphrase);
55+
}
56+
} catch (IOException | SecurityException e) {
57+
throw new KeychainAccessException("Storing password failed.", e);
58+
}
59+
}
60+
61+
@Override
62+
public char[] loadPassphrase(String key) throws KeychainAccessException {
63+
try (SimpleCollection keyring = new SimpleCollection()) {
64+
List<String> list = keyring.getItems(createAttributes(key));
65+
if (list != null && !list.isEmpty()) {
66+
return keyring.getSecret(list.get(0));
67+
} else {
68+
return null;
69+
}
70+
} catch (IOException | SecurityException e) {
71+
throw new KeychainAccessException("Loading password failed.", e);
72+
}
73+
}
74+
75+
@Override
76+
public void deletePassphrase(String key) throws KeychainAccessException {
77+
try (SimpleCollection keyring = new SimpleCollection()) {
78+
List<String> list = keyring.getItems(createAttributes(key));
79+
if (list != null && !list.isEmpty()) {
80+
keyring.deleteItem(list.get(0));
81+
}
82+
} catch (IOException | SecurityException e) {
83+
throw new KeychainAccessException("Deleting password failed.", e);
84+
}
85+
}
86+
87+
@Override
88+
public void changePassphrase(String key, String displayName, CharSequence passphrase) throws KeychainAccessException {
89+
try (SimpleCollection keyring = new SimpleCollection()) {
90+
List<String> list = keyring.getItems(createAttributes(key));
91+
if (list != null && !list.isEmpty()) {
92+
keyring.updateItem(list.get(0), LABEL_FOR_SECRET_IN_KEYRING, passphrase, createAttributes(key));
93+
}
94+
} catch (IOException | SecurityException e) {
95+
throw new KeychainAccessException("Changing password failed.", e);
96+
}
97+
}
98+
99+
private Map<String, String> createAttributes(String key) {
100+
return Map.of("Vault", key);
101+
}
102+
103+
}
Lines changed: 196 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,196 @@
1+
package org.cryptomator.linux.keychain;
2+
3+
import org.cryptomator.integrations.common.DisplayName;
4+
import org.cryptomator.integrations.common.OperatingSystem;
5+
import org.cryptomator.integrations.common.Priority;
6+
import org.cryptomator.integrations.keychain.KeychainAccessException;
7+
import org.cryptomator.integrations.keychain.KeychainAccessProvider;
8+
import org.cryptomator.linux.util.CheckUtil;
9+
import org.freedesktop.dbus.connections.impl.DBusConnection;
10+
import org.freedesktop.dbus.connections.impl.DBusConnectionBuilder;
11+
import org.freedesktop.dbus.exceptions.DBusConnectionException;
12+
import org.freedesktop.dbus.exceptions.DBusException;
13+
import org.freedesktop.dbus.exceptions.DBusExecutionException;
14+
import org.purejava.kwallet.KWallet;
15+
import org.purejava.kwallet.KDEWallet;
16+
import org.purejava.kwallet.Static;
17+
import org.slf4j.Logger;
18+
import org.slf4j.LoggerFactory;
19+
20+
import java.util.Optional;
21+
22+
@Priority(900)
23+
@OperatingSystem(OperatingSystem.Value.LINUX)
24+
@DisplayName("KDE Wallet")
25+
public class KDEWalletKeychainAccess implements KeychainAccessProvider {
26+
27+
private static final Logger LOG = LoggerFactory.getLogger(KDEWalletKeychainAccess.class);
28+
private static final String FOLDER_NAME = "Cryptomator";
29+
private static final String APP_NAME = "Cryptomator";
30+
31+
private final Optional<ConnectedWallet> wallet;
32+
33+
public KDEWalletKeychainAccess() {
34+
this.wallet = ConnectedWallet.connect();
35+
}
36+
37+
@Override
38+
public boolean isSupported() {
39+
return wallet.map(ConnectedWallet::isSupported).orElse(false);
40+
}
41+
42+
@Override
43+
public boolean isLocked() {
44+
return wallet.map(ConnectedWallet::isLocked).orElse(false);
45+
}
46+
47+
@Override
48+
public void storePassphrase(String key, String displayName, CharSequence passphrase) throws KeychainAccessException {
49+
CheckUtil.checkState(wallet.isPresent(), "Keychain not supported.");
50+
wallet.get().storePassphrase(key, passphrase);
51+
}
52+
53+
@Override
54+
public char[] loadPassphrase(String key) throws KeychainAccessException {
55+
CheckUtil.checkState(wallet.isPresent(), "Keychain not supported.");
56+
return wallet.get().loadPassphrase(key);
57+
}
58+
59+
@Override
60+
public void deletePassphrase(String key) throws KeychainAccessException {
61+
CheckUtil.checkState(wallet.isPresent(), "Keychain not supported.");
62+
wallet.get().deletePassphrase(key);
63+
}
64+
65+
@Override
66+
public void changePassphrase(String key, String displayName, CharSequence passphrase) throws KeychainAccessException {
67+
CheckUtil.checkState(wallet.isPresent(), "Keychain not supported.");
68+
wallet.get().changePassphrase(key, passphrase);
69+
}
70+
71+
private static class ConnectedWallet {
72+
73+
private final KDEWallet wallet;
74+
private int handle = -1;
75+
76+
public ConnectedWallet(DBusConnection connection) {
77+
this.wallet = new KDEWallet(connection);
78+
}
79+
80+
static Optional<ConnectedWallet> connect() {
81+
try {
82+
return Optional.of(new ConnectedWallet(getNewConnection()));
83+
} catch (DBusException e) {
84+
LOG.warn("Connecting to D-Bus failed.", e);
85+
return Optional.empty();
86+
}
87+
}
88+
89+
private static DBusConnection getNewConnection() throws DBusException {
90+
try {
91+
return DBusConnectionBuilder.forSessionBus().withShared(false).build();
92+
} catch (DBusConnectionException | DBusExecutionException de) {
93+
LOG.warn("Connecting to SESSION bus failed.", de);
94+
LOG.warn("Falling back to SYSTEM DBus");
95+
return DBusConnectionBuilder.forSystemBus().build();
96+
}
97+
}
98+
99+
public boolean isSupported() {
100+
try {
101+
return wallet.isEnabled();
102+
} catch (RuntimeException e) {
103+
LOG.warn("Failed to check if KDE Wallet is available.", e);
104+
return false;
105+
}
106+
}
107+
108+
public boolean isLocked() {
109+
try {
110+
return !wallet.isOpen(Static.DEFAULT_WALLET);
111+
} catch (RuntimeException e) {
112+
LOG.warn("Failed to check whether KDE Wallet is open, therefore considering it closed.", e);
113+
return true;
114+
}
115+
}
116+
117+
public void storePassphrase(String key, CharSequence passphrase) throws KeychainAccessException {
118+
try {
119+
if (walletIsOpen() &&
120+
!(wallet.hasEntry(handle, FOLDER_NAME, key, APP_NAME) && wallet.entryType(handle, FOLDER_NAME, key, APP_NAME) == 1)
121+
&& wallet.writePassword(handle, FOLDER_NAME, key, passphrase.toString(), APP_NAME) == 0) {
122+
LOG.debug("Passphrase successfully stored.");
123+
} else {
124+
LOG.debug("Passphrase was not stored.");
125+
}
126+
} catch (RuntimeException e) {
127+
throw new KeychainAccessException("Storing the passphrase failed.", e);
128+
}
129+
}
130+
131+
public char[] loadPassphrase(String key) throws KeychainAccessException {
132+
String password = "";
133+
try {
134+
if (walletIsOpen()) {
135+
password = wallet.readPassword(handle, FOLDER_NAME, key, APP_NAME);
136+
LOG.debug("loadPassphrase: wallet is open.");
137+
} else {
138+
LOG.debug("loadPassphrase: wallet is closed.");
139+
}
140+
return (password.isEmpty()) ? null : password.toCharArray();
141+
} catch (RuntimeException e) {
142+
throw new KeychainAccessException("Loading the passphrase failed.", e);
143+
}
144+
}
145+
146+
public void deletePassphrase(String key) throws KeychainAccessException {
147+
try {
148+
if (walletIsOpen()
149+
&& wallet.hasEntry(handle, FOLDER_NAME, key, APP_NAME)
150+
&& wallet.entryType(handle, FOLDER_NAME, key, APP_NAME) == 1
151+
&& wallet.removeEntry(handle, FOLDER_NAME, key, APP_NAME) == 0) {
152+
LOG.debug("Passphrase successfully deleted.");
153+
} else {
154+
LOG.debug("Passphrase was not deleted.");
155+
}
156+
} catch (RuntimeException e) {
157+
throw new KeychainAccessException("Deleting the passphrase failed.", e);
158+
}
159+
}
160+
161+
public void changePassphrase(String key, CharSequence passphrase) throws KeychainAccessException {
162+
try {
163+
if (walletIsOpen()
164+
&& wallet.hasEntry(handle, FOLDER_NAME, key, APP_NAME)
165+
&& wallet.entryType(handle, FOLDER_NAME, key, APP_NAME) == 1
166+
&& wallet.writePassword(handle, FOLDER_NAME, key, passphrase.toString(), APP_NAME) == 0) {
167+
LOG.debug("Passphrase successfully changed.");
168+
} else {
169+
LOG.debug("Passphrase could not be changed.");
170+
}
171+
} catch (RuntimeException e) {
172+
throw new KeychainAccessException("Changing the passphrase failed.", e);
173+
}
174+
}
175+
176+
private boolean walletIsOpen() throws KeychainAccessException {
177+
try {
178+
if (wallet.isOpen(Static.DEFAULT_WALLET)) {
179+
// This is needed due to KeechainManager loading the passphase directly
180+
if (handle == -1) handle = wallet.open(Static.DEFAULT_WALLET, 0, APP_NAME);
181+
return true;
182+
}
183+
wallet.openAsync(Static.DEFAULT_WALLET, 0, APP_NAME, false);
184+
wallet.getSignalHandler().await(KWallet.walletAsyncOpened.class, Static.ObjectPaths.KWALLETD5, () -> null);
185+
handle = wallet.getSignalHandler().getLastHandledSignal(KWallet.walletAsyncOpened.class, Static.ObjectPaths.KWALLETD5).handle;
186+
LOG.debug("Wallet successfully initialized.");
187+
return handle != -1;
188+
} catch (RuntimeException e) {
189+
throw new KeychainAccessException("Asynchronous opening the wallet failed.", e);
190+
}
191+
}
192+
193+
194+
}
195+
}
196+
Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,3 @@
1-
org.cryptomator.linux.keychain.SecretServiceKeychainAccess
1+
org.cryptomator.linux.keychain.SecretServiceKeychainAccess
2+
org.cryptomator.linux.keychain.KDEWalletKeychainAccess
3+
org.cryptomator.linux.keychain.GnomeKeyringKeychainAccess

0 commit comments

Comments
 (0)