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; 019 020import java.io.IOException; 021import java.util.Optional; 022 023/** 024 * SecretManager is a "vault" of 🔑 {@link Secret}s. 025 * 026 * <p>You {@link #store(String, char[])}, then {@link #getOptional(String)} it; and maybe later 027 * {@link #delete(String)} it again. There is very intentionally no <code>Set<String> 028 * listKeys()</code> sort of method here. 029 */ 030public interface SecretManager { 031 032 // TODO: Implement a FileSecretManager which stores the secrets in an encrypted file vault which 033 // is decrypted with a passphrase which the user must type when starting an application is a 034 // possibility. We, however, prefer focusing on implementations based on OS support. 035 036 // TODO Write an ExecAgeSecretManager for https://github.com/Foxboron/age-plugin-tpm 037 // by generalizing the existing initial ExecPassSecretManager 038 039 // TODO Write an AppleSecretManager 040 // Initially using https://github.com/remko/age-plugin-se 041 // Later see if there is some direct Java JCA for HSM API for this? 042 043 // TODO Write a WindowsSecretManager 044 045 // TODO Write CLI with ./enola secret store 046 047 // TODO Write a multi-user dev.enola.common.context.TLC-based wrapper for this. 048 049 /** 050 * Stores a secret value associated with a unique key. The sensitive value is provided as a char 051 * array. Implementations will zero out the input {@code value} array immediately after calling 052 * this method for security. If a secret with the same key already exists, its value is 053 * overwritten. 054 * 055 * @param key The unique key (name) for the secret. 056 * @param value The sensitive secret value as a character array. 057 * @throws IOException If an error occurs while storing the secret. 058 */ 059 void store(String key, char[] value) throws IOException; 060 061 /** 062 * Retrieves a secret by its key, with optionality. 063 * 064 * @param key The unique key (name) of the secret to retrieve. 065 * @return An {@link Optional} containing the {@link Secret}, if found. 066 * @throws IOException If an error occurs while retrieving the secret. 067 */ 068 Optional<Secret> getOptional(String key) throws IOException; 069 070 /** 071 * Retrieves a secret by its key; throws if not found. 072 * 073 * @param key The unique key (name) of the secret to retrieve. 074 * @return An {@link Optional} containing the {@link Secret}, if found. 075 * @throws IllegalStateException if the secret is not found. 076 * @throws IOException If an error occurs while retrieving the secret. 077 */ 078 default Secret get(String key) throws IllegalStateException, IOException { 079 return getOptional(key) 080 .orElseThrow( 081 () -> 082 new IllegalStateException( 083 getClass().getSimpleName() + " missing secret: " + key)); 084 } 085 086 /** 087 * Deletes a secret from the manager. 088 * 089 * @param key The unique key (name) of the secret to delete. 090 * @throws IOException If an error occurs while deleting the secret. 091 */ 092 void delete(String key) throws IOException; 093}