Class KeyringStore

java.lang.Object
zeroecho.core.storage.KeyringStore
All Implemented Interfaces:
AutoCloseable, Destroyable

public final class KeyringStore extends Object implements AutoCloseable, Destroyable
Path-bound encrypted local software keystore.

A keyring uses PBKDF2-HMAC-SHA-256 to unwrap one random 256-bit store master key. Every entry and the ordered manifest are independently protected by AES-256-GCM. Java class names are neither persisted nor resolved. Import mappings come exclusively from a closed mapping to the immutable algorithm registry. Provider names are not persisted: standard key encodings are reconstructed by the current runtime's canonical importer. HMAC persistence admits only HmacSHA256, HmacSHA384, and HmacSHA512.

The keyring exclusively owns its filesystem path from successful create(Path, KeyringPassword) or open(Path, KeyringPassword) until close(). Only POSIX filesystems on which owner-only permissions and ownership can be verified are supported. Mutations replace a complete encrypted image atomically.

The nonce-reservation sidecar is authenticated independently from AES entry and manifest encryption. Its 256-bit MAC key is derived from the store master key and binary store UUID with HKDF-HMAC-SHA-256 and the fixed domain label zeroecho:keyring:nonce-reservation-mac:v1. The derived key is retained only while the store is open and is cleared on close. Previous sidecar versions are rejected.

