Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft: Develop 2.x.x #32

Draft
wants to merge 74 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
74 commits
Select commit Hold shift + click to select a range
5d85e9b
Add versions-maven-plugin for dependency updates
swiesend Oct 21, 2021
cf74249
Require JDK17 and update dbus-java to version `4.0.0` and
swiesend Feb 20, 2022
451ff3e
Add Optional<>
swiesend Mar 6, 2022
ed834ff
Add better error handling
swiesend Mar 6, 2022
915445d
Let `MessageHandler` return `Optional`s
swiesend Apr 2, 2022
98b6a20
Make version 2.0.0-alpha
swiesend Apr 2, 2022
da5ed3b
Add new functional interfaces
swiesend Apr 2, 2022
32fee71
Extend the README
swiesend Sep 11, 2022
ca5839d
Align
swiesend Sep 11, 2022
9fc2429
Only log simple names
swiesend Sep 11, 2022
dfb60d1
Enable setProperty() with checked response
swiesend Sep 11, 2022
e23b359
Add "org.freedesktop.DBus.Error.UnknownObject"
swiesend Oct 6, 2022
19cb9aa
Add subclasses for unambiguous control flow for the initialization
swiesend Nov 10, 2022
252a2d8
Shorten type
swiesend Nov 10, 2022
4e014dd
Implement the new secret service 2.0
swiesend Nov 10, 2022
9d02040
Add interfaces and implementations
swiesend Nov 10, 2022
b690696
Add autoclose
swiesend Jan 16, 2023
40a3f1c
Change Messaging Interface to returning `Optional`
swiesend Jun 26, 2023
c7f3b1e
Add functional interfaces
swiesend Jun 26, 2023
be1ddb6
Add functional implementations
swiesend Jun 26, 2023
687cab4
Add a basic functional test
swiesend Jun 26, 2023
eca8302
Add abstraction layer for detecting available D-Bus services
swiesend Jun 26, 2023
1d5c95d
Add basic unit test for D-Bus System connection management
swiesend Jun 26, 2023
4b0cc96
Add high-level documentation
swiesend Jun 26, 2023
ed68948
Add functional interfaces
swiesend Jun 26, 2023
d22ee4e
Fix merge problems
swiesend Jul 2, 2023
4d87063
Remove log statement
swiesend Jul 2, 2023
d86eb43
Remove log statement
swiesend Jul 2, 2023
8a92c47
Remove unused method from the SecretService interface
swiesend Jul 2, 2023
d71eea8
Remove unused method clear()
swiesend Jul 2, 2023
46e1475
Move functional packages to de.swiesend.secretservice.functional
swiesend Jul 27, 2023
d4082f2
Let decrypt return an Optional<char[]>
swiesend Jul 27, 2023
3487006
Fix createItem()
swiesend Jul 27, 2023
f29b9ef
Fix Item.setSecret()
swiesend Jul 27, 2023
6fc84c6
Handle "org.freedesktop.DBus.Error.Failed" and multiple parameters
swiesend Jul 27, 2023
7657a9e
Fix createItem()
swiesend Jul 27, 2023
1ac023b
Disable disconnect() test
swiesend Jul 27, 2023
3b39833
Fix MessageHandler switch
swiesend Jul 27, 2023
ae69821
Return true in an array when no error or other parameters are present
swiesend Jul 27, 2023
5eecd9e
Remove unnecessary exception imports
swiesend Jul 27, 2023
d0834e9
Remove solved TODOs
swiesend Jul 27, 2023
1ebe2e9
Alter comment
swiesend Jul 27, 2023
8b2c739
Move end-to-end example
swiesend Jul 27, 2023
db5e6e7
Activate collection.delete() for the example
swiesend Jul 27, 2023
f89d005
Change doc-string
swiesend Jul 27, 2023
cf292a6
Adapt performPrompt() and unlock() returning boolean
swiesend Jul 28, 2023
6272921
Allow fine-grained auto-close for the system
swiesend Jul 28, 2023
dc1453a
Fix collection.delete() and add basic tests
swiesend Jul 28, 2023
f60dc35
Fix typo
swiesend Oct 2, 2023
cc6cf6e
Simplify path extraction
swiesend Oct 2, 2023
a3214b6
Add <maven.compiler.source> and <maven.compiler.target>
swiesend Oct 2, 2023
7a07339
Elaborate mitigation on CVE-2018-19358
swiesend Oct 3, 2023
f625a12
Fix prompting for KeePassXC
swiesend Oct 3, 2023
e7ae714
Simplify collection retrieval
swiesend Oct 3, 2023
b302295
Remove logging for collection creation
swiesend Oct 3, 2023
52b423a
Reformat nested statement
swiesend Oct 3, 2023
2931014
Add documentation to the createItem interface
swiesend Oct 3, 2023
85291d4
Remove unnecessary calls for the simple e2e test.
swiesend Oct 3, 2023
9092419
Update comment on keyring dependent results
swiesend Oct 3, 2023
8287487
Make label Optional and do not init Optionals with null
swiesend Oct 8, 2023
19ffa6f
Fix nullable Optional
swiesend Oct 8, 2023
857a02f
Rename to isGnomeKeyringAvailable
swiesend Oct 8, 2023
651b6a5
Fix deleteItem() for gnome-keyring
swiesend Oct 8, 2023
e74f3e0
Activate deleting test collection
swiesend Oct 8, 2023
9ba88ad
Reformat
swiesend Oct 8, 2023
853d6f1
Rewrite prompt handling with Optional
swiesend Oct 8, 2023
64ab330
Add: default constructor for a Collection without any parameters
swiesend Nov 11, 2023
4d4b679
Add: deleteItem() and deleteItems()
swiesend Nov 11, 2023
4ce1817
Add: getAttributes()
swiesend Nov 11, 2023
6bb26e0
Add: clear() collection password
swiesend Nov 11, 2023
699315c
Add: test for unlockItem()
swiesend Dec 1, 2023
e052a57
Add: remaining tests
swiesend Dec 1, 2023
38aa004
Add: early exit, when already closed
swiesend Dec 5, 2023
ae19623
Change: lock all collections when retrieving all secrets
swiesend Dec 5, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 45 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@

