UNPKG

@credo-ts/core

Version:

<p align="center"> <br /> <img alt="Credo Logo" src="https://github.com/openwallet-foundation/credo-ts/blob/c7886cb8377ceb8ee4efe8d264211e561a75072d/images/credo-logo.png" height="250px" /> </p> <h1 align="center"><b>Credo - Core</b></h1

213 lines (209 loc) • 10.8 kB
import { TypedArrayEncoder } from "../../utils/TypedArrayEncoder.mjs"; import "../../utils/index.mjs"; import "../../agent/index.mjs"; import "../kms/index.mjs"; import { X509Certificate } from "../x509/X509Certificate.mjs"; import "../x509/index.mjs"; import { ClaimFormat } from "../vc/models/ClaimFormat.mjs"; import "../vc/index.mjs"; import { MdocError } from "./MdocError.mjs"; import { getMdocContext } from "./MdocContext.mjs"; import { isMdocSupportedSignatureAlgorithm, mdocSupportedSignatureAlgorithms } from "./mdocSupportedAlgs.mjs"; import { nameSpacesRecordToMap } from "./mdocUtil.mjs"; import { convertDocumentRequest } from "./utils/convertDocumentRequest.mjs"; import { CoseKey, DeviceNamespaces, DeviceRequest, DeviceResponse, DeviceSignedItems, DocRequest, ItemsRequest, SessionTranscript, defaultVerificationCallback, limitDisclosureToDeviceRequestNameSpaces } from "@owf/mdoc"; import { convertDcqlQueryToDeviceRequest, convertPresentationDefinitionToDeviceRequest } from "@verifiables/request-converter"; //#region src/modules/mdoc/MdocDeviceResponse.ts var MdocDeviceResponse = class MdocDeviceResponse { constructor(deviceResponse) { this.deviceResponse = deviceResponse; } /** * claim format is convenience method added to all credential instances */ get claimFormat() { return ClaimFormat.MsoMdoc; } /** * Encoded is convenience method added to all credential instances */ get encoded() { return TypedArrayEncoder.toBase64Url(this.deviceResponse.encode()); } /** * To support a single DeviceResponse with multiple documents in OpenID4VP */ splitIntoSingleDocumentResponses() { const deviceResponses = []; if (!this.deviceResponse.documents || this.deviceResponse.documents.length === 0) throw new MdocError("mdoc device response does not contain any mdocs"); for (const document of this.deviceResponse.documents) deviceResponses.push(new MdocDeviceResponse(DeviceResponse.createSimple({ version: this.deviceResponse.version, status: this.deviceResponse.status, documentErrors: this.deviceResponse.documentErrors, documents: [document] }))); return deviceResponses; } static fromBase64Url(base64Url) { return new MdocDeviceResponse(DeviceResponse.decode(TypedArrayEncoder.fromBase64Url(base64Url))); } static async createDeviceResponseWithPresentationDefinition(agentContext, options) { const { deviceRequest } = convertPresentationDefinitionToDeviceRequest(options.presentationDefinition); const documentRequests = convertDocumentRequest(deviceRequest.docRequests); return MdocDeviceResponse.createDeviceResponse(agentContext, { ...options, documentRequests }); } static async createDeviceResponseWithDcqlQuery(agentContext, options) { const { deviceRequest } = convertDcqlQueryToDeviceRequest(options.dcqlQuery); const documentRequests = convertDocumentRequest(deviceRequest.docRequests); return MdocDeviceResponse.createDeviceResponse(agentContext, { ...options, documentRequests }); } static async limitDisclosureToInputDescriptor(_agentContext, { mdoc, ...options }) { const inputDescriptor = MdocDeviceResponse.assertMdocInputDescriptor(options.inputDescriptor); let issuerNamespaces; try { const { deviceRequest: { docRequests: [convertedDocumentRequest] } } = convertPresentationDefinitionToDeviceRequest({ id: "<UNUSED_CREDO_ID>", input_descriptors: [inputDescriptor] }); const documentRequest = DocRequest.create({ itemsRequest: ItemsRequest.create({ docType: convertedDocumentRequest.itemsRequest.docType, namespaces: convertedDocumentRequest.itemsRequest.nameSpaces }) }); issuerNamespaces = limitDisclosureToDeviceRequestNameSpaces(mdoc.issuerSigned, documentRequest); } catch (_error) { const { deviceRequest: { docRequests: [convertedDocumentRequest] } } = convertPresentationDefinitionToDeviceRequest({ id: "<UNUSED_CREDO_ID>", input_descriptors: [inputDescriptor] }, { skipOptionalFields: true }); const documentRequest = DocRequest.create({ itemsRequest: ItemsRequest.create({ docType: convertedDocumentRequest.itemsRequest.docType, namespaces: convertedDocumentRequest.itemsRequest.nameSpaces }) }); issuerNamespaces = limitDisclosureToDeviceRequestNameSpaces(mdoc.issuerSigned, documentRequest); } return Object.fromEntries(Array.from(issuerNamespaces.issuerNamespaces.entries()).map(([namespace, issuerSignedItem]) => [namespace, Object.fromEntries(issuerSignedItem.map((isi) => [isi.elementIdentifier, isi.elementValue]))])); } static assertMdocInputDescriptor(inputDescriptor) { if (!inputDescriptor.format || !inputDescriptor.format.mso_mdoc) throw new MdocError(`Input descriptor must contain 'mso_mdoc' format property`); if (!inputDescriptor.format.mso_mdoc.alg) throw new MdocError(`Input descriptor mso_mdoc must contain 'alg' property`); if (!inputDescriptor.constraints?.limit_disclosure || inputDescriptor.constraints.limit_disclosure !== "required") throw new MdocError(`Input descriptor must contain 'limit_disclosure' constraints property which is set to required`); if (!inputDescriptor.constraints?.fields?.every((field) => field.intent_to_retain !== void 0)) throw new MdocError(`Input descriptor must contain 'intent_to_retain' constraints property`); return { ...inputDescriptor, format: { mso_mdoc: inputDescriptor.format.mso_mdoc }, constraints: { ...inputDescriptor.constraints, limit_disclosure: "required", fields: (inputDescriptor.constraints.fields ?? []).map((field) => { return { ...field, intent_to_retain: field.intent_to_retain ?? false }; }) } }; } static async createDeviceResponse(agentContext, options) { const documents = []; for (const document of options.mdocs) { const deviceKeyJwk = document.deviceKey; const deviceKeyJwkJson = deviceKeyJwk.toJson(); if (!deviceKeyJwkJson.alg) deviceKeyJwkJson.alg = MdocDeviceResponse.getAlgForDeviceKeyJwk(deviceKeyJwk); if (!deviceKeyJwk.hasKeyId) deviceKeyJwk.keyId = deviceKeyJwk.legacyKeyId; const deviceRequestForDocument = DeviceRequest.create({ version: "1.0", docRequests: options.documentRequests.filter((request) => request.docType === document.docType).map((request) => DocRequest.create({ itemsRequest: ItemsRequest.create({ docType: request.docType, namespaces: nameSpacesRecordToMap(request.nameSpaces) }) })) }); const mdocContext = getMdocContext(agentContext); const deviceResponse = await DeviceResponse.createWithDeviceRequest({ deviceRequest: deviceRequestForDocument, issuerSigned: [document.issuerSigned], sessionTranscript: await MdocDeviceResponse.calculateSessionTranscriptBytes(mdocContext, options.sessionTranscriptOptions), deviceNamespaces: options.deviceNameSpaces ? DeviceNamespaces.create({ deviceNamespaces: new Map(Object.entries(options.deviceNameSpaces).map(([namespace, namespaceValue]) => [namespace, DeviceSignedItems.create({ deviceSignedItems: new Map(Object.entries(namespaceValue)) })])) }) : void 0, signature: { signingKey: CoseKey.fromJwk(deviceKeyJwk.toJson()) } }, mdocContext); if (!deviceResponse.documents) throw new MdocError("Device response does not contain any documents"); documents.push(deviceResponse.documents[0]); } return new MdocDeviceResponse(DeviceResponse.createSimple({ version: "1.0", documents })); } async verify(agentContext, options) { const mdocContext = getMdocContext(agentContext); defaultVerificationCallback({ status: this.deviceResponse.documents?.length ? "PASSED" : "FAILED", check: "Device Response must include at least one document.", category: "DOCUMENT_FORMAT" }); await this.deviceResponse.verify({ trustedCertificates: options.trustedCertificates?.map((certificate) => X509Certificate.fromEncodedCertificate(certificate).rawCertificate) ?? [], disableCertificateChainValidation: false, now: options.now, skewSeconds: agentContext.config.validitySkewSeconds, sessionTranscript: await MdocDeviceResponse.calculateSessionTranscriptBytes(mdocContext, options.sessionTranscriptOptions) }, mdocContext).catch((error) => { throw new MdocError(`Mdoc with doctype ${this.deviceResponse.documents?.[0].docType} is not valid`, { cause: error }); }); } static async calculateSessionTranscriptBytes(mdocContext, options) { if (options.type === "sesionTranscriptBytes") return options.sessionTranscriptBytes; if (options.type === "openId4VpDraft18") return SessionTranscript.forOid4VpDraft18(options, mdocContext); if (options.type === "openId4Vp") { const { encryptionJwk, verifierGeneratedNonce, ...rest } = options; return SessionTranscript.forOid4Vp({ ...rest, jwkThumbprint: encryptionJwk ? encryptionJwk.getJwkThumbprint() : void 0, nonce: verifierGeneratedNonce }, mdocContext); } if (options.type === "openId4VpDcApi") { const { encryptionJwk, verifierGeneratedNonce, ...rest } = options; return SessionTranscript.forOid4VpDcApi({ ...rest, nonce: verifierGeneratedNonce, jwkThumbprint: encryptionJwk ? encryptionJwk.getJwkThumbprint() : void 0 }, mdocContext); } if (options.type === "openId4VpDcApiDraft24") { const { verifierGeneratedNonce, ...rest } = options; return SessionTranscript.forOid4VpDcApiDraft24({ ...rest, nonce: verifierGeneratedNonce }, mdocContext); } throw new MdocError("Unsupported session transcript option"); } static getAlgForDeviceKeyJwk(jwk) { const signatureAlgorithm = jwk.supportedSignatureAlgorithms.find(isMdocSupportedSignatureAlgorithm); if (!signatureAlgorithm) throw new MdocError(`Unable to create mdoc device response. No supported signature algorithm found to sign device response for jwk ${jwk.jwkTypeHumanDescription}. Key supports algs ${jwk.supportedSignatureAlgorithms.join(", ")}. mdoc supports algs ${mdocSupportedSignatureAlgorithms.join(", ")}`); return signatureAlgorithm; } get issuerClaims() { if (!this.deviceResponse.documents) return {}; return this.deviceResponse.documents.reduce((prev, document) => ({ ...prev, [document.docType]: Object.fromEntries(Array.from(document.issuerSigned.issuerNamespaces.issuerNamespaces.entries()).map(([namespace, claim]) => [namespace, Object.fromEntries(claim.map((c) => [c.elementIdentifier, c.elementValue]))])) }), {}); } get deviceClaims() { if (!this.deviceResponse.documents) return {}; return this.deviceResponse.documents.reduce((prev, document) => ({ ...prev, [document.docType]: Object.fromEntries(Array.from(document.deviceSigned.deviceNamespaces.deviceNamespaces.entries()).map(([namespace, claim]) => [namespace, Object.fromEntries(claim.deviceSignedItems)])) }), {}); } }; //#endregion export { MdocDeviceResponse }; //# sourceMappingURL=MdocDeviceResponse.mjs.map