Instances are thread-safe. Reads may proceed concurrently; mutations and close are exclusive. Closing clears the master and sidecar MAC keys and makes all subsequent operations fail. The pre-release plaintext format is rejected and is not migrated.

  • Method Details

    • create

      public static KeyringStore create(Path path, KeyringPassword password) throws IOException, GeneralSecurityException
      Creates a new encrypted keyring with the standard protection policy.
      Parameters:
      path - new keyring file
      password - borrowed unlock password; the caller remains responsible for destroying it
      Returns:
      open encrypted keyring
      Throws:
      IOException - if secure creation or durable persistence fails
      GeneralSecurityException - if cryptographic initialization fails
    • create

      public static KeyringStore create(Path path, KeyringPassword password, KeyringProtection protection) throws IOException, GeneralSecurityException
      Creates a new encrypted keyring.
      Parameters:
      path - new keyring file
      password - borrowed unlock password
      protection - operational protection policy
      Returns:
      open encrypted keyring
      Throws:
      IOException - if secure creation or durable persistence fails
      GeneralSecurityException - if cryptographic initialization fails
    • open

      public static KeyringStore open(Path path, KeyringPassword password) throws IOException, GeneralSecurityException
      Opens an encrypted keyring with the standard protection policy.
      Parameters:
      path - keyring file
      password - borrowed unlock password
      Returns:
      open encrypted keyring
      Throws:
      IOException - if the format, filesystem, or authentication check fails
      GeneralSecurityException - if cryptographic initialization fails
    • open

      public static KeyringStore open(Path path, KeyringPassword password, KeyringProtection protection) throws IOException, GeneralSecurityException
      Opens an encrypted keyring.
      Parameters:
      path - keyring file
      password - borrowed unlock password
      protection - operational protection policy
      Returns:
      open encrypted keyring
      Throws:
      IOException - if the format, filesystem, or authentication check fails
      GeneralSecurityException - if cryptographic initialization fails
    • generateStatefulKey

      public PublicKey generateStatefulKey(String alias, String algorithmId, LocalStatefulSigningAuthority authority) throws IOException, GeneralSecurityException
      Generates and enrolls a new default LMS or XMSS key under an independently preserved local authority. Existing aliases and imported keys cannot enroll. The caller owns the authority; the keyring never closes it. Enrollment is fenced before key insertion and becomes usable only after both key entries and the READY authority record are durable. Partial enrollment fails closed. Keep the authority outside the keyring directory and every backup root. Only the algorithm's default key-generation parameters are supported by this enrollment API. Exhaustion durably removes the private entry while preserving the public key for verification; private-key retrieval then becomes unavailable. Existing imported keys cannot be enrolled.
      Parameters:
      alias - unused logical alias without a key-type suffix
      algorithmId - registered stateful algorithm identifier
      authority - open independent local authority
      Returns:
      generated public key; the private key is retained by the keyring
      Throws:
      IOException - if enrollment or durable persistence fails
      GeneralSecurityException - if generation or authentication fails
      IllegalArgumentException - if the alias already exists or is invalid
      NullPointerException - if authority is null
    • statefulSigner

      public StatefulSigner statefulSigner(String alias, LocalStatefulSigningAuthority authority) throws IOException, GeneralSecurityException
      Resolves an explicit durable signing capability for an enrolled key. No missing authority record is created or reconstructed. Each future operation revalidates the current state under both ownership locks.
      Parameters:
      alias - enrolled logical alias
      authority - same independently preserved authority used for generation
      Returns:
      handle borrowing this keyring and authority
      Throws:
      IOException - if enrollment is missing, stale, pending or mismatched
      GeneralSecurityException - if key import/authentication fails
    • putPublic

      public void putPublic(String alias, String algorithmId, PublicKey key) throws IOException, GeneralSecurityException
      Adds or replaces an encoded public key and persists the mutation.
      Parameters:
      alias - logical alias
      algorithmId - canonical registered algorithm identifier
      key - exportable public key
      Throws:
      IOException - if validation or durable persistence fails
      GeneralSecurityException - if entry encryption fails
    • putPrivate

      public void putPrivate(String alias, String algorithmId, PrivateKey key) throws IOException, GeneralSecurityException
      Adds or replaces an encoded private key and persists the mutation.
      Parameters:
      alias - logical alias
      algorithmId - canonical registered algorithm identifier
      key - exportable private key
      Throws:
      IOException - if validation or durable persistence fails
      GeneralSecurityException - if entry encryption fails
    • putSecret

      public void putSecret(String alias, String algorithmId, SecretKey key) throws IOException, GeneralSecurityException
      Adds or replaces an encoded secret key and persists the mutation.
      Parameters:
      alias - logical alias
      algorithmId - canonical registered algorithm identifier
      key - exportable secret key
      Throws:
      IOException - if validation or durable persistence fails
      GeneralSecurityException - if entry encryption fails
    • putKeyPairIfAbsent

      public void putKeyPairIfAbsent(String alias, String algorithmId, KeyPair pair) throws IOException, GeneralSecurityException
      Inserts both canonical key encodings in one durable encrypted snapshot. Both keys and the registered algorithm are validated before reserving any nonce. Validation checks canonical representation, not mathematical pairing. Existing single-component put methods retain their replacement semantics.

      Only nonblank unsuffixed logical aliases are accepted. Any existing entry in the alias's suffix namespace (including incomplete pairs and legacy mixed suffix chains) makes it occupied. No existing entry is replaced. Mutations and close are serialized; readers cannot observe a partial pair. A failed prepublication write leaves no component visible; after uncertain publication this instance refuses further use until reopened. Reopening exposes either the entire pair or neither component.

      Parameters:
      alias - unused logical alias, without .pub or .priv suffix
      algorithmId - canonical registered asymmetric algorithm identifier
      pair - pair with non-null, exportable public and private keys
      Throws:
      IllegalArgumentException - if the alias is null, blank, suffixed or occupied
      NullPointerException - if the pair or either component is null
      IOException - if alias or metadata UTF-8 limits are exceeded (KEYRING_LIMIT_EXCEEDED), validation, nonce reservation or persistence fails; KEYRING_CLOSED after close, KEYRING_DURABILITY_UNCONFIRMED on an uncertain publication (the caller must close and reopen)
      GeneralSecurityException - if canonical validation or encryption fails
    • removeAlias

      public boolean removeAlias(String alias) throws IOException, GeneralSecurityException
      Removes one logical secret alias or all existing components of its pair in one durable snapshot, preserving all other entries and their order. This is logical deletion; old storage blocks and backups are not erased.

      Accepts only nonblank aliases without .pub or .priv suffixes. Legacy suffix chains or conflicting secret/pair entries make the namespace ambiguous and are rejected without mutation. Stateful signing algorithms, including exhausted public-only entries, require a separate authority contract and cannot be removed here. Mutations and close are serialized. On uncertain I/O the instance refuses further use until reopened; reopening exposes either the original alias or its complete removal.

      Parameters:
      alias - unsuffixed logical alias
      Returns:
      true if an alias was durably removed; false if absent, with no write
      Throws:
      IllegalArgumentException - if the alias is null, blank, suffixed or its namespace is ambiguous
      IOException - if the alias UTF-8 limit is exceeded (KEYRING_LIMIT_EXCEEDED), persistence fails, the store is closed (KEYRING_CLOSED), or stateful authority is required (KEYRING_STATEFUL_RECOVERY_REQUIRED); uncertain publication reports KEYRING_DURABILITY_UNCONFIRMED and requires close and reopen
      GeneralSecurityException - if reindexing encryption fails
    • contains

      public boolean contains(String alias)
      Reports whether an alias is present.
      Parameters:
      alias - logical or suffixed alias
      Returns:
      true if a matching entry exists
      Throws:
      IllegalStateException - after close
    • aliases

      public List<String> aliases()
      Returns aliases in their persisted order.
      Returns:
      independent alias list
      Throws:
      IllegalStateException - after close
    • zeroEchoSigningConfigurationTag

      public byte[] zeroEchoSigningConfigurationTag(String workflowId, Duration operationHorizon, String keyRefPrefix, boolean requireComponentSuffix) throws GeneralSecurityException
      Computes the stable ZeroEcho signing-configuration tag for this keyring.

      The tag is HMAC-SHA-256 under a store-specific key derived from the keyring master key and store UUID. Its canonical transcript binds the resolved workflow identifier, operation horizon, key-reference prefix, component-suffix policy, and the ordered identities, metadata, ciphertext lengths, and ciphertext digests of all effective keyring entries. Paths, passwords, salts, nonces, counters, timestamps, plaintext keys, and raw ciphertext are excluded.

      The returned 32-byte array is newly owned by the caller. This method is thread-safe, runs in O(n + b) time for n entries and b bytes of included metadata, and uses O(b) temporary memory. All temporary transcript and derived-key bytes are cleared before return.

      Parameters:
      workflowId - stable resolved workflow identifier; never blank
      operationHorizon - resolved positive operation horizon
      keyRefPrefix - resolved non-blank key-reference prefix
      requireComponentSuffix - resolved component-suffix policy
      Returns:
      newly owned 32-byte signing-configuration tag
      Throws:
      IllegalArgumentException - if an effective configuration value is invalid
      IllegalStateException - if the keyring is closed or poisoned
      GeneralSecurityException - if HMAC-SHA-256 is unavailable
    • getPublicWithId

      public KeyringStore.PublicWithId getPublicWithId(String alias) throws IOException, GeneralSecurityException
      Resolves a public key and its canonical algorithm identifier.
      Parameters:
      alias - logical alias
      Returns:
      imported key and algorithm
      Throws:
      IOException - if the entry cannot be authenticated
      GeneralSecurityException - if decryption or import fails
    • getPrivateWithId

      public KeyringStore.PrivateWithId getPrivateWithId(String alias) throws IOException, GeneralSecurityException
      Resolves a private key and its canonical algorithm identifier.
      Parameters:
      alias - logical alias
      Returns:
      imported key and algorithm
      Throws:
      IOException - if the entry cannot be authenticated
      GeneralSecurityException - if decryption or import fails
    • getSecretWithId

      public KeyringStore.SecretWithId getSecretWithId(String alias) throws IOException, GeneralSecurityException
      Resolves a secret key and its canonical algorithm identifier.
      Parameters:
      alias - logical alias
      Returns:
      imported key and algorithm
      Throws:
      IOException - if the entry cannot be authenticated
      GeneralSecurityException - if decryption or import fails
    • getPublic

      public PublicKey getPublic(String alias) throws IOException, GeneralSecurityException
      Resolves a public key.
      Parameters:
      alias - logical alias
      Returns:
      imported public key
      Throws:
      IOException - if the entry cannot be authenticated
      GeneralSecurityException - if decryption or import fails
    • getPrivate

      public PrivateKey getPrivate(String alias) throws IOException, GeneralSecurityException
      Resolves a private key.
      Parameters:
      alias - logical alias
      Returns:
      imported private key
      Throws:
      IOException - if the entry cannot be authenticated
      GeneralSecurityException - if decryption or import fails
    • getSecret

      public SecretKey getSecret(String alias) throws IOException, GeneralSecurityException
      Resolves a secret key.
      Parameters:
      alias - logical alias
      Returns:
      imported secret key
      Throws:
      IOException - if the entry cannot be authenticated
      GeneralSecurityException - if decryption or import fails
    • destroy

      public void destroy() throws DestroyFailedException
      Clears the master and nonce-reservation MAC keys and releases filesystem ownership.

      Concurrent calls are safe and cleanup occurs exactly once.

      Specified by:
      destroy in interface Destroyable
      Throws:
      DestroyFailedException - if resource release fails
    • isDestroyed

      public boolean isDestroyed()
      Reports whether the keyring has been destroyed.
      Specified by:
      isDestroyed in interface Destroyable
      Returns:
      true after close or destruction
    • close

      public void close()
      Closes the keyring, clearing its master key.
      Specified by:
      close in interface AutoCloseable