Skip to content

swiesend/secret-service

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

582 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Secret Service

Maven Central

A Java library for storing secrets in a keyring over the DBus.

The library is conform to the freedesktop.org Secret Service API 0.2 and thus compatible with Gnome linux systems.

The Secret Service itself is implemented by the gnome-keyring and provided by the gnome-keyring-daemon.

This library can be seen as the functional equivalent to the libsecret C client library.

Related

For KDE systems there is the kdewallet client library, kindly provided by @purejava.

Security Issues

CVE-2018-19358 (Vulnerability)

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, SecurityPhilosophy and disputed because the behavior represents a design decision.

Publisher Url Base Score Vector Published Last Update Status
NVD NIST 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 [5.5 MEDIUM] CVSS:3.0 unpublished
Red Hat 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 [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 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 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 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

The library provides two API layers, both using transport-encrypted secrets over the D-Bus.

Dependency

Add the secret-service as dependency to your project. You may want to exclude the slf4j-api if you use an incompatible version. The current version requires at least JDK 17.

<dependency>
    <groupId>de.swiesend</groupId>
    <artifactId>secret-service</artifactId>
    <version>3.0.0-beta</version>
    <exclusions>
        <exclusion>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-api</artifactId>
        </exclusion>
    </exclusions>
</dependency>

Functional API (Recommended)

The functional API uses instance-scoped connections, Optional returns, and AutoCloseable lifecycle management.

Basic Usage

try (ServiceInterface service = SecretService.create()
        .orElseThrow(() -> new IOException("Secret service not available"))) {
    CollectionInterface collection = service
            .openSession()
            .flatMap(session -> session.collection("My Collection", Optional.empty()))
            .orElseThrow(() -> new IOException("Could not open collection"));

    // Store a secret
    String item = collection.createItem("My Item", "secret")
            .orElseThrow(() -> new IOException("Could not create item"));

    // Retrieve a secret safely using the callback API
    // The char[] is automatically zeroed after the callback returns.
    collection.withSecret(item, secret -> {
        // Use the secret here — it never escapes this scope.
        return Arrays.equals(secret, "secret".toCharArray());
    });

    // Clean up
    collection.deleteItem(item);
    collection.delete();
}

Secure Secret Access with Callbacks

The withSecret() and withSecrets() methods guarantee that decrypted secrets are zeroed from memory after the callback returns (or throws). This prevents sensitive data from lingering on the heap.

try (ServiceInterface service = SecretService.create()
        .orElseThrow(() -> new IOException("Secret service not available"))) {
    CollectionInterface collection = service
            .openSession()
            .flatMap(session -> session.collection("My Collection", Optional.of("collection-password")))
            .orElseThrow(() -> new IOException("Could not open collection"));

    Map<String, String> attributes = Map.of("application", "my-app", "uuid", "42");
    String item = collection.createItem("API Key", "my-secret-api-key", attributes)
            .orElseThrow(() -> new IOException("Could not create item"));

    // Hash a secret without exposing it — only the hash escapes the callback.
    MessageDigest md = MessageDigest.getInstance("SHA-256");
    Optional<byte[]> hash = collection.withSecret(item, secret -> {
        ByteBuffer encoded = StandardCharsets.UTF_8.encode(CharBuffer.wrap(secret));
        byte[] bytes = new byte[encoded.remaining()];
        encoded.get(bytes);
        try {
            return md.digest(bytes);
        } finally {
            Arrays.fill(bytes, (byte) 0);
            if (encoded.hasArray()) Arrays.fill(encoded.array(), (byte) 0);
        }
    });

    // Compare a secret against user input — only a boolean escapes.
    Optional<Boolean> matches = collection.withSecret(item, secret -> {
        return Arrays.equals(secret, userInput);
    });

    collection.deleteItem(item);
    collection.delete();
}

Standalone Collection (No Manual Session Management)

// Opens its own D-Bus connection, session, and encryption — all cleaned up on close().
try (CollectionInterface collection = Collection.open("My Collection")
        .orElseThrow(() -> new IOException("Could not open collection"))) {
    String item = collection.createItem("My Item", "secret", Map.of("key", "value"))
            .orElseThrow(() -> new IOException("Could not create item"));

    // Find items by attributes
    List<String> found = collection.getItems(Map.of("key", "value")).orElse(List.of());

    collection.deleteItem(item);
    collection.delete();
}

SimpleCollection API (Legacy)

The SimpleCollection API preserves the original 1.x interface for backward compatibility. It uses a static shared D-Bus connection with a JVM shutdown hook.

New code should prefer the functional API above.

try (SimpleCollection collection = new SimpleCollection("My Collection", "super secret")) {
    // define unique attributes
    Map<String, String> attributes = new HashMap<>();
    attributes.put("uuid", "42");

    // create and forget
    collection.createItem("My Item", "secret", attributes);

    // find by attributes
    List<String> items = collection.getItems(attributes);
    String item = items.get(0);

    char[] actual = collection.getSecret(item);
    assertEquals("secret", new String(actual));
    Arrays.fill(actual, '\0'); // caller must clear manually

    collection.deleteItem(item);
    collection.delete();
}
// The D-Bus connection is closed at JVM shutdown, not by close().
// Call SimpleCollection.disconnect() to close it manually.

Transport Encryption:

Both APIs use transport encryption (DH key exchange + AES-128-CBC) automatically. For details see: Transfer of Secrets, Transport Encryption Example

Low-Level API

The low-level API gives access to all defined D-Bus Methods, Properties and Signals of the Secret Service interface:

For the usage of the low-level API see the tests:

D-Bus Interfaces

The underlying introspected XML D-Bus interfaces are available as resources.

Testing & Tools

All tests are integration tests that require a running session D-Bus and a Secret Service provider.

mvn test                                  # default suite (excludes @Tag("system-test"))
mvn test -Psystem-test                    # provider-agnostic system suite (ProviderSystemTest)

The CI test environments live under .github/providers/ — one container per provider (gnome-keyring, kwallet, keepassxc), each starting a path-based D-Bus via the shared dbus-up.sh. The default regression workflow runs against gnome-keyring; the system-tests workflow runs the system suite across all three providers (the KWallet/KeePassXC legs are best-effort and non-blocking).

docker build -f .github/providers/gnome-keyring/Dockerfile -t secret-service-test .
docker run --rm -v "$(pwd)":/workspace secret-service-test

Standalone GUI

A standalone Swing GUI for manually exercising the library against a live provider lives in the separate tools/gui project (kept out of the library jar and the test suite). Build the core, then launch it:

mvn -DskipTests -Dgpg.skip=true install   # install the core artifact locally
mvn -f tools/gui/pom.xml exec:java         # launch the GUI

Contributing

You are welcome to point out issues, file PRs and comment on the project.

Please keep in mind that this is a non-profit effort in my spare time and thus it may take some time until issues are addressed.

Thank You

Special thanks goes out to

Packages

 
 
 

Contributors

Languages