001/* 002 * SPDX-License-Identifier: Apache-2.0 003 * 004 * Copyright 2025-2026 The Enola <https://enola.dev> Authors 005 * 006 * Licensed under the Apache License, Version 2.0 (the "License"); 007 * you may not use this file except in compliance with the License. 008 * You may obtain a copy of the License at 009 * 010 * https://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, software 013 * distributed under the License is distributed on an "AS IS" BASIS, 014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 015 * See the License for the specific language governing permissions and 016 * limitations under the License. 017 */ 018package dev.enola.common.secret.gnome; 019 020import de.swiesend.secretservice.simple.SimpleCollection; 021 022import dev.enola.common.secret.Secret; 023import dev.enola.common.secret.SecretManager; 024 025import java.io.Closeable; 026import java.io.IOException; 027import java.util.Arrays; 028import java.util.Optional; 029 030/** 031 * GnomeSecretManager is a {@link SecretManager} implementation that uses the GNOME keyring via 032 * D-Bus. (An alternative would be to "shell out" and execute the external "secret-tool" command.) 033 */ 034public class GnomeSecretManager implements SecretManager, Closeable { 035 036 // TODO This does not actually work; see https://github.com/swiesend/secret-service/issues/52 037 038 private final SimpleCollection dbus; 039 040 public GnomeSecretManager() throws IOException { 041 if (!SimpleCollection.isGnomeKeyringAvailable()) 042 throw new IllegalStateException("Gnome keyring is not available."); 043 this.dbus = new SimpleCollection(); 044 } 045 046 @Override 047 public void store(String key, char[] value) throws IOException { 048 if (dbus.createItem(key, new ArrayCharSequence(value)) == null) { 049 Arrays.fill(value, '\0'); 050 throw new IOException("Failed to store secret."); 051 } 052 } 053 054 @Override 055 public Optional<Secret> getOptional(String key) { 056 char[] secret = dbus.getSecret(key); 057 return secret != null ? Optional.of(new Secret(secret)) : Optional.empty(); 058 } 059 060 @Override 061 public void delete(String key) { 062 dbus.deleteItem(key); 063 } 064 065 @Override 066 public void close() { 067 dbus.close(); 068 } 069 070 public static void main(String[] args) throws IOException { 071 try (SimpleCollection collection = new SimpleCollection()) { 072 String item = collection.createItem("My Item", "secret"); 073 074 char[] actual = collection.getSecret(item); 075 // assertEquals("secret", new String(actual)); 076 // assertEquals("My Item", collection.getLabel(item)); 077 078 collection.deleteItem(item); 079 } 080 081 var secret = "hello, world".toCharArray(); 082 var secretManager = new GnomeSecretManager(); 083 secretManager.store("test", secret); 084 secretManager 085 .get("test") 086 .process( 087 it -> { 088 if (Arrays.compare(it, secret) != 0) throw new IllegalStateException(); 089 }); 090 secretManager.delete("test"); 091 secretManager.close(); 092 } 093}