UNPKG

vess-mdl

Version:

Parse and and validate MDOC CBOR encoded binaries according to ISO 18013-5.

295 lines 24.1 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.DeviceResponse = void 0; const cose_kit_1 = require("cose-kit"); const buffer_1 = require("buffer"); const MDoc_1 = require("./MDoc"); const DeviceSignedDocument_1 = require("./DeviceSignedDocument"); const parser_1 = require("../parser"); const utils_1 = require("../utils"); const cbor_1 = require("../../cbor"); /** * A builder class for creating a device response. */ class DeviceResponse { /** * Create a DeviceResponse builder. * * @param {MDoc | Uint8Array} mdoc - The mdoc to use as a base for the device response. * It can be either a parsed MDoc or a CBOR encoded MDoc. * @returns {DeviceResponse} - A DeviceResponse builder. */ static from(mdoc) { if (mdoc instanceof Uint8Array) { return new DeviceResponse((0, parser_1.parse)(mdoc)); } return new DeviceResponse(mdoc); } constructor(mdoc) { this.useMac = true; this.nameSpaces = {}; this.mdoc = mdoc; } /** * * @param pd - The presentation definition to use for the device response. * @returns {DeviceResponse} */ usingPresentationDefinition(pd) { if (!pd.input_descriptors.length) { throw new Error('The Presentation Definition must have at least one Input Descriptor object.'); } const hasDuplicates = pd.input_descriptors.some((id1, idx) => pd.input_descriptors.findIndex((id2) => id2.id === id1.id) !== idx); if (hasDuplicates) { throw new Error('Each Input Descriptor object must have a unique id property.'); } this.pd = pd; return this; } /** * Set the session transcript data to use for the device response with the given handover data. * this is a shortcut to calling {@link usingSessionTranscriptBytes}(`<cbor encoding of [null, null, handover] in a Tagged 24 structure>`), * which is what the OID4VP protocol expects. * * @deprecated Use {@link usingSessionTranscriptForOID4VP} instead. * @param {string[]} handover - The handover data to use in the session transcript. * @returns {DeviceResponse} */ usingHandover(handover) { this.usingSessionTranscriptBytes((0, cbor_1.cborEncode)(cbor_1.DataItem.fromData([ null, // deviceEngagementBytes null, // eReaderKeyBytes handover ]))); return this; } /** * Set the session transcript data to use for the device response. * * This is arbitrary and should match the session transcript as it will be calculated by the verifier. * The transcript must be a CBOR encoded DataItem of an array, there is no further requirement. * * Example: `usingSessionTranscriptBytes(cborEncode(DataItem.fromData([a,b,c])))` where `a`, `b` and `c` can be anything including `null`. * * It is preferable to use {@link usingSessionTranscriptForOID4VP} or {@link usingSessionTranscriptForWebAPI} when possible. * * @param {Buffer} sessionTranscriptBytes - The sessionTranscriptBytes data to use in the session transcript. * @returns {DeviceResponse} */ usingSessionTranscriptBytes(sessionTranscriptBytes) { if (this.sessionTranscriptBytes) { throw new Error('A session transcript has already been set, either with .usingSessionTranscriptForOID4VP, .usingSessionTranscriptForWebAPI or .usingSessionTranscriptBytes'); } this.sessionTranscriptBytes = sessionTranscriptBytes; return this; } /** * Set the session transcript data to use for the device response as defined in ISO/IEC 18013-7 in Annex B (OID4VP), 2023 draft. * * This should match the session transcript as it will be calculated by the verifier. * * @param {string} mdocGeneratedNonce - A cryptographically random number with sufficient entropy. * @param {string} clientId - The client_id Authorization Request parameter from the Authorization Request Object. * @param {string} responseUri - The response_uri Authorization Request parameter from the Authorization Request Object. * @param {string} verifierGeneratedNonce - The nonce Authorization Request parameter from the Authorization Request Object. * @returns {DeviceResponse} */ usingSessionTranscriptForOID4VP(mdocGeneratedNonce, clientId, responseUri, verifierGeneratedNonce) { this.usingSessionTranscriptBytes((0, cbor_1.cborEncode)(cbor_1.DataItem.fromData([ null, // deviceEngagementBytes null, // eReaderKeyBytes [mdocGeneratedNonce, clientId, responseUri, verifierGeneratedNonce], ]))); return this; } /** * Set the session transcript data to use for the device response as defined in ISO/IEC 18013-7 in Annex A (Web API), 2023 draft. * * This should match the session transcript as it will be calculated by the verifier. * * @param {Buffer} deviceEngagementBytes - The device engagement, encoded as a Tagged 24 cbor * @param {Buffer} readerEngagementBytes - The reader engagement, encoded as a Tagged 24 cbor * @param {Buffer} eReaderKeyBytes - The reader ephemeral public key as a COSE Key, encoded as a Tagged 24 cbor * @returns {DeviceResponse} */ usingSessionTranscriptForWebAPI(deviceEngagementBytes, readerEngagementBytes, eReaderKeyBytes) { this.usingSessionTranscriptBytes((0, cbor_1.cborEncode)(cbor_1.DataItem.fromData([ new cbor_1.DataItem({ buffer: deviceEngagementBytes }), new cbor_1.DataItem({ buffer: eReaderKeyBytes }), readerEngagementBytes, ]))); return this; } /** * Add a name space to the device response. * * @param {string} nameSpace - The name space to add to the device response. * @param {Record<string, any>} data - The data to add to the name space. * @returns {DeviceResponse} */ addDeviceNameSpace(nameSpace, data) { this.nameSpaces[nameSpace] = data; return this; } /** * Set the device's private key to be used for signing the device response. * * @param {jose.JWK | Uint8Array} devicePrivateKey - The device's private key either as a JWK or a COSEKey. * @param {SupportedAlgs} alg - The algorithm to use for signing the device response. * @returns {DeviceResponse} */ authenticateWithSignature(devicePrivateKey, alg) { if (devicePrivateKey instanceof Uint8Array) { this.devicePrivateKey = devicePrivateKey; } else { this.devicePrivateKey = (0, cose_kit_1.COSEKeyFromJWK)(devicePrivateKey); } this.alg = alg; this.useMac = false; return this; } /** * Set the reader shared key to be used for signing the device response with MAC. * * @param {jose.JWK | Uint8Array} devicePrivateKey - The device's private key either as a JWK or a COSEKey. * @param {Uint8Array} ephemeralPublicKey - The public part of the ephemeral key generated by the MDOC. * @param {SupportedAlgs} alg - The algorithm to use for signing the device response. * @returns {DeviceResponse} */ authenticateWithMAC(devicePrivateKey, ephemeralPublicKey, alg) { if (devicePrivateKey instanceof Uint8Array) { this.devicePrivateKey = devicePrivateKey; } else { this.devicePrivateKey = (0, cose_kit_1.COSEKeyFromJWK)(devicePrivateKey); } this.ephemeralPublicKey = ephemeralPublicKey; this.macAlg = alg; this.useMac = true; return this; } /** * Sign the device response and return the MDoc. * * @returns {Promise<MDoc>} - The device response as an MDoc. */ async sign() { if (!this.pd) throw new Error('Must provide a presentation definition with .usingPresentationDefinition()'); if (!this.sessionTranscriptBytes) throw new Error('Must provide the session transcript with either .usingSessionTranscriptForOID4VP, .usingSessionTranscriptForWebAPI or .usingSessionTranscriptBytes'); const docs = await Promise.all(this.pd.input_descriptors.map((id) => this.handleInputDescriptor(id))); return new MDoc_1.MDoc(docs); } async handleInputDescriptor(id) { const document = (this.mdoc.documents || []).find((d) => d.docType === id.id); if (!document) { // TODO; probl need to create a DocumentError here, but let's just throw for now throw new Error(`The mdoc does not have a document with DocType "${id.id}"`); } const nameSpaces = await this.prepareNamespaces(id, document); return new DeviceSignedDocument_1.DeviceSignedDocument(document.docType, { nameSpaces, issuerAuth: document.issuerSigned.issuerAuth, }, await this.getDeviceSigned(document.docType)); } async getDeviceSigned(docType) { const deviceAuthenticationBytes = (0, utils_1.calculateDeviceAutenticationBytes)(this.sessionTranscriptBytes, docType, this.nameSpaces); const deviceSigned = { nameSpaces: this.nameSpaces, deviceAuth: this.useMac ? await this.getDeviceAuthMac(deviceAuthenticationBytes, this.sessionTranscriptBytes) : await this.getDeviceAuthSign(deviceAuthenticationBytes), }; return deviceSigned; } async getDeviceAuthMac(deviceAuthenticationBytes, sessionTranscriptBytes) { const { kid } = (0, cose_kit_1.COSEKeyToJWK)(this.devicePrivateKey); const ephemeralMacKey = await (0, utils_1.calculateEphemeralMacKey)(this.devicePrivateKey, this.ephemeralPublicKey, sessionTranscriptBytes); const mac = await cose_kit_1.Mac0.create({ alg: this.macAlg }, { kid }, deviceAuthenticationBytes, ephemeralMacKey); return { deviceMac: mac }; } async getDeviceAuthSign(cborData) { if (!this.devicePrivateKey) throw new Error('Missing devicePrivateKey'); const key = await (0, cose_kit_1.importCOSEKey)(this.devicePrivateKey); const { kid } = (0, cose_kit_1.COSEKeyToJWK)(this.devicePrivateKey); const deviceSignature = await cose_kit_1.Sign1.sign({ alg: this.alg }, { kid }, buffer_1.Buffer.from(cborData), key); return { deviceSignature }; } async prepareNamespaces(id, document) { const requestedFields = id.constraints.fields; const nameSpaces = {}; for await (const field of requestedFields) { const result = await this.prepareDigest(field.path, document); if (!result) { // TODO: Do we add an entry to DocumentErrors if not found? console.log(`No matching field found for ${field.path}`); continue; } const { nameSpace, digest } = result; if (!nameSpaces[nameSpace]) nameSpaces[nameSpace] = []; nameSpaces[nameSpace].push(digest); } return nameSpaces; } async prepareDigest(paths, document) { /** * path looks like this: "$['org.iso.18013.5.1']['family_name']" * the regex creates two groups with contents between "['" and "']" * the second entry in each group contains the result without the "'[" or "']" */ for (const path of paths) { // eslint-disable-next-line @typescript-eslint/no-unused-vars const [[_1, nameSpace], [_2, elementIdentifier]] = [...path.matchAll(/\['(.*?)'\]/g)]; if (!nameSpace) throw new Error(`Failed to parse namespace from path "${path}"`); if (!elementIdentifier) throw new Error(`Failed to parse elementIdentifier from path "${path}"`); const nsAttrs = document.issuerSigned.nameSpaces[nameSpace] || []; const digest = nsAttrs.find((d) => d.elementIdentifier === elementIdentifier); if (elementIdentifier.startsWith('age_over_')) { return this.handleAgeOverNN(elementIdentifier, nameSpace, nsAttrs); } if (digest) { return { nameSpace, digest, }; } } return null; } handleAgeOverNN(request, nameSpace, attributes) { const ageOverList = attributes .map((a, i) => { const { elementIdentifier: key, elementValue: value } = a; return { key, value, index: i }; }) .filter((i) => i.key.startsWith('age_over_')) .map((i) => ({ nn: parseInt(i.key.replace('age_over_', ''), 10), ...i, })) .sort((a, b) => a.nn - b.nn); const reqNN = parseInt(request.replace('age_over_', ''), 10); let item; // Find nearest TRUE item = ageOverList.filter((i) => i.value === true && i.nn >= reqNN)?.[0]; if (!item) { // Find the nearest False item = ageOverList.sort((a, b) => b.nn - a.nn).filter((i) => i.value === false && i.nn <= reqNN)?.[0]; } if (!item) { return null; } return { nameSpace, digest: attributes[item.index], }; } } exports.DeviceResponse = DeviceResponse; //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiRGV2aWNlUmVzcG9uc2UuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi9zcmMvbWRvYy9tb2RlbC9EZXZpY2VSZXNwb25zZS50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOzs7QUFDQSx1Q0FBb0Y7QUFDcEYsbUNBQWdDO0FBRWhDLGlDQUE4QjtBQUc5QixpRUFBOEQ7QUFFOUQsc0NBQWtDO0FBQ2xDLG9DQUF1RjtBQUN2RixxQ0FBa0Q7QUFHbEQ7O0dBRUc7QUFDSCxNQUFhLGNBQWM7SUFZekI7Ozs7OztPQU1HO0lBQ0ksTUFBTSxDQUFDLElBQUksQ0FBQyxJQUF1QjtRQUN4QyxJQUFJLElBQUksWUFBWSxVQUFVLEVBQUUsQ0FBQztZQUMvQixPQUFPLElBQUksY0FBYyxDQUFDLElBQUEsY0FBSyxFQUFDLElBQUksQ0FBQyxDQUFDLENBQUM7UUFDekMsQ0FBQztRQUNELE9BQU8sSUFBSSxjQUFjLENBQUMsSUFBSSxDQUFDLENBQUM7SUFDbEMsQ0FBQztJQUVELFlBQVksSUFBVTtRQXRCZCxXQUFNLEdBQUcsSUFBSSxDQUFDO1FBR2YsZUFBVSxHQUF3QyxFQUFFLENBQUM7UUFvQjFELElBQUksQ0FBQyxJQUFJLEdBQUcsSUFBSSxDQUFDO0lBQ25CLENBQUM7SUFFRDs7OztPQUlHO0lBQ0ksMkJBQTJCLENBQUMsRUFBMEI7UUFDM0QsSUFBSSxDQUFDLEVBQUUsQ0FBQyxpQkFBaUIsQ0FBQyxNQUFNLEVBQUUsQ0FBQztZQUNqQyxNQUFNLElBQUksS0FBSyxDQUFDLDZFQUE2RSxDQUFDLENBQUM7UUFDakcsQ0FBQztRQUVELE1BQU0sYUFBYSxHQUFHLEVBQUUsQ0FBQyxpQkFBaUIsQ0FBQyxJQUFJLENBQUMsQ0FBQyxHQUFHLEVBQUUsR0FBRyxFQUFFLEVBQUUsQ0FBQyxFQUFFLENBQUMsaUJBQWlCLENBQUMsU0FBUyxDQUFDLENBQUMsR0FBRyxFQUFFLEVBQUUsQ0FBQyxHQUFHLENBQUMsRUFBRSxLQUFLLEdBQUcsQ0FBQyxFQUFFLENB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