UNPKG

@dwn-protocol/id-sdk

Version:

SDK for accessing the features and capabilities

302 lines (230 loc) 9.46 kB
import type { ManagedKey, PortableKey, SignOptions, CryptoManager, VerifyOptions, DecryptOptions, EncryptOptions, ManagedKeyPair, GenerateKeyType, ManagedKeyStore, ImportKeyOptions, UpdateKeyOptions, DeriveBitsOptions, PortableKeyPair, GenerateKeyOptions, KeyManagementSystem, GenerateKeyOptionTypes, } from './types/managed-key.js'; import { IDManagedAgent } from './types/agent.js'; import { LocalKms } from './kms-local.js'; import { isManagedKey, isManagedKeyPair } from './utils.js'; import { KeyStoreMemory, PrivateKeyStoreMemory } from './store-managed-key.js'; export type KmsMap = { [name: string]: KeyManagementSystem; } export type KeyManagerOptions = { agent?: IDManagedAgent; kms?: KmsMap; store?: ManagedKeyStore<string, ManagedKey | ManagedKeyPair>; } /** * KeyManager * * This class orchestrates implementations of {@link KeyManagementSystem}, * using a ManagedKeyStore to remember the link between a key reference, * its metadata, and the respective key management system that provides the * actual cryptographic capabilities. * * The methods of this class are used automatically by other Agent * components to perform their required cryptographic operations using * the managed keys. * * @public */ export class KeyManager implements CryptoManager { /** * Holds the instance of a `IDManagedAgent` that represents the current * execution context for the `KeyManager`. This agent is utilized * to interact with other agent components. It's vital * to ensure this instance is set to correctly contextualize * operations within the broader agent framework. */ private _agent?: IDManagedAgent; // ManagedKey to use for signing DWN messages with DWN-backed store. private _defaultSigningKey?: ManagedKeyPair; // KMS name to KeyManagementSystem mapping. private _kms: Map<string, KeyManagementSystem>; // Store for managed key metadata. private _store: ManagedKeyStore<string, ManagedKey | ManagedKeyPair>; constructor(options?: KeyManagerOptions) { let { agent, kms, store } = options ?? { }; this._agent = agent; this._store = store ?? new KeyStoreMemory(); kms ??= this.useMemoryKms(); this._kms = new Map(Object.entries(kms)) ; } /** * Retrieves the `IDManagedAgent` execution context. * If the `agent` instance proprety is undefined, it will throw an error. * * @returns The `IDManagedAgent` instance that represents the current execution * context. * * @throws Will throw an error if the `agent` instance property is undefined. */ get agent(): IDManagedAgent { if (this._agent === undefined) { throw new Error('KeyManager: Unable to determine agent execution context.'); } return this._agent; } set agent(agent: IDManagedAgent) { this._agent = agent; this._kms.forEach((kms) => { kms.agent = agent; }); } async decrypt(options: DecryptOptions): Promise<Uint8Array> { let { keyRef, ...decryptOptions } = options; const key = await this.getKey({ keyRef }); if (!isManagedKey(key)) { throw new Error(`Key not found: '${keyRef}'`); } const kmsName = key.kms; const kms = this.getKms(kmsName); const keyId = key.id; const plaintext = await kms.decrypt({ keyRef: keyId, ...decryptOptions }); return plaintext; } async deriveBits(options: DeriveBitsOptions): Promise<Uint8Array> { const { baseKeyRef, ...deriveBitsOptions } = options; const ownKeyPair = await this.getKey({ keyRef: baseKeyRef }); if (!isManagedKeyPair(ownKeyPair)) { throw new Error(`Key not found: '${baseKeyRef}'`); } const kmsName = ownKeyPair.privateKey.kms; const kms = this.getKms(kmsName); const ownKeyId = ownKeyPair.privateKey.id; const sharedSecret = kms.deriveBits({ baseKeyRef: ownKeyId, ...deriveBitsOptions }); return sharedSecret; } async encrypt(options: EncryptOptions): Promise<Uint8Array> { let { keyRef, ...encryptOptions } = options; const key = await this.getKey({ keyRef }); if (!isManagedKey(key)) { throw new Error(`Key not found: '${keyRef}'`); } const kmsName = key.kms; const kms = this.getKms(kmsName); const keyId = key.id; const ciphertext = await kms.encrypt({ keyRef: keyId, ...encryptOptions }); return ciphertext; } async generateKey<T extends GenerateKeyOptionTypes>(options: GenerateKeyOptions<T> & { kms?: string }): Promise<GenerateKeyType<T>> { const { kms: kmsName, ...generateKeyOptions } = options; const kms = this.getKms(kmsName); const keyOrKeyPair = await kms.generateKey(generateKeyOptions); // Store the ManagedKey or ManagedKeyPair in KeyManager's key store. await this._store.importKey({ key: keyOrKeyPair, agent: this.agent }); return keyOrKeyPair; } async getKey({ keyRef }: { keyRef: string }): Promise<ManagedKey | ManagedKeyPair | undefined> { let keyOrKeyPair: ManagedKey | ManagedKeyPair | undefined; // First, check to see if the requested key is the default signing key. const defaultSigningKeyId = this._defaultSigningKey?.publicKey.id; const defaultSigningKeyAlias = this._defaultSigningKey?.publicKey.alias; if (keyRef === defaultSigningKeyId || keyRef === defaultSigningKeyAlias) { return this._defaultSigningKey; } // Try to get key by ID. keyOrKeyPair = await this._store.getKey({ id: keyRef, agent: this.agent }); if (keyOrKeyPair) return keyOrKeyPair; // Try to find key by alias. keyOrKeyPair = await this._store.findKey({ alias: keyRef, agent: this.agent }); if (keyOrKeyPair) return keyOrKeyPair; return undefined; } async importKey(options: PortableKeyPair): Promise<ManagedKeyPair>; async importKey(options: PortableKey): Promise<ManagedKey>; async importKey(options: ImportKeyOptions): Promise<ManagedKey | ManagedKeyPair> { const kmsName = ('privateKey' in options) ? options.privateKey.kms : options.kms; const kms = this.getKms(kmsName); // Store the ManagedKey or ManagedKeyPair in the given KMS. const importedKeyOrKeyPair = await kms.importKey(options); // Store the ManagedKey or ManagedKeyPair in KeyManager's key store. await this._store.importKey({ key: importedKeyOrKeyPair, agent: this.agent }); return importedKeyOrKeyPair; } listKms() { return Array.from(this._kms.keys()); } async setDefaultSigningKey({ key }: { key: PortableKeyPair }) { const kmsName = key.privateKey.kms; const kms = this.getKms(kmsName); // Store the default signing key pair in an in-memory KMS. const importedDefaultSigningKey = await kms.importKey(key); // Set the in-memory key to be KeyManager's default signing key. this._defaultSigningKey = importedDefaultSigningKey; } async sign(options: SignOptions): Promise<Uint8Array> { const { keyRef, ...signOptions } = options; const keyPair = await this.getKey({ keyRef }); if (!isManagedKeyPair(keyPair)) { throw new Error(`Key not found: '${keyRef}'`); } const kmsName = keyPair.privateKey.kms; const kms = this.getKms(kmsName); const keyId = keyPair.privateKey.id; const signature = await kms.sign({ keyRef: keyId, ...signOptions }); return signature; } async updateKey(options: UpdateKeyOptions): Promise<boolean> { const { keyRef, alias, metadata } = options; const keyOrKeyPair = await this.getKey({ keyRef }); if (!keyOrKeyPair) { throw new Error(`Key not found: '${keyRef}'`); } const { id: keyId, kms: kmsName } = (isManagedKeyPair(keyOrKeyPair)) ? { ...keyOrKeyPair.publicKey } : { ...keyOrKeyPair }; // Update the ManagedKey or ManagedKeyPair in the given KMS. const kms = this.getKms(kmsName); const kmsUpdated = await kms.updateKey(options); if (!kmsUpdated) return false; // Since the KMS was successfully updated, update the KeyManager store. return await this._store.updateKey({ id: keyId, alias, metadata, agent: this.agent }); } async verify(options: VerifyOptions): Promise<boolean> { let { keyRef, ...verifyOptions } = options; const keyPair = await this.getKey({ keyRef }); if (!isManagedKeyPair(keyPair)) { throw new Error(`Key not found: '${keyRef}'`); } const kmsName = keyPair.publicKey.kms; const kms = this.getKms(kmsName); const keyId = keyPair.publicKey.id; const isValid = await kms.verify({ keyRef: keyId, ...verifyOptions }); return isValid; } private getKms(name: string | undefined): KeyManagementSystem { // For developer convenience, if a KMS name isn't specified and KeyManager only has // one KMS defined, use it. Otherwise, an exception will be thrown. name ??= (this._kms.size === 1) ? this._kms.keys().next().value : ''; const kms = this._kms.get(name!); if (!kms) { throw Error(`Unknown key management system: '${name}'`); } return kms; } private useMemoryKms(): KmsMap { // Instantiate in-memory store for KMS key metadata and public keys. const keyStore = new KeyStoreMemory(); // Instantiate in-memory store for KMS private keys. const privateKeyStore = new PrivateKeyStoreMemory(); // Instantiate local KMS using in-memory key stores. const kms = new LocalKms({ kmsName: 'memory', keyStore, privateKeyStore }); return { memory: kms }; } }