UNPKG

@microsoft/useragent-sdk

Version:

SDK for building decentralized identity wallets and enterprise agents.

212 lines 10.4 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 Identifier_1 = require("../Identifier"); const UserAgentError_1 = require("../UserAgentError"); const ClaimObject_1 = require("../credentials/ClaimObject"); const UserAgentOptions_1 = require("../UserAgentOptions"); const HttpResolver_1 = require("../resolvers/HttpResolver"); const CryptoOptions_1 = require("../CryptoOptions"); const JwsToken_1 = require("../crypto/protocols/jose/jws/JwsToken"); const PublicKey_1 = require("../crypto/keys/PublicKey"); const node_fetch_1 = require("node-fetch"); /** * Standard iss for SIOP Response. */ const iss = 'https://self-issued.me'; class OidcResponse { /** * Private Constructor for creating OidcResponse Object. * @param {Identifier} sender Identifier who is sending response. * @param {string} nonce Nonce from request. * @param {string} redirectUrl RedirectURL from request. * @param {OptionalOIDCResponseParams} options optional parameters for specific responses. */ constructor(sender, nonce, redirectUrl, options) { this.redirectUrl = redirectUrl; this.nonce = nonce; this.sender = sender; this.state = options.state; this.presentedClaimObjects = options.presentedClaimObjects; } /** * Create an OIDC Response from an OIDC Request Object. * @param {OidcRequest} oidcRequest OIDC Request that response is responding to. * @param {Identifier} sender The Sender Identifier of the OIDC Response. * @returns {OidcResponse} */ static async create(oidcRequest, sender) { if (!oidcRequest.state) { return new OidcResponse(sender, oidcRequest.nonce, oidcRequest.redirectUrl); } return new OidcResponse(sender, oidcRequest.nonce, oidcRequest.redirectUrl, { state: oidcRequest.state }); } /** * sign oidcRequest and send compact JWT to redirectUrl. * @param {string} keyReference reference to signing key. * @param {ClaimObject[]} claimObjects any claims that were asked for. * @returns the body of the httpResponse if status is 200. */ async signAndSend(keyReference, claimObjects) { // check if sender has keystore if (!this.sender.options || !this.sender.options.keyStore) { throw new UserAgentError_1.default(`No KeyStore specified for '${this.sender.id}`); } // get public key of key referenced by keyReference in keystore const publicKey = (await this.sender.options.keyStore.get(keyReference, true)).getKey(); // milliseconds to seconds const milliseconds = 1000; const expirationTimeOffsetInMinutes = 5; const expiration = new Date(Date.now() - milliseconds * 60 * expirationTimeOffsetInMinutes); // 5 minutes from now const exp = Math.floor(expiration.getTime() / milliseconds); const iat = Math.floor(Date.now() / milliseconds); // ms to seconds const response = { iss, sub: await PublicKey_1.default.getThumbprint(publicKey), aud: this.redirectUrl, nonce: this.nonce, did: this.sender.id, sub_jwk: publicKey, iat, exp }; const claimSources = {}; const claimNames = {}; let index = 1; // if state exists, add it to response. if (this.state) { Object.assign(response, { state: this.state }); } // for presentation of claims. if (claimObjects) { const claim = claimObjects[0]; const signedPresentationDetails = await this.sender.sign(claim.serialize(), keyReference); const src = `src${index}`; claimNames[claim.claimClass] = src; claimSources[src] = [{ 'JWT': signedPresentationDetails }]; index++; Object.assign(response, { '_claim_names': claimNames }); Object.assign(response, { '_claim_sources': claimSources }); } const signedResponse = await this.sender.sign(response, keyReference); return this.sendResponse(signedResponse, this.redirectUrl, this.state); } /** * Parses and Verifies signed JWT. * @param {string} signedResponse signed JWT containing OIDC response. * @returns {OidcResponse} Object if verified. */ static async verifyAndParse(signedResponse, optionalCryptoFactory) { // get cryptoFactory or create one let cryptoFactory; if (!optionalCryptoFactory) { cryptoFactory = new CryptoOptions_1.default().cryptoFactory; } else { cryptoFactory = optionalCryptoFactory; } // get identifier id from did. const token = await JwsToken_1.default.deserialize(signedResponse, { cryptoFactory }); const response = JSON.parse(token.payload.toString()); const senderId = response.did; // create new identifier and verify Auth Response const options = new UserAgentOptions_1.default(); options.resolver = new HttpResolver_1.default('https://beta.discover.did.microsoft.com'); const sender = new Identifier_1.default(senderId, options); // if (!await VerifyHelper.verify(sender, token)) { console.log('not verifying for now'); // throw new UserAgentError(`Invalid signature for token issued by: ${response.did}`); // } // check exp const milliseconds = 1000; // set a leeway of 5 minutes. const expirationCheckTimeOffsetInMinutes = 5; const expirationCheck = new Date(Date.now() - milliseconds * 60 * expirationCheckTimeOffsetInMinutes); // 5 minutes from now const expCheck = Math.floor(expirationCheck.getTime() / milliseconds); if (response.exp < expCheck) { throw new UserAgentError_1.default(`Token from id: ${senderId} has expired`); } const claimNames = response['_claim_names']; const claimSources = response['_claim_sources']; let claimObjects = []; for (var name in claimNames) { // 1. get src name (ex. src1) const src = claimNames[name]; const srcArray = claimSources[src]; const claimObjectsFromSource = await OidcResponse.getClaimObjects(srcArray, sender, cryptoFactory); claimObjects = claimObjects.concat(claimObjectsFromSource); } if (claimObjects !== []) { return new OidcResponse(sender, response.nonce, response.redirectUrl, { state: response.state, presentedClaimObjects: claimObjects }); } return new OidcResponse(sender, response.nonce, response.redirectUrl, { state: response.state }); } /** * Helper method to parse for ClaimObjects in OIDC Response. * @param sourceArray source array that contain claims * @param {Identifier} sender Identifer that represents entity that sent request. * @param {CryptoFactory} cryptoFactory cryptoFactory used for crypto operations. */ static async getClaimObjects(sourceArray, sender, cryptoFactory) { console.log(sender.id); const claimObjects = []; // Assuming only asking for one claim for now. const jwtBinding = sourceArray[0]; // 1. JWT is signed by entity that owns claim (same as entity that sent auth response). const signedClaim = jwtBinding.JWT; const claimToken = await JwsToken_1.default.deserialize(signedClaim, { cryptoFactory }); const claimObject = await ClaimObject_1.default.deserialize(claimToken.payload.toString()); // 2. Verify signature. // if (!await VerifyHelper.verify(sender, claimToken)) { console.log('not verifying for now'); // throw new UserAgentError(`Presented Claim: ${claimObject.claimClass} not verified`); // } // 3. get issuer id from claimObject. const issuerId = claimObject.issuer; const options = new UserAgentOptions_1.default(); options.resolver = new HttpResolver_1.default('https://beta.discover.did.microsoft.com'); // 4. create new issuer Identifier. const issuer = new Identifier_1.default(issuerId, options); // 5. Verify that issuer signed the claimDetails. const claimDetailsToken = await JwsToken_1.default.deserialize(signedClaim, { cryptoFactory }); // if (!await VerifyHelper.verify(issuer, claimDetailsToken)) { // console.log('not verifying for now'); // throw new UserAgentError(`Presented Claim: ${claimObject.claimClass} not verified`); // } // 6. Add claimObject to array of presentedClaims. claimObjects.push(claimObject); return claimObjects; } /** * Send response to redirect url and * * @param {string} response OIDC Response * @param {string} url url to send the response to. * @param {string} state optional state parameter */ async sendResponse(response, url, state) { let responseBody = `id_token=${response}`; if (state) { responseBody = `${responseBody}&state=${state}`; } const oidcResponse = await node_fetch_1.default(url, { method: 'POST', body: responseBody, headers: { 'Content-Type': 'application/x-www-form-urlencoded', 'Content-Length': responseBody.length.toString() }, }); if (oidcResponse.status !== 200) { throw new Error(`OpenID Connect response failed to send: ${oidcResponse.text()}`); } return oidcResponse.json(); } } exports.default = OidcResponse; //# sourceMappingURL=OidcResponse.js.map