@microsoft/useragent-sdk
Version:
SDK for building decentralized identity wallets and enterprise agents.
299 lines • 14.7 kB
JavaScript
"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