/** * KEM Key Exchange Service * * Exchanges ML-KEM public keys between agents for quantum-safe vault encryption. * * Key Design: * - Uses existing X25519 channel to bootstrap ML-KEM key exchange * - Seals ML-KEM public keys in HPKE envelopes (RFC 9180) * - Stores peer's ML-KEM keys in connection metadata * - Uses signing protocol's provide-artifacts message (sealed-secret@1) * * Flow: * 1. Agent A generates ML-KEM keypair * 2. Agent A seals their ML-KEM public key using peer's X25519 key * 3. Agent A sends sealed key via provide-artifacts message * 4. Agent B unwraps the ML-KEM key using their X25519 private key * 5. Agent B stores Agent A's ML-KEM key in connection metadata * 6. Agent B can now encrypt vaults to Agent A's ML-KEM key */ import type { AgentContext, Logger } from '@credo-ts/core'; import { ConnectionRepository } from '@credo-ts/core'; import { HPKEService } from './HPKEService'; import { KemKeypairRepository } from '../repository/KemKeypairRepository'; /** * ML-KEM keypair with derived key ID */ export interface KemKeypairWithKid { /** Key identifier (derived from public key hash) */ kid: string; /** ML-KEM public key (1184 bytes for ML-KEM-768) */ publicKey: Uint8Array; /** ML-KEM secret key (2400 bytes for ML-KEM-768) */ secretKey: Uint8Array; } /** * ML-KEM public key info (no secret key) */ export interface KemPublicKeyInfo { /** Key identifier */ kid: string; /** ML-KEM public key */ publicKey: Uint8Array; /** Timestamp when the key was stored */ storedAt?: string; } /** * Sealed artifact containing ML-KEM public key * Encrypted with HPKE to recipient's X25519 key */ export interface SealedKemKeyArtifact { /** Artifact type identifier */ type: 'sealed-ml-kem-key@1'; /** HPKE envelope suite */ envelope: 'envelope-hpke@1'; /** Base64url-encoded ciphertext */ ciphertext: string; /** Base64url-encoded ephemeral public key */ ephemeralPublicKey: string; /** Key ID of the sealed ML-KEM key */ kid: string; } /** * Connection metadata key for ML-KEM keys */ export declare const KEM_KEY_METADATA_KEY = "vaults/kem-key"; /** * Metadata stored in connection for peer's KEM key */ export interface KemKeyMetadata { /** Key identifier */ kid: string; /** Base64url-encoded ML-KEM public key */ publicKey: string; /** When the key was stored */ storedAt: string; /** Optional: when the key exchange session was initiated */ exchangeSessionId?: string; } export declare class KemKeyExchangeService { private logger; private connectionRepository; private hpkeService; private kemKeypairRepository; constructor(logger: Logger, connectionRepository: ConnectionRepository, hpkeService: HPKEService, kemKeypairRepository: KemKeypairRepository); /** * Generate a new ML-KEM-768 keypair * * @returns Keypair with derived key ID */ generateKemKeypair(): KemKeypairWithKid; /** * Create a sealed artifact containing our ML-KEM public key * * This creates an artifact suitable for sending via provide-artifacts message. * The ML-KEM public key is sealed using HPKE (RFC 9180) to the recipient's P-256 public key. * * @param keypair - Our ML-KEM keypair * @param recipientPublicKey - Recipient's P-256 public key for HPKE encryption * @returns Sealed artifact ready for transmission */ createKemKeyArtifact(keypair: KemKeypairWithKid, recipientPublicKey: Uint8Array): Promise; /** * Unwrap a sealed KEM key artifact * * Decrypts the sealed artifact using HPKE to extract the peer's ML-KEM public key. * * @param artifact - Sealed artifact received from peer * @param recipientSecretKey - Our P-256 private key to decrypt * @returns Peer's ML-KEM public key info */ unwrapKemKeyArtifact(artifact: SealedKemKeyArtifact, recipientSecretKey: Uint8Array): Promise; /** * Store peer's ML-KEM public key in connection metadata * * This allows retrieving the peer's KEM key later for vault encryption. * * @param agentContext - Agent context * @param connectionId - Connection record ID * @param keyInfo - Peer's ML-KEM public key info */ storePeerKemKey(agentContext: AgentContext, connectionId: string, keyInfo: KemPublicKeyInfo): Promise; /** * Retrieve peer's ML-KEM public key from connection metadata * * @param agentContext - Agent context * @param connectionId - Connection record ID * @returns Peer's KEM key info, or null if not found */ getPeerKemKey(agentContext: AgentContext, connectionId: string): Promise; /** * Delete peer's ML-KEM public key from connection metadata * * @param agentContext - Agent context * @param connectionId - Connection record ID */ deletePeerKemKey(agentContext: AgentContext, connectionId: string): Promise; /** * Check if a connection has a peer KEM key * * @param agentContext - Agent context * @param connectionId - Connection record ID * @returns True if peer has KEM key stored */ hasPeerKemKey(agentContext: AgentContext, connectionId: string): Promise; /** * Store a local KEM keypair (including secret key) for a connection * * Call this after generateKemKeypair() to persist the keypair so it can * be retrieved later for vault decryption. * * @param agentContext - Agent context * @param connectionId - Connection to associate the keypair with * @param keypair - Full ML-KEM keypair */ storeLocalKeypair(agentContext: AgentContext, connectionId: string, keypair: KemKeypairWithKid): Promise; /** * Get the local KEM keypair for a connection * * @param agentContext - Agent context * @param connectionId - Connection record ID * @returns Full keypair including secret key, or null if not found */ getLocalKeypair(agentContext: AgentContext, connectionId: string): Promise; /** * Find a local keypair by its key identifier (kid) * * This is the primary lookup used during vault decryption: extract recipient * kids from vault header, then find which local keypair matches. * * @param agentContext - Agent context * @param kid - Key identifier to search for * @returns Keypair and associated connectionId, or null */ findKeypairByKid(agentContext: AgentContext, kid: string): Promise<{ keypair: KemKeypairWithKid; connectionId: string; } | null>; /** * Find a local keypair matching any of the given recipient kids * * Used to find which local key can decrypt a vault when the vault header * lists multiple possible recipients. * * @param agentContext - Agent context * @param kids - Set of key identifiers from vault recipients * @returns Matching keypair and connectionId, or null */ findKeypairByRecipientKids(agentContext: AgentContext, kids: Set): Promise<{ keypair: KemKeypairWithKid; connectionId: string; } | null>; /** * Check if a local keypair exists for a connection * * @param agentContext - Agent context * @param connectionId - Connection record ID * @returns True if local keypair exists */ hasLocalKeypair(agentContext: AgentContext, connectionId: string): Promise; /** * Delete the local keypair for a connection * * @param agentContext - Agent context * @param connectionId - Connection record ID */ deleteLocalKeypair(agentContext: AgentContext, connectionId: string): Promise; }