[![Maven Central](https://img.shields.io/maven-central/v/de.swiesend/secret-service.svg?label=Maven%20Central)](https://search.maven.org/search?q=g:%22de.swiesend%22%20AND%20a:%22secret-service%22)

A Java library for storing secrets in a keyring over the D-Bus.
A _Java_ library for storing secrets in a keyring over the _DBus_.

The library is conforming to the freedesktop.org
The library is conform to the freedesktop.org
[Secret Service API 0.2](https://specifications.freedesktop.org/secret-service/0.2) and thus compatible with Gnome linux systems.

The Secret Service itself is implemented by the [`gnome-keyring`](https://wiki.gnome.org/action/show/Projects/GnomeKeyring) and provided by the [`gnome-keyring-daemon`](https://wiki.gnome.org/Projects/GnomeKeyring/RunningDaemon).
Expand All @@ -19,9 +19,49 @@ For KDE systems there is the [`kdewallet`](https:/purejava/kdewallet

### CVE-2018-19358 (Vulnerability)

There is a current investigation on the behaviour of the Secret Service API, as other applications can easily read __any__ secret, if the keyring is unlocked (if a user is logged in, then the `login`/`default` collection is unlocked). Available D-Bus protection mechanisms (involving the busconfig and policy XML elements) are not used by default. The Secret Service API was never designed with a secure retrieval mechanism.

* [CVE-2018-19358](https://nvd.nist.gov/vuln/detail/CVE-2018-19358) Base Score: __[7.8 HIGH]__, CVSS:3.0
There is an investigation on the behaviour of the Secret Service API, as other applications can easily read __any__ secret, if the keyring is unlocked (if a user is logged in, then the `login`/`default` collection is unlocked).
Available D-Bus protection mechanisms (involving the `busconfig` and `policy XML elements) are not used by default. But D-Bus protection mechanisms are not sufficient to protect against malicious attackers, because applications could identify themselves as different applications with various mechanisms.
The Secret Service API was never designed with a secure retrieval mechanism, as this problem is mainly a design problem in the Linux desktop itself, which does not provide _Sandboxing_ (like Flatpak, sandbox, containers) for applications by default.

The attack vector is known, see GnomeKeyring [SecurityFAQ](https://wiki.gnome.org/Projects/GnomeKeyring/SecurityFAQ), [SecurityPhilosophy](https://wiki.gnome.org/Projects/GnomeKeyring/SecurityPhilosophy) and [disputed](https://gitlab.gnome.org/GNOME/gnome-keyring/-/issues/5) because the behavior represents a design decision.

| Publisher | Url | Base Score | Vector | Published | Last Update | Status |
|-----------|-------------------------------------------------------------------------------------------------------------------------------------------------|------------------|--------|-----------|-------------|-------------|
| NVD NIST | [CVE-2018-19358](https://nvd.nist.gov/vuln/detail/CVE-2018-19358) | __[7.8 HIGH]__ | CVSS:3.0/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H | 2018-11-18 | 2020-08-24 | active |
| Cisco | [GNOME Keyring Secret Service API Login Credentials Retrieval Vulnerability](https://tools.cisco.com/security/center/viewAlert.x?alertId=59179) | __[5.5 MEDIUM]__ | CVSS:3.0 | | | unpublished |
| Red Hat | [CVE-2018-19358](https://access.redhat.com/security/cve/cve-2018-19358) | __[4.3 MEDIUM]__ | CVSS:3.0/AV:P/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:N | 2018-07-06 | 2023-04-06 | Red Hat Product Security determined that this flaw was not a security vulnerability, but a design problem in the Linux desktop |
| Suse | related issue [CVE-2008-7320](https://www.suse.com/security/cve/CVE-2008-7320.html) | [2.1 LOW] | CVSS:2.0/AV:L/AC:L/Au:N/C:P/I:N/A:N | 2018-11-09 | 2023-07-03 | Resolved |

**Mitigation**

**Not recommended**
- Storing secrets in the `login`/`default` keyring, when there are potentially malicious applications installed by the user. This is often the case in not well maintained desktop environment.
- Implementing a `busconfig` for the D-Bus that enforces restrictions on the Secret Service API of the host system, knowing that these can be mitigated by providing a false sender/window/process id or dbus address.

**Recommended**
- [`easy`] Storing secrets in a non-default collection that is always locked. This is a compromise that is useful when the user is willing to be prompted for the collection password, when accessing a secret. One can lock the collection after retrieval, so that the secrets are only exposed for a brief moment.
- [`easy`] Using [KeePassXC](https://keepassxc.org/) as provider. The KeePassXC implementation of the Secret Service API mitigates unauthorized retrievals by providing several access control mechanisms.
- [`easy`] Always locked collection: One can lock the collection after retrieval, so that the secrets are only exposed for a brief moment.
- [`easy`] Storing secrets in a file with proper permissions instead of using the Secret Service API.
- [`moderate`] There are projects like [SOPS](https:/getsops/sops) for secret-management to encrypt and edit files. But again oneself has to store the encryption keys securely.
- [`easy`/`moderate`] Using disk encryption like [LUKS](https://gitlab.com/cryptsetup/cryptsetup) does not help against malicious applications, but at least against several scenarios with physical access.
- [`moderate`/`advanced`] Deliver your application in a secure sandbox.

**KeePassXC**

Notification:
- Show notification when passwords are retrieved by clients

Access Control:
- Confirm when passwords are retrieved by clients.
- Confirm when clients request entry deletion.
- Prompt to unlock database before searching.
- Management of an exposed database group, instead of the whole database.
- Prohibiting the deletion of the database. Only entries can be deleted, but are moved to the "Recycle Bin" group by default.

Authorization:
- Showing connected applications by PID and DBus Address.
- Using a keyfile that has to be present when accessing the collection.

## Usage

Expand Down
39 changes: 33 additions & 6 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -57,14 +57,17 @@
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.release>17</maven.compiler.release>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<java.version>17</java.version>

<!-- 3rd party dependencies -->
<hkdf.version>2.0.0</hkdf.version>
<dbus-java.version>4.3.0</dbus-java.version>
<slf4j.version>2.0.9</slf4j.version>
<slf4j.version>2.0.7</slf4j.version>

<!-- test dependencies -->
<junit-jupiter.version>5.10.0</junit-jupiter.version>
<junit-jupiter.version>5.9.3</junit-jupiter.version>
</properties>

<dependencies>
Expand All @@ -76,7 +79,8 @@
<version>${hkdf.version}</version>
</dependency>

<!-- https://mvnrepository.com/artifact/com.github.hypfvieh/dbus-java-core -->
<!-- https://mvnrepository.com/artifact/com.github.hypfvieh/dbus-java till `3.3.1` -->
<!-- https://mvnrepository.com/artifact/com.github.hypfvieh/dbus-java-core since `4.0.0`-->
<dependency>
<groupId>com.github.hypfvieh</groupId>
<artifactId>dbus-java-core</artifactId>
Expand Down Expand Up @@ -133,6 +137,12 @@
<scope>test</scope>
</dependency>

<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.12.0</version>
</dependency>

</dependencies>

<profiles>
Expand Down Expand Up @@ -165,6 +175,7 @@
</profiles>

<build>
<!-- What is pluginManagement in Maven's pom.xml? https://stackoverflow.com/questions/10483180/what-is-pluginmanagement-in-mavens-pom-xml -->
<pluginManagement><!-- lock down plugins versions to avoid using Maven defaults (may be moved to parent pom) -->
<plugins>
<!-- https://maven.apache.org/enforcer/maven-enforcer-plugin/ -->
Expand Down Expand Up @@ -238,6 +249,11 @@
<artifactId>jacoco-maven-plugin</artifactId>
<version>0.8.10</version>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>versions-maven-plugin</artifactId>
<version>2.8.1</version>
</plugin>
</plugins>
</pluginManagement>
<plugins>
Expand All @@ -247,9 +263,9 @@
<artifactId>maven-compiler-plugin</artifactId>
<version>3.11.0</version>
<configuration>
<release>9</release>
<source>9</source>
<target>9</target>
<release>17</release>
<source>17</source>
<target>17</target>
</configuration>
</plugin>
<!-- https://mvnrepository.com/artifact/org.apache.maven.plugins/maven-source-plugin -->
Expand Down Expand Up @@ -328,6 +344,17 @@
</dependency>
</dependencies>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>versions-maven-plugin</artifactId>
<configuration>
<allowAnyUpdates>false</allowAnyUpdates>
<allowMajorUpdates>false</allowMajorUpdates>
<allowMinorUpdates>true</allowMinorUpdates>
<!--<allowIncrementalUpdates>true</allowIncrementalUpdates>-->
<verbose>true</verbose>
</configuration>
</plugin>
</plugins>
</build>
</project>
85 changes: 42 additions & 43 deletions src/main/java/de/swiesend/secretservice/Collection.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,40 +2,41 @@

import org.freedesktop.dbus.DBusPath;
import org.freedesktop.dbus.ObjectPath;
import org.freedesktop.dbus.connections.impl.DBusConnection;
import org.freedesktop.dbus.messages.DBusSignal;
import org.freedesktop.dbus.types.UInt64;
import org.freedesktop.dbus.types.Variant;
import de.swiesend.secretservice.Static.Utils;
import de.swiesend.secretservice.handlers.Messaging;

import java.util.*;

public class Collection extends Messaging implements de.swiesend.secretservice.interfaces.Collection {

private String id;

public static final List<Class<? extends DBusSignal>> signals = Arrays.asList(
ItemCreated.class, ItemChanged.class, ItemDeleted.class);
private String id;

public Collection(DBusPath path, Service service) {
super(service.getConnection(), signals,
public Collection(DBusPath path, DBusConnection connection) {
super(connection, signals,
Static.Service.SECRETS,
path.getPath(),
Static.Interfaces.COLLECTION);
String[] split = path.getPath().split("/");
this.id = split[split.length - 1];
}

public Collection(DBusPath path, Service service, List<Class<? extends DBusSignal>> signals) {
super(service.getConnection(), signals,
public Collection(DBusPath path, DBusConnection connection, List<Class<? extends DBusSignal>> signals) {
super(connection, signals,
Static.Service.SECRETS,
path.getPath(),
Static.Interfaces.COLLECTION);
String[] split = path.getPath().split("/");
this.id = split[split.length - 1];
}

public Collection(String id, Service service) {
super(service.getConnection(), signals,
public Collection(String id, DBusConnection connection) {
super(connection, signals,
Static.Service.SECRETS,
Static.ObjectPaths.collection(id),
Static.Interfaces.COLLECTION);
Expand Down Expand Up @@ -70,71 +71,69 @@ public Collection(String id, Service service) {
*/
public static Map<String, Variant> createProperties(String label) {
HashMap<String, Variant> properties = new HashMap();
properties.put(LABEL, new Variant(label));
properties.put(LABEL, new Variant<>(label));
return properties;
}

@Override
public ObjectPath delete() {
Object[] response = send("Delete", "");
if (response == null) return null;
ObjectPath prompt = (ObjectPath) response[0];
public Optional<ObjectPath> delete() {
Optional<ObjectPath> prompt = send("Delete", "").flatMap(response ->
Utils.isNullOrEmpty(response) ? Optional.empty() : Optional.of((ObjectPath) response[0])
);
return prompt;
}

@Override
public List<ObjectPath> searchItems(Map<String, String> attributes) {
Object[] response = send("SearchItems", "a{ss}", attributes);
if (response == null) return null;
return (List<ObjectPath>) response[0];
public Optional<List<ObjectPath>> searchItems(Map<String, String> attributes) {
return send("SearchItems", "a{ss}", attributes).flatMap(response ->
Utils.isNullOrEmpty(response) ? Optional.empty() : Optional.of((List<ObjectPath>) response[0])
);
}

@Override
public Pair<ObjectPath, ObjectPath> createItem(Map<String, Variant> properties, Secret secret,
boolean replace) {
Object[] response = send("CreateItem", "a{sv}(oayays)b", properties, secret, replace);
if (response == null) return null;
return new Pair(response[0], response[1]);
public Optional<Pair<ObjectPath, ObjectPath>> createItem(Map<String, Variant> properties, Secret secret, boolean replace) {
return send("CreateItem", "a{sv}(oayays)b", properties, secret, replace).flatMap(response ->
(Utils.isNullOrEmpty(response) || response.length != 2) ?
Optional.empty() :
Optional.of(new Pair<ObjectPath, ObjectPath>((ObjectPath) response[0], (ObjectPath) response[1])));
}

@Override
public List<ObjectPath> getItems() {
Variant response = getProperty("Items");
if (response == null) return null;
return (ArrayList<ObjectPath>) response.getValue();
public Optional<List<ObjectPath>> getItems() {
return getProperty("Items").flatMap(variant ->
variant == null ? Optional.empty() : Optional.ofNullable((List<ObjectPath>) variant.getValue())
);
}

@Override
public String getLabel() {
Variant response = getProperty("Label");
if (response == null) return null;
return (String) response.getValue();
public Optional<String> getLabel() {
return getProperty("Label").flatMap(variant ->
variant == null ? Optional.empty() : Optional.ofNullable((String) variant.getValue())
);
}

@Override
public void setLabel(String label) {
setProperty("Label", new Variant(label));
public boolean setLabel(String label) {
return setProperty("Label", new Variant(label));
}

@Override
public boolean isLocked() {
Variant response = getProperty("Locked");
if (response == null) return true;
return (boolean) response.getValue();
Optional<Boolean> response = getProperty("Locked").map(variant ->
variant == null ? false : (boolean) variant.getValue());
return response.isPresent() ? response.get() : false;
}

@Override
public UInt64 created() {
Variant response = getProperty("Created");
if (response == null) return null;
return (UInt64) response.getValue();
public Optional<UInt64> created() {
return getProperty("Created").flatMap(variant ->
variant == null ? Optional.empty() : Optional.ofNullable((UInt64) variant.getValue()));
}

@Override
public UInt64 modified() {
Variant response = getProperty("Modified");
if (response == null) return null;
return (UInt64) response.getValue();
public Optional<UInt64> modified() {
return getProperty("Modified").flatMap(variant ->
variant == null ? Optional.empty() : Optional.ofNullable((UInt64) variant.getValue()));
}

@Override
Expand Down
Loading