UNPKG

@microsoft/useragent-sdk

Version:

SDK for building decentralized identity wallets and enterprise agents.

299 lines 14.7 kB
"use strict"; /*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ Object.defineProperty(exports, "__esModule", { value: true }); const KeyStoreConstants_1 = require("./keystores/KeyStoreConstants"); const IdentifierDocument_1 = require("./IdentifierDocument"); const UserAgentError_1 = require("./UserAgentError"); const ProtectionFormat_1 = require("./crypto/keyStore/ProtectionFormat"); const SubtleCryptoExtension_1 = require("./crypto/plugin/SubtleCryptoExtension"); const JwsToken_1 = require("./crypto/protocols/jose/jws/JwsToken"); const CryptoHelpers_1 = require("./crypto/utilities/CryptoHelpers"); const KeyUseFactory_1 = require("./crypto/keys/KeyUseFactory"); const JweToken_1 = require("./crypto/protocols/jose/jwe/JweToken"); const JoseConstants_1 = require("./crypto/protocols/jose/JoseConstants"); const UserAgentConstants_1 = require("./UserAgentConstants"); const KeyContainer_1 = require("./crypto/keys/KeyContainer"); const JoseHelpers_1 = require("./crypto/protocols/jose/JoseHelpers"); /** * Class for creating and managing identifiers, * retrieving identifier documents. */ class Identifier { /** * Constructs an instance of the Identifier * class using the provided identifier or identifier document. * @param identifier either the string representation of an identifier or a identifier document. * @param [options] for configuring how to register and resolve identifiers. */ constructor(identifier, options) { this.identifier = identifier; // Check whether passed an identifier document // or an identifier string if (typeof identifier === 'object') { this.document = identifier; this.id = identifier.id; } else { this.id = identifier; } this.options = options; } /** * Creates a new decentralized identifier. * @param [options] for configuring how to register and resolve identifiers. */ static async create(options) { const id = options.didPrefix; return new Identifier(id, options).createLinkedIdentifier(id, true); } /** * Creates a new decentralized identifier, using the current identifier * and the specified target. If the registar flag is true, the newly created * identifier will be registered using the * @param target entity for which to create the linked identifier * @param register flag indicating whether the new identifier should be registered * with a ledger. */ async createLinkedIdentifier(target, register = false) { if (this.options && this.options.keyStore) { // Create DID key const cryptoFactory = this.options.cryptoFactory; const signingKeyStorageId = Identifier.keyStorageIdentifier(this.id, target, this.options.cryptoOptions.signingAlgorithm, JoseHelpers_1.default.createUseViaJwa(this.options.cryptoOptions.signingAlgorithm)); const signingPublicKey = await this.generateAndSaveKey(new SubtleCryptoExtension_1.default(cryptoFactory), this.options.cryptoOptions.signingAlgorithm, target, `#${UserAgentConstants_1.default.keyTagSigning}1`, signingKeyStorageId || this.options.cryptoOptions.signingKeyReference); const encryptionKeyStorageId = Identifier.keyStorageIdentifier(this.id, target, this.options.cryptoOptions.encryptionAlgorithm, JoseHelpers_1.default.createUseViaJwa(this.options.cryptoOptions.encryptionAlgorithm)); const encryptionPublicKey = await this.generateAndSaveKey(new SubtleCryptoExtension_1.default(cryptoFactory), this.options.cryptoOptions.encryptionAlgorithm, target, `#${UserAgentConstants_1.default.keyTagEncryption}1`, encryptionKeyStorageId || this.options.cryptoOptions.encryptionKeyReference); // Set key format // todo switch by leveraging pairwiseKey const signingDocumentKey = { id: signingPublicKey.kid, type: this.getDidDocumentKeyType(), publicKeyJwk: signingPublicKey }; const encryptionDocumentKey = { id: encryptionPublicKey.kid, // we need to add RsaEncryptionKey 2018 as type - todo type: 'RsaVerificationKey2018', publicKeyJwk: encryptionPublicKey }; let identifier; if (this.options.registrar) { // add encryptionDocumentKey to register the encryption key const document = await this.createIdentifierDocument(this.id, [signingDocumentKey, encryptionDocumentKey]); if (register) { // register did document identifier = await this.options.registrar.register(document, signingKeyStorageId); document.id = identifier.id; } identifier = new Identifier(document, this.options); // If we create a new identifier save the signing key if (target === this.id) { this.options.cryptoOptions.signingKeyReference = signingKeyStorageId; this.options.cryptoOptions.encryptionKeyReference = encryptionKeyStorageId; } return identifier; } else { throw new UserAgentError_1.default(`No registrar in options to register DID document`); } } throw new UserAgentError_1.default('No keyStore in options'); } /** * Generate a key and save it into the store * @param generator interface * @param algorithm for the key * @param target id of peer */ async generateAndSaveKey(generator, algorithm, target, kid, keyReference) { const alg = CryptoHelpers_1.default.jwaToWebCrypto(algorithm); const jwk = await generator.generatePairwiseKey(alg, KeyStoreConstants_1.default.masterSeed, this.id, target); jwk.kid = kid; jwk.use = JoseHelpers_1.default.createUseViaJwa(algorithm); const pubJwk = jwk.getPublicKey(); pubJwk.kid = jwk.kid; const pairwiseKeyStorageId = keyReference || Identifier.keyStorageIdentifier(this.id, target, algorithm, JoseHelpers_1.default.createUseViaJwa(algorithm)); await this.options.keyStore.save(pairwiseKeyStorageId, new KeyContainer_1.default(jwk)); return pubJwk; } /** * Gets the IdentifierDocument for the identifier * instance, throwing if no identifier has been * created. */ async getDocument() { // If we already have not already // retrieved the document use the // resolver to get the document if (!this.document) { if (!this.options || !this.options.resolver) { throw new UserAgentError_1.default('Resolver not specified in user agent options.'); } // We need to resolve the document this.document = await this.options.resolver.resolve(this); } return this.document; } /** * Performs a public key lookup using the * specified key identifier, returning the * key defined in document. * @param keyIdentifier the identifier of the public key. */ async getPublicKey(keyIdentifier) { if (!this.document) { await this.getDocument(); } // If we have been provided a key identifier use // the identifier to look up a key in the document if (this.document && this.document.publicKeys && keyIdentifier) { const index = this.document.publicKeys.findIndex((key) => key.id === keyIdentifier); // trim down the key Identifier to the unique keyID const keyIdentifierComponents = keyIdentifier.split('#'); const keyId = keyIdentifierComponents[keyIdentifierComponents.length - 1]; const matchingPublicKeys = this.document.publicKeys.filter((PublicKey) => PublicKey.id.endsWith(keyId)); if (matchingPublicKeys.length === 0) { throw new UserAgentError_1.default(`No matching public key found for ${keyIdentifier}`); } return matchingPublicKeys[0]; } else if (this.document && this.document.publicKeys && this.document.publicKeys.length > 0) { // If only one key has been specified in the document // return that return this.document.publicKeys[0]; } throw new UserAgentError_1.default('Document does not contain any public keys'); } /** * Generate a storage identifier to store a key * @param personaId The identifier for the persona * @param target The identifier for the peer. Will be persona for non-pairwise keys * @param algorithm Key algorithm * @param keyType Key type */ static keyStorageIdentifier(personaId, target, algorithm, keyType) { console.log(`${personaId}-${target}-${algorithm}-${keyType}`); return `${personaId}-${target}-${algorithm}-${keyType}`; } // Create an identifier document. Included the public key. async createIdentifierDocument(id, publicKeys) { return IdentifierDocument_1.default.createAndGenerateId(id, publicKeys, this.options); } // Get the did document public key type getDidDocumentKeyType() { // Support other key types return 'Secp256k1VerificationKey2018'; } /** * Sign payload with key specified by keyStorageIdentifier in options.keyStore * @param payload object to be signed * @param keyReference the identifier for the key used to sign payload. */ async sign(payload, keyReference) { let body; if (this.options && this.options.cryptoOptions) { if (this.options.keyStore) { if (typeof (payload) !== 'string') { body = JSON.stringify(payload); } else { body = payload; } const signingOptions = { cryptoFactory: this.options.cryptoFactory }; const jws = new JwsToken_1.default(signingOptions); const signature = await jws.sign(keyReference, Buffer.from(body), ProtectionFormat_1.ProtectionFormat.JwsCompactJson); return signature.serialize(ProtectionFormat_1.ProtectionFormat.JwsCompactJson); ; } else { throw new UserAgentError_1.default('No KeyStore in Options'); } } else { throw new UserAgentError_1.default('No Crypto Options in User Agent Options'); } } /** * Verify the payload with public key from the Identifier Document. * @param jws the signed token to be verified. */ async verify(jws) { if (!this.document) { this.document = await this.getDocument(); } const signingOptions = { cryptoFactory: this.options.cryptoFactory }; const token = JwsToken_1.default.deserialize(jws, signingOptions); if (await token.verify(this.document.getPublicKeysFromDocument(), signingOptions)) { return token.getPayload(); } throw new UserAgentError_1.default(`The signature validation for '${this.id}' failed.`); } /** * Encrypt payload using Public Key registered on Identifier Document. * @param payload object that will be encrypted. * @param encryptionKeys used for the encryption. */ async encrypt(payload) { if (!this.options) { throw new UserAgentError_1.default('Options Undefined'); } // get document if undefined if (!this.document) { this.document = await this.getDocument(); } const keyStore = this.options.keyStore; const cryptoFactory = this.options.cryptoFactory; const options = { cryptoFactory: cryptoFactory, contentEncryptionAlgorithm: JoseConstants_1.default.AesGcm256 }; // create a jweToken with temp cryptoFactory and algorithm. const jweToken = new JweToken_1.default(options); // get any JWK key marked use as 'enc' const publicKey = this.document.getPublicKeysFromDocument().reduce((keyFound, currentKey) => { if (keyFound) { return keyFound; } if (currentKey.use === KeyUseFactory_1.KeyUse.Encryption) { return currentKey; } return undefined; }, undefined); if (!publicKey) { throw new UserAgentError_1.default(`No Public Key found with use equal to 'enc' for ${this.id}`); } // keyIDs retrieved from the DID Document may not be fully quantified if (publicKey.kid && publicKey.kid.indexOf('#') <= 0) { publicKey.kid = `${this.id}${publicKey.kid.indexOf('#') === -1 ? '#' : ''}${publicKey.kid}`; } // encrypt payload using public keys. const encryptedToken = await jweToken.encrypt([publicKey], payload, ProtectionFormat_1.ProtectionFormat.JweCompactJson); // return serialized token. return encryptedToken.serialize(ProtectionFormat_1.ProtectionFormat.JweCompactJson); } /** * Decrypt cipher using key referenced in keystore. * @param cipher cipher to be decrypted. * @param keyReference string that references what key to use from keystore. */ async decrypt(cipher, keyReference) { if (!this.options) { throw new UserAgentError_1.default('Options Undefined'); } const options = { cryptoFactory: this.options.cryptoFactory }; const jweToken = JweToken_1.default.deserialize(cipher.toString(), options); // create jweToken, feed in ciphertext, and decrypt. const payload = await jweToken.decrypt(keyReference); return payload.toString(); } } exports.default = Identifier; //# sourceMappingURL=Identifier.js.map