UNPKG

@dwn-protocol/id-sdk

Version:

SDK for accessing the features and capabilities

615 lines 26.6 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); __setModuleDefault(result, mod); return result; }; var __rest = (this && this.__rest) || function (s, e) { var t = {}; for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0) t[p] = s[p]; if (s != null && typeof Object.getOwnPropertySymbols === "function") for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) { if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i])) t[p[i]] = s[p[i]]; } return t; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.PrivateKeyStoreMemory = exports.PrivateKeyStoreDwn = exports.KeyStoreMemory = exports.KeyStoreDwn = void 0; const cryptoUtils = __importStar(require("../crypto/utils.js")); const index_js_1 = require("../common/index.js"); const utils_js_1 = require("./utils.js"); /** * An implementation of `ManagedKeyStore` that stores key metadata and * public key material in a DWN. * * An instance of this class can be used by `KeyManager` or * an implementation of `KeyManagementSystem`. */ class KeyStoreDwn { constructor(options) { this._keyRecordProperties = { dataFormat: 'application/json', schema: 'https://identity.foundation/schemas/dwn/managed-key' }; const { schema } = options !== null && options !== void 0 ? options : {}; if (schema) { this._keyRecordProperties.schema = schema; } } async deleteKey(options) { var _a; const { agent, context, id } = options; // Determine which DID to use to author DWN messages. const authorDid = await this.getAuthor({ agent, context }); // Query the DWN for all stored key objects. const { reply: queryReply } = await this.getKeyRecords(agent, context); // Loop through all of the entries and try to find a match. let matchingRecordId; for (const record of (_a = queryReply.entries) !== null && _a !== void 0 ? _a : []) { if (record.encodedData) { const storedKey = this.decodeKey(record.encodedData); const storedKeyId = (0, utils_js_1.isManagedKeyPair)(storedKey) ? storedKey.publicKey.id : storedKey.id; if (storedKey && storedKeyId === id) { matchingRecordId = record.recordId; break; } } } // Return undefined if the specified key was not found in the store. if (!matchingRecordId) return false; // If a record for the specified key was found, attempt to delete it. const { reply: { status } } = await agent.dwnManager.processRequest({ author: authorDid, target: authorDid, messageType: 'RecordsDelete', messageOptions: { recordId: matchingRecordId } }); // If the key was successfully deleted, return true; if (status.code === 202) return true; // If the key could not be deleted, return false; return false; } async findKey(options) { var _a; const { agent, alias, context, id } = options; // Query the DWN for all stored managed key objects. const { reply: queryReply } = await this.getKeyRecords(agent, context); // Loop through all of the entries and return a match, if found. for (const record of (_a = queryReply.entries) !== null && _a !== void 0 ? _a : []) { if (record.encodedData) { const storedKey = this.decodeKey(record.encodedData); if ((0, utils_js_1.isManagedKeyPair)(storedKey)) { if (storedKey.publicKey.id === id) return storedKey; if (storedKey.publicKey.alias === alias) return storedKey; } else { if (storedKey.id === id) return storedKey; if (storedKey.alias === alias) return storedKey; } } } // Return undefined if no matches were found. return undefined; } async getKey(options) { var _a; const { agent, context, id } = options; // Query the DWN for all stored managed key objects. const { reply: queryReply } = await this.getKeyRecords(agent, context); // Loop through all of the entries and return a match, if found. for (const record of (_a = queryReply.entries) !== null && _a !== void 0 ? _a : []) { if (record.encodedData) { const storedKey = this.decodeKey(record.encodedData); const storedKeyId = (0, utils_js_1.isManagedKeyPair)(storedKey) ? storedKey.publicKey.id : storedKey.id; if (storedKeyId === id) return storedKey; } } // Return undefined if no matches were found. return undefined; } async importKey(options) { const { agent, context, key } = options; let keyId; if ((0, utils_js_1.isManagedKeyPair)(key)) { keyId = key.publicKey.id; } else { // If an ID wasn't specified, generate one. if (!key.id) { key.id = cryptoUtils.randomUuid(); } keyId = key.id; } // Determine which DID to use to author DWN messages. const authorDid = await this.getAuthor({ agent, context }); // Check if the key being imported is already present in the store. const duplicateFound = await this.getKey({ agent, context, id: keyId }); if (duplicateFound) { throw new Error(`KeyStoreDwn: Key with ID already exists: '${keyId}'`); } // Encode the managed key or key pair as bytes. const encodedKey = this.encodeKey(key); const { reply: { status } } = await agent.dwnManager.processRequest({ author: authorDid, target: authorDid, messageType: 'RecordsWrite', messageOptions: Object.assign({}, this._keyRecordProperties), dataStream: new Blob([encodedKey]) }); // If the write fails, throw an error. if (status.code !== 202) { throw new Error('DidStoreDwn: Failed to write imported DID to store.'); } return keyId; } async listKeys(options) { var _a; const { agent, context } = options; // Query the DWN for all stored managed key objects. const { reply: queryReply } = await this.getKeyRecords(agent, context); // Loop through all of the entries and accumulate the key objects. let storedKeys = []; for (const record of (_a = queryReply.entries) !== null && _a !== void 0 ? _a : []) { if (record.encodedData) { const storedKey = this.decodeKey(record.encodedData); storedKeys.push(storedKey); } } return storedKeys; } async updateKey(options) { var _a; const { agent, context, id } = options; const propertyUpdates = { alias: options.alias, metadata: options.metadata }; // Determine which DID to use to author DWN messages. const authorDid = await this.getAuthor({ agent, context }); // Query the DWN for all stored managed key objects. const { reply: queryReply } = await this.getKeyRecords(agent, context); // Confirm the key being updated is already present in the store. let keyToUpdate; let recordToUpdate; for (const entry of (_a = queryReply.entries) !== null && _a !== void 0 ? _a : []) { const { encodedData } = entry, record = __rest(entry, ["encodedData"]); if (encodedData) { const storedKey = this.decodeKey(encodedData); const storedKeyId = (0, utils_js_1.isManagedKeyPair)(storedKey) ? storedKey.publicKey.id : storedKey.id; if (storedKey && storedKeyId === id) { keyToUpdate = storedKey; recordToUpdate = record; break; } } } // Key with given ID not present so update operation cannot proceed. if (!recordToUpdate || !keyToUpdate) return false; // Make a deep copy of the update properties to ensure all nested objects do not share references. (0, index_js_1.removeUndefinedProperties)(propertyUpdates); (0, index_js_1.removeEmptyObjects)(propertyUpdates); const clonedUpdates = structuredClone(propertyUpdates); // Update the given properties of the key. if ((0, utils_js_1.isManagedKeyPair)(keyToUpdate)) { keyToUpdate.privateKey = Object.assign(Object.assign({}, keyToUpdate.privateKey), clonedUpdates); keyToUpdate.publicKey = Object.assign(Object.assign({}, keyToUpdate.publicKey), clonedUpdates); } else { keyToUpdate = Object.assign(Object.assign({}, keyToUpdate), clonedUpdates); } // Encode the updated key or key pair as bytes. const updatedKeyBytes = this.encodeKey(keyToUpdate); // Assemble the update messsage, including record ID and context ID, if any. let messageOptions = Object.assign({}, recordToUpdate.descriptor); messageOptions.contextId = recordToUpdate.contextId; messageOptions.recordId = recordToUpdate.recordId; /** Remove properties from the update messageOptions to let the DWN SDK * auto-fill. Otherwisse, you will get 409 Conflict errors. */ delete messageOptions.dataCid; delete messageOptions.dataSize; delete messageOptions.data; delete messageOptions.messageTimestamp; // Overwrite the entry in the store with the updated object. const { reply: { status } } = await agent.dwnManager.processRequest({ author: authorDid, target: authorDid, messageType: 'RecordsWrite', messageOptions, dataStream: new Blob([updatedKeyBytes]) }); // If the write fails, throw an error. if (status.code !== 202) { throw new Error('DidStoreDwn: Failed to write updated key to store.'); } return true; } decodeKey(keyEncodedData) { const encodedKey = index_js_1.Convert.base64Url(keyEncodedData).toObject(); if ('publicKey' in encodedKey) { const privateKeyMaterial = encodedKey.privateKey.material ? index_js_1.Convert.base64Url(encodedKey.privateKey.material).toUint8Array() : undefined; const publicKeyMaterial = encodedKey.publicKey.material ? index_js_1.Convert.base64Url(encodedKey.publicKey.material).toUint8Array() : undefined; const managedKeyPair = { privateKey: Object.assign(Object.assign({}, encodedKey.privateKey), { material: privateKeyMaterial }), publicKey: Object.assign(Object.assign({}, encodedKey.publicKey), { material: publicKeyMaterial }) }; return managedKeyPair; } else { const material = encodedKey.material ? index_js_1.Convert.base64Url(encodedKey.material).toUint8Array() : undefined; const managedKey = Object.assign(Object.assign({}, encodedKey), { material }); return managedKey; } } encodeKey(managedKey) { let encodedKey; if ((0, utils_js_1.isManagedKeyPair)(managedKey)) { const privateKeyMaterial = managedKey.privateKey.material ? index_js_1.Convert.uint8Array(managedKey.privateKey.material).toBase64Url() : undefined; const publicKeyMaterial = managedKey.publicKey.material ? index_js_1.Convert.uint8Array(managedKey.publicKey.material).toBase64Url() : undefined; encodedKey = { privateKey: Object.assign(Object.assign({}, managedKey.privateKey), { material: privateKeyMaterial }), publicKey: Object.assign(Object.assign({}, managedKey.publicKey), { material: publicKeyMaterial }) }; } else { const material = managedKey.material ? index_js_1.Convert.uint8Array(managedKey.material).toBase64Url() : undefined; encodedKey = Object.assign(Object.assign({}, managedKey), { material }); } const keyBytes = index_js_1.Convert.object(encodedKey).toUint8Array(); return keyBytes; } async getAuthor(options) { const { agent, context } = options; // If `context` is specified, DWN messages will be signed by this DID. if (context) return context; // If Agent has an agentDid, use it to sign DWN messages. if (agent.agentDid) return agent.agentDid; // If `context` and `agent.agentDid`are undefined, throw error. throw new Error(`KeyStoreDwn: Agent property 'agentDid' is undefined and no context was specified.`); } async getKeyRecords(agent, context) { // Determine which DID to use to author DWN messages. const authorDid = await this.getAuthor({ agent, context }); const dwnResponse = await agent.dwnManager.processRequest({ author: authorDid, target: authorDid, messageType: 'RecordsQuery', messageOptions: { filter: Object.assign({}, this._keyRecordProperties) } }); return dwnResponse; } } exports.KeyStoreDwn = KeyStoreDwn; /** * An implementation of `ManagedKeyStore` that stores key metadata and * public key material in memory. * * An instance of this class can be used by `KeyManager` or * an implementation of `KeyManagementSystem`. */ class KeyStoreMemory { constructor() { /** * A private field that contains the Map used as the in-memory key-value store. */ this.store = new Map(); } async deleteKey({ id }) { if (this.store.has(id)) { // Key with given ID exists so proceed with delete. this.store.delete(id); return true; } // Key with given ID not present so delete operation not possible. return false; } async findKey(options) { let { alias, id } = options; // Get key by ID. if (id) return this.store.get(id); if (alias) { // Search through the store to find a matching entry. for (const key of await this.listKeys()) { if ('alias' in key && key.alias === alias) return key; if ('publicKey' in key && key.publicKey.alias === alias) return key; } } return undefined; } async getKey({ id }) { return this.store.get(id); } async importKey({ key }) { let id; if ((0, utils_js_1.isManagedKeyPair)(key)) { id = key.publicKey.id; } else { // If an ID wasn't specified, generate one. if (!key.id) { key.id = cryptoUtils.randomUuid(); } id = key.id; } if (this.store.has(id)) { // Key with given ID already exists so import operation cannot proceed. throw new Error(`KeyStoreMemory: Key with ID already exists: '${id}'`); } // Make a deep copy of the key so that the object stored does not share the same references as the input key. const clonedKey = structuredClone(key); this.store.set(id, clonedKey); return id; } async listKeys() { return Array.from(this.store.values()); } async updateKey(options) { const id = options.id; const propertyUpdates = { alias: options.alias, metadata: options.metadata }; const keyExists = this.store.has(id); if (!keyExists) { // Key with given ID not present so update operation cannot proceed. return false; } // Retrieve the current value of the key from the store. let key = await this.getKey({ id }); // Make a deep copy of the update properties to ensure all nested objects do not share references. (0, index_js_1.removeUndefinedProperties)(propertyUpdates); (0, index_js_1.removeEmptyObjects)(propertyUpdates); const clonedUpdates = structuredClone(propertyUpdates); // Update the given properties of the key. if ((0, utils_js_1.isManagedKeyPair)(key)) { key.privateKey = Object.assign(Object.assign({}, key.privateKey), clonedUpdates); key.publicKey = Object.assign(Object.assign({}, key.publicKey), clonedUpdates); } else { key = Object.assign(Object.assign(Object.assign({}, key), clonedUpdates), { id: key.id }); } // Overwrite the entry in the store with the updated object. this.store.set(id, key); return true; } } exports.KeyStoreMemory = KeyStoreMemory; /** * An implementation of `ManagedKeyStore` that stores private key * material in a DWN. * * An instance of this class can be used by an implementation of * `KeyManagementSystem`. */ class PrivateKeyStoreDwn { constructor() { this._keyRecordProperties = { dataFormat: 'application/json', schema: 'https://identity.foundation/schemas/dwn/kms-private-key' }; } async deleteKey(options) { var _a; const { agent, context, id } = options; // Determine which DID to use to author DWN messages. const authorDid = await this.getAuthor({ agent, context }); // Query the DWN for all stored key objects. const { reply: queryReply } = await this.getKeyRecords(agent, context); // Loop through all of the entries and try to find a match. let matchingRecordId; for (const record of (_a = queryReply.entries) !== null && _a !== void 0 ? _a : []) { if (record.encodedData) { const storedKey = this.decodeKey(record.encodedData); if (storedKey && storedKey.id === id) { matchingRecordId = record.recordId; break; } } } // Return undefined if the specified key was not found in the store. if (!matchingRecordId) return false; // If a record for the specified key was found, attempt to delete it. const { reply: { status } } = await agent.dwnManager.processRequest({ author: authorDid, target: authorDid, messageType: 'RecordsDelete', messageOptions: { recordId: matchingRecordId } }); // If the key was successfully deleted, return true; if (status.code === 202) return true; // If the key could not be deleted, return false; return false; } async findKey() { throw new Error(`PrivateKeyStoreDwn: Method not implemented: 'findKey'`); } async getKey(options) { var _a; const { agent, context, id } = options; // Query the DWN for all stored key objects. const { reply: queryReply } = await this.getKeyRecords(agent, context); // Loop through all of the entries and return a match, if found. for (const record of (_a = queryReply.entries) !== null && _a !== void 0 ? _a : []) { if (record.encodedData) { const storedKey = this.decodeKey(record.encodedData); if (storedKey.id === id) return storedKey; } } // Return undefined if no matches were found. return undefined; } async importKey(options) { const { agent, context, key } = options; if (!key.material) throw new TypeError(`Required parameter missing: 'material'`); if (!key.type) throw new TypeError(`Required parameter missing: 'type'`); // Determine which DID to use to author DWN messages. const authorDid = await this.getAuthor({ agent, context }); // Encode the managed key or key pair as bytes. const id = cryptoUtils.randomUuid(); // Generate a random ID. const encodedPrivateKey = this.encodeKey(Object.assign(Object.assign({}, key), { id })); const { reply: { status } } = await agent.dwnManager.processRequest({ author: authorDid, target: authorDid, messageType: 'RecordsWrite', messageOptions: Object.assign({}, this._keyRecordProperties), dataStream: new Blob([encodedPrivateKey]) }); // If the write fails, throw an error. if (status.code !== 202) { throw new Error('PrivateKeyStoreDwn: Failed to write imported DID to store.'); } return id; } async listKeys(options) { var _a; const { agent, context } = options; // Query the DWN for all stored key objects. const { reply: queryReply } = await this.getKeyRecords(agent, context); // Loop through all of the entries and accumulate the key objects. let storedKeys = []; for (const record of (_a = queryReply.entries) !== null && _a !== void 0 ? _a : []) { if (record.encodedData) { const storedKey = this.decodeKey(record.encodedData); storedKeys.push(storedKey); } } return storedKeys; } async updateKey() { throw new Error(`PrivateKeyStoreMemory: Method not implemented: 'updateKey'`); } decodeKey(keyEncodedData) { const encodedKey = index_js_1.Convert.base64Url(keyEncodedData).toObject(); const privateKey = Object.assign(Object.assign({}, encodedKey), { material: index_js_1.Convert.base64Url(encodedKey.material).toUint8Array() }); return privateKey; } encodeKey(privateKey) { const encodedKey = Object.assign(Object.assign({}, privateKey), { material: index_js_1.Convert.uint8Array(privateKey.material).toBase64Url() }); const keyBytes = index_js_1.Convert.object(encodedKey).toUint8Array(); return keyBytes; } async getAuthor(options) { const { agent, context } = options; // If `context` is specified, DWN messages will be signed by this DID. if (context) return context; // If Agent has an agentDid, use it to sign DWN messages. if (agent.agentDid) return agent.agentDid; // If `context` and `agent.agentDid`are undefined, throw error. throw new Error(`PrivateKeyStoreDwn: Agent property 'agentDid' is undefined and no context was specified.`); } async getKeyRecords(agent, context) { // Determine which DID to use to author DWN messages. const authorDid = await this.getAuthor({ agent, context }); const dwnResponse = await agent.dwnManager.processRequest({ author: authorDid, target: authorDid, messageType: 'RecordsQuery', messageOptions: { filter: Object.assign({}, this._keyRecordProperties) } }); return dwnResponse; } } exports.PrivateKeyStoreDwn = PrivateKeyStoreDwn; /** * An implementation of `ManagedKeyStore` that stores private key * material in memory. * * An instance of this class can be used by an implementation of * `KeyManagementSystem`. */ class PrivateKeyStoreMemory { constructor() { /** * A private field that contains the Map used as the in-memory key-value store. */ this.store = new Map(); } async deleteKey({ id }) { if (this.store.has(id)) { // Key with given ID exists so proceed with delete. this.store.delete(id); return true; } // Key with given ID not present so delete operation not possible. return false; } async findKey() { throw new Error(`PrivateKeyStoreMemory: Method not implemented: 'findKey'`); } async getKey({ id }) { return this.store.get(id); } async importKey({ key }) { if (!key.material) throw new TypeError(`Required parameter missing: 'material'`); if (!key.type) throw new TypeError(`Required parameter missing: 'type'`); // Make a deep copy of the key so that the object stored does not share the same references as the input key. // The private key material is transferred to the new object, making the original obj.material unusable. const clonedKey = structuredClone(key, { transfer: [key.material.buffer] }); clonedKey.id = cryptoUtils.randomUuid(); this.store.set(clonedKey.id, clonedKey); return clonedKey.id; } async listKeys() { return Array.from(this.store.values()); } async updateKey() { throw new Error(`PrivateKeyStoreMemory: Method not implemented: 'updateKey'`); } } exports.PrivateKeyStoreMemory = PrivateKeyStoreMemory; //# sourceMappingURL=store-managed-key.js.map