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

1 lines • 32.3 kB
{"version":3,"file":"MdocDcApiService.mjs","names":[],"sources":["../../../src/modules/mdoc/MdocDcApiService.ts"],"sourcesContent":["import {\n CoseKey,\n type CredentialMatchFailure,\n type CredentialMatchSuccess,\n defaultVerificationCallback,\n EncryptionInfo,\n Holder,\n IsoMdocDcApi,\n type IsoMdocDcApiParsedRequest,\n type IsoMdocDcApiRequest,\n type IsoMdocDcApiResponse,\n onCategoryCheck,\n reportDeviceRequestMatch,\n Verifier,\n} from '@owf/mdoc'\nimport { AgentContext } from '../../agent'\nimport { EventEmitter } from '../../agent/EventEmitter'\nimport { TrustedIssuerContext } from '../../agent/TrustedIssuerContext'\nimport type { VerificationTypeMdocReaderAuth } from '../../agent/TrustedIssuersForVerification'\nimport { getMdocContext } from '../../crypto/contexts/mdocContext'\nimport { injectable } from '../../plugins'\nimport { mapNonEmptyArray } from '../../types'\nimport { TypedArrayEncoder } from '../../utils'\nimport {\n CredentialMultiInstanceUseMode,\n canUseInstanceFromCredentialRecord,\n useInstanceFromCredentialRecord,\n} from '../../utils/credentialUse'\nimport { KeyManagementApi } from '../kms'\nimport { type EncodedX509Certificate, X509Certificate, X509ModuleConfig, X509Service } from '../x509'\nimport { convertLegacyTrustedCertificates } from '../x509/utils/convertLegacyTrustedCertificates'\nimport { Mdoc } from './Mdoc'\nimport { MdocDeviceResponse } from './MdocDeviceResponse'\nimport { MdocDeviceRequestNotSatisfiedError, MdocError, MdocVerificationSessionExpiredError } from './MdocError'\nimport { MdocEventTypes, type MdocVerificationSessionStateChangedEvent } from './MdocEvents'\nimport type {\n MdocDcApiCreateResponseOptions,\n MdocDcApiCreateVerificationSessionOptions,\n MdocDcApiFailedCredential,\n MdocDcApiResolvedDocRequest,\n MdocDcApiResolvedDocRequestSuccess,\n MdocDcApiResolvedRequest,\n MdocDcApiResolveRequestOptions,\n MdocDcApiValidCredential,\n MdocDcApiVerifyResponseOptions,\n} from './MdocOptions'\nimport { MdocVerificationSessionState } from './MdocVerificationSessionState'\nimport { mdocSigningJwk, nameSpacesRecordToDeviceNamespaces } from './mdocUtil'\nimport {\n MdocRecord,\n MdocRepository,\n MdocVerificationSessionRecord,\n MdocVerificationSessionRepository,\n} from './repository'\nimport { createDeviceRequestDefinition, getDeviceRequestMatchOptions } from './utils/deviceRequestDefinition'\n\nconst defaultExpiresInSeconds = 5 * 60\n\n/**\n * Verifier and wallet APIs for the ISO/IEC TS 18013-7:2025 Annex C (`org-iso-mdoc`) DC API.\n *\n * @internal\n */\n@injectable()\nexport class MdocDcApiService {\n public constructor(\n private mdocRepository: MdocRepository,\n private mdocVerificationSessionRepository: MdocVerificationSessionRepository,\n private eventEmitter: EventEmitter\n ) {}\n\n /**\n * Create an `org-iso-mdoc` request (verifier side) and persist a verification session for it.\n *\n * The origin is not part of the request payload; it only enters the protocol through the session\n * transcript. It therefore has to be supplied again to {@link MdocDcApiService.verifyResponse},\n * except that a `readerAuth` request signs over the transcript and is fixed to the origin it is\n * created for.\n */\n public async createVerificationSession(\n agentContext: AgentContext,\n options: MdocDcApiCreateVerificationSessionOptions\n ): Promise<{ verificationSession: MdocVerificationSessionRecord; request: IsoMdocDcApiRequest }> {\n const kms = agentContext.resolve(KeyManagementApi)\n const mdocContext = getMdocContext(agentContext)\n\n const deviceRequestDefinition = createDeviceRequestDefinition(\n options.docRequests,\n options.treatAmbiguousMultipleDocRequestsAsAlternatives\n )\n\n const { keyId, publicJwk } = await kms.createKey({ type: { kty: 'EC', crv: 'P-256' } })\n\n try {\n const readerCertificateChain = options.readerAuth\n ? Array.isArray(options.readerAuth.certificate)\n ? options.readerAuth.certificate\n : [options.readerAuth.certificate]\n : undefined\n\n const { request, encryptionInfo } = await IsoMdocDcApi.createRequest(\n {\n docRequests: deviceRequestDefinition.docRequests.map(({ docType, nameSpaces }) => ({\n docType,\n namespaces: Object.fromEntries(\n Object.entries(nameSpaces).map(([nameSpace, elements]) => [\n nameSpace,\n Object.fromEntries(\n Object.entries(elements).map(([elementIdentifier, { intentToRetain }]) => [\n elementIdentifier,\n intentToRetain,\n ])\n ),\n ])\n ),\n })),\n recipientPublicKey: CoseKey.fromJwk({ ...publicJwk, kid: keyId }),\n readerAuth:\n readerCertificateChain && options.readerAuth\n ? {\n signingKey: CoseKey.fromJwk(mdocSigningJwk(readerCertificateChain[0].publicJwk)),\n certificateChain: readerCertificateChain.map((certificate) => certificate.rawCertificate),\n origin: options.readerAuth.origin,\n }\n : undefined,\n },\n mdocContext\n )\n\n const verificationSession = new MdocVerificationSessionRecord({\n state: MdocVerificationSessionState.RequestCreated,\n deviceRequestBase64Url: request.deviceRequest,\n deviceRequestDefinition,\n sessionTranscript: {\n type: 'isoMdocDcApi',\n encryptionInfoBase64Url: request.encryptionInfo,\n nonce: TypedArrayEncoder.toBase64Url(encryptionInfo.nonce),\n },\n sessionKeyId: keyId,\n expiresAt: new Date(Date.now() + (options.expiresInSeconds ?? defaultExpiresInSeconds) * 1000),\n })\n\n await this.mdocVerificationSessionRepository.save(agentContext, verificationSession)\n this.emitStateChangedEvent(agentContext, verificationSession, null)\n\n return { verificationSession, request }\n } catch (error) {\n await kms.deleteKey({ keyId }).catch((deleteError) => {\n agentContext.config.logger.error(`Failed to delete session key ${keyId} after failed request creation`, {\n deleteError,\n requestCreationError: error,\n })\n })\n\n throw error\n }\n }\n\n /**\n * Decrypt and verify an `org-iso-mdoc` response (verifier side).\n *\n * The response is bound to the origin through the session transcript, so decryption only succeeds\n * for the origin the wallet actually used. That is what makes a response relayed from another\n * origin fail.\n */\n public async verifyResponse(agentContext: AgentContext, options: MdocDcApiVerifyResponseOptions) {\n const mdocContext = getMdocContext(agentContext)\n\n const verificationSession = await this.mdocVerificationSessionRepository.getById(\n agentContext,\n options.verificationSessionId\n )\n verificationSession.assertState(MdocVerificationSessionState.RequestCreated)\n\n const sessionTranscript = verificationSession.getSessionTranscript('isoMdocDcApi')\n const response = options.response.response\n\n try {\n if (verificationSession.isExpired) {\n throw new MdocVerificationSessionExpiredError(verificationSession.expiresAt)\n }\n\n // The recipient key is identified by the `keyId` on the public key, the same way signing keys\n // are resolved. Take the public key from the `encryptionInfo` we sent, and bind the `keyId`\n // from the session record so the KMS resolves the private key we created for this session.\n const recipientKey = CoseKey.fromJwk({\n ...EncryptionInfo.fromBase64Url(sessionTranscript.encryptionInfoBase64Url).recipientPublicKey.jwk,\n kid: verificationSession.sessionKeyId,\n })\n\n const decrypted = await IsoMdocDcApi.decryptResponse(\n {\n response,\n origin: options.origin,\n encryptionInfo: sessionTranscript.encryptionInfoBase64Url,\n recipientKey,\n },\n mdocContext\n )\n\n const deviceResponse = MdocDeviceResponse.fromBase64Url(\n TypedArrayEncoder.toBase64Url(decrypted.deviceResponse.encode())\n )\n\n const sessionTranscriptOptions = {\n type: 'sessionTranscriptBytes',\n sessionTranscriptBytes: decrypted.sessionTranscript.encode(),\n } as const\n\n if (options.trustedCertificates) {\n await deviceResponse.verify(agentContext, {\n trustedCertificates: options.trustedCertificates,\n sessionTranscriptOptions,\n now: options.now,\n })\n } else {\n // Trust is resolved per document, as each document can be issued by a different issuer and\n // the trust callbacks are given the certificate chain of the document being verified.\n for (const documentResponse of deviceResponse.splitIntoSingleDocumentResponses()) {\n // biome-ignore lint/style/noNonNullAssertion: splitIntoSingleDocumentResponses always returns responses with exactly one document\n const mdoc = new Mdoc(documentResponse.deviceResponse.documents![0].issuerSigned)\n\n await documentResponse.verify(agentContext, {\n trustedCertificates: await this.getTrustedCertificatesForMdoc(agentContext, mdoc),\n sessionTranscriptOptions,\n now: options.now,\n })\n }\n }\n\n // Annex C has no query language such as DCQL, so the device request we sent is what the\n // response has to satisfy. Matched on the full response, as a doc request can be answered by\n // any of the documents in it. The wallet selected its credentials with the same rules. The\n // session's definition also carries whether the doc requests were meant as alternatives, of\n // which the response only has to satisfy one.\n const deviceRequestMatch = Verifier.matchDeviceRequest({\n deviceRequest: TypedArrayEncoder.fromBase64Url(verificationSession.deviceRequestBase64Url),\n deviceResponse: decrypted.deviceResponse,\n matchOptions: getDeviceRequestMatchOptions(verificationSession.deviceRequestDefinition),\n })\n\n if (!deviceRequestMatch.success) {\n const reasons: string[] = []\n reportDeviceRequestMatch(deviceRequestMatch, ({ status, reason, check }) => {\n if (status === 'FAILED') reasons.push(reason ?? check)\n })\n\n throw new MdocDeviceRequestNotSatisfiedError(\n deviceRequestMatch,\n `Device response does not satisfy the device request. ${reasons.join('. ')}`\n )\n }\n\n verificationSession.sessionTranscript = { ...sessionTranscript, origin: options.origin }\n await this.updateState(agentContext, verificationSession, MdocVerificationSessionState.ResponseVerified)\n\n return { verificationSession, deviceResponse, deviceRequestMatch, origin: options.origin }\n } catch (error) {\n verificationSession.errorMessage = error instanceof Error ? error.message : 'Unknown error'\n await this.updateState(agentContext, verificationSession, MdocVerificationSessionState.Error)\n\n throw error\n }\n }\n\n /**\n * Resolve the trusted certificates for a single mdoc in a device response.\n *\n * Same resolution order as the other credential verification paths: the global\n * `getTrustedIssuersForVerification` callback first, then the deprecated\n * `getTrustedCertificatesForVerification` callback, and only if neither returns anything the\n * statically configured trusted certificates. Both callbacks receive the certificate chain of\n * the mdoc being verified, so trust can be decided per document.\n */\n private async getTrustedCertificatesForMdoc(agentContext: AgentContext, mdoc: Mdoc) {\n const x509ModuleConfig = agentContext.dependencyManager.resolve(X509ModuleConfig)\n const certificateChain = mdoc.issuerSignedCertificateChain.map((certificate) =>\n X509Certificate.fromRawCertificate(certificate)\n )\n\n const trustedIssuers = await TrustedIssuerContext.getTrustedIssuersForVerification(agentContext, {\n signer: {\n method: 'x509',\n certificateChain,\n },\n verification: {\n type: 'credential',\n credential: mdoc,\n },\n })\n\n return (\n trustedIssuers?.trustedIssuers ??\n (await x509ModuleConfig.getTrustedCertificatesForVerification?.(agentContext, {\n certificateChain,\n verification: {\n type: 'credential',\n credential: mdoc,\n },\n })) ??\n x509ModuleConfig.trustedCertificates ??\n []\n )\n }\n\n /**\n * Assert the reader certificate chain of a single doc request is trusted.\n *\n * Same resolution order as the issuer trust in {@link MdocDcApiService.getTrustedCertificatesForMdoc}:\n * the certificates passed to `resolveRequest` first, then the global\n * `getTrustedIssuersForVerification` callback, then the deprecated\n * `getTrustedCertificatesForVerification` callback, and only if none of these return anything\n * the statically configured trusted certificates.\n *\n * As everywhere else x509 signers are verified, resolving nothing is not a reason to continue\n * without establishing trust: the reader certificate chain is signed by the reader itself, so\n * skipping the chain check would leave the reader identity self-asserted. A wallet that wants to\n * decide on the reader identity itself can return the leaf certificate from the callback, which\n * trusts the reader on the certificate it presented.\n */\n private async assertTrustedReaderCertificateChain(\n agentContext: AgentContext,\n options: {\n certificateChain: X509Certificate[]\n verification: VerificationTypeMdocReaderAuth\n trustedReaderCertificates?: Array<EncodedX509Certificate | X509Certificate>\n now?: Date\n }\n ) {\n const x509ModuleConfig = agentContext.dependencyManager.resolve(X509ModuleConfig)\n\n let trustedCertificates = options.trustedReaderCertificates?.map((certificate) =>\n typeof certificate === 'string' ? certificate : certificate.toString('pem')\n )\n\n if (!trustedCertificates) {\n const trustedIssuers = await TrustedIssuerContext.getTrustedIssuersForVerification(agentContext, {\n signer: {\n method: 'x509',\n certificateChain: options.certificateChain,\n },\n verification: options.verification,\n })\n\n const legacyTrustedCertificates = trustedIssuers\n ? undefined\n : await x509ModuleConfig.getTrustedCertificatesForVerification?.(agentContext, {\n certificateChain: options.certificateChain,\n verification: options.verification,\n })\n\n trustedCertificates =\n trustedIssuers?.trustedIssuers.flatMap(({ issuance }) => issuance) ??\n (legacyTrustedCertificates\n ? convertLegacyTrustedCertificates(legacyTrustedCertificates).flatMap(({ issuance }) => issuance)\n : undefined) ??\n x509ModuleConfig.trustedCertificates\n }\n\n if (!trustedCertificates) {\n throw new MdocError('No trusted certificates found. Cannot verify reader authentication.')\n }\n\n // Reported as a check rather than thrown directly, so it fails the same way as the reader auth\n // signature check in `parseRequest`. The default callback throws on a failed check.\n const onCheck = onCategoryCheck(defaultVerificationCallback, 'READER_AUTH')\n try {\n await X509Service.validateCertificateChain(agentContext, {\n certificateChain: options.certificateChain.map((certificate) => certificate.toString('pem')),\n trustedCertificates,\n verificationDate: options.now,\n })\n\n onCheck({\n status: 'PASSED',\n check: 'Reader certificate chain must be trusted',\n })\n } catch (error) {\n onCheck({\n status: 'FAILED',\n check: 'Reader certificate chain must be trusted',\n reason: error instanceof Error ? error.message : 'Unknown error',\n })\n }\n }\n\n /**\n * Parse an incoming `org-iso-mdoc` request (wallet side) and match it against stored mdocs.\n *\n * `origin` must be the origin provided by the platform; Annex C C.5 requires the mdoc to abort\n * when no origin was received.\n */\n public async resolveRequest(\n agentContext: AgentContext,\n options: MdocDcApiResolveRequestOptions\n ): Promise<MdocDcApiResolvedRequest> {\n const mdocContext = getMdocContext(agentContext)\n\n // Reader trust is not resolved here, but per doc request below. `parseRequest` takes a single\n // list of trust anchors for the whole request, while every doc request carries its own reader\n // certificate chain and the trust callbacks are scoped to the chain they are asked about.\n // `parseRequest` therefore only verifies the reader auth signatures.\n const parsedRequest = await IsoMdocDcApi.parseRequest(\n {\n request: options.request,\n origin: options.origin,\n disableReaderCertificateChainValidation: true,\n now: options.now,\n },\n mdocContext\n )\n\n const candidates = await this.getCandidates(\n agentContext,\n parsedRequest.docRequests.map((docRequest) => docRequest.docType),\n options.useMode ?? CredentialMultiInstanceUseMode.NewOrFirst\n )\n\n // The same rules the verifier matches the response with, so a credential that matches in full\n // results in a response that satisfies the doc request.\n const deviceNamespaces = options.deviceNameSpaces\n ? nameSpacesRecordToDeviceNamespaces(options.deviceNameSpaces)\n : undefined\n const holderMatch = Holder.matchDeviceRequest({\n deviceRequest: parsedRequest.deviceRequest,\n credentials: candidates.map(({ mdoc }) => ({ issuerSigned: mdoc.issuerSigned, deviceNamespaces })),\n treatAmbiguousMultipleDocRequestsAsAlternatives: options.treatAmbiguousMultipleDocRequestsAsAlternatives,\n })\n\n const toValidCredential = ({\n credentialIndex,\n ...credentialMatch\n }: CredentialMatchSuccess): MdocDcApiValidCredential => ({\n ...credentialMatch,\n record: candidates[credentialIndex].record,\n })\n const toFailedCredential = ({\n credentialIndex,\n ...credentialMatch\n }: CredentialMatchFailure): MdocDcApiFailedCredential => ({\n ...credentialMatch,\n record: candidates[credentialIndex].record,\n })\n\n const docRequests: MdocDcApiResolvedDocRequest[] = []\n for (const [docRequestIndex, docRequest] of parsedRequest.docRequests.entries()) {\n const nameSpaces = Object.fromEntries(\n Array.from(docRequest.namespaces.entries()).map(([nameSpace, elements]) => [\n nameSpace,\n Object.fromEntries(elements.entries()),\n ])\n )\n\n const readerAuth = docRequest.hasReaderAuth\n ? {\n certificateChain: (docRequest.readerCertificateChain ?? []).map((certificate) =>\n X509Certificate.fromRawCertificate(certificate)\n ),\n }\n : undefined\n\n if (readerAuth) {\n await this.assertTrustedReaderCertificateChain(agentContext, {\n certificateChain: readerAuth.certificateChain,\n verification: {\n type: 'mdocReaderAuth',\n exchange: { type: 'dcApi', origin: parsedRequest.origin },\n docType: docRequest.docType,\n nameSpaces,\n },\n trustedReaderCertificates: options.trustedReaderCertificates,\n now: options.now,\n })\n }\n\n const docRequestMatch = holderMatch.docRequests[docRequestIndex]\n const resolvedDocRequest = {\n docRequestIndex,\n docType: docRequest.docType,\n nameSpaces,\n readerAuth,\n failedCredentials: docRequestMatch.failedCredentials.map(toFailedCredential),\n }\n\n docRequests.push(\n docRequestMatch.success\n ? {\n ...resolvedDocRequest,\n success: true,\n validCredentials: mapNonEmptyArray(docRequestMatch.validCredentials, toValidCredential),\n }\n : { ...resolvedDocRequest, success: false, validCredentials: [] }\n )\n }\n\n // `holderMatch.success` already applies the alternatives rule, so it is the single source of\n // truth for whether the request can be answered. The doc requests are only narrowed to the\n // successful ones when all of them succeeded, as with alternatives the failed ones are kept so\n // the wallet can see which alternative it can answer.\n if (holderMatch.docRequestsAsAlternatives) {\n return {\n origin: parsedRequest.origin,\n success: holderMatch.success,\n docRequestsAsAlternatives: true,\n docRequests,\n parsedRequest,\n }\n }\n\n return holderMatch.success\n ? {\n origin: parsedRequest.origin,\n success: true,\n docRequestsAsAlternatives: false,\n docRequests: docRequests.filter(\n (docRequest): docRequest is MdocDcApiResolvedDocRequestSuccess => docRequest.success\n ),\n parsedRequest,\n }\n : {\n origin: parsedRequest.origin,\n success: false,\n docRequestsAsAlternatives: false,\n docRequests,\n parsedRequest,\n }\n }\n\n /**\n * Fetch the stored mdocs for the requested doctypes.\n *\n * Only the requested doctypes are fetched, as a wallet can hold many more mdocs than a request\n * covers. The issuer-signed CBOR of each record is decoded once, and matched against every doc\n * request from there.\n *\n * Records that cannot provide an instance for `useMode` are left out, as creating a response with\n * them would throw.\n */\n private async getCandidates(agentContext: AgentContext, docTypes: string[], useMode: CredentialMultiInstanceUseMode) {\n const uniqueDocTypes = Array.from(new Set(docTypes))\n if (uniqueDocTypes.length === 0) return []\n\n const records = await this.mdocRepository.findByQuery(agentContext, {\n $or: uniqueDocTypes.map((docType) => ({ docType })),\n })\n\n return records\n .filter((record) => canUseInstanceFromCredentialRecord({ credentialRecord: record, useMode }))\n .map((record) => ({ record, mdoc: record.firstCredential }))\n }\n\n /**\n * Create the encrypted `org-iso-mdoc` response (wallet side).\n */\n public async createResponse(\n agentContext: AgentContext,\n options: MdocDcApiCreateResponseOptions\n ): Promise<IsoMdocDcApiResponse> {\n const mdocContext = getMdocContext(agentContext)\n const parsedRequest = options.resolvedRequest.parsedRequest as IsoMdocDcApiParsedRequest\n\n const documents = await Promise.all(\n options.credentials.map(async ({ docRequestIndex, record, useMode, elements, deviceNameSpaces }) => {\n const mdocRecord =\n record instanceof MdocRecord ? record : await this.mdocRepository.getById(agentContext, record)\n\n const { credentialInstance: mdoc } = await useInstanceFromCredentialRecord({\n agentContext,\n credentialRecord: mdocRecord,\n useMode: useMode ?? CredentialMultiInstanceUseMode.NewOrFirst,\n })\n\n const deviceKeyJwk = mdocSigningJwk(mdoc.deviceKey)\n if (!deviceKeyJwk.kid) {\n throw new MdocError('Unable to create device response. The mdoc device key has no key id.')\n }\n\n return {\n docRequestIndex,\n issuerSigned: mdoc.issuerSigned,\n deviceKey: CoseKey.fromJwk(deviceKeyJwk),\n elements,\n deviceNamespaces: deviceNameSpaces ? nameSpacesRecordToDeviceNamespaces(deviceNameSpaces) : undefined,\n }\n })\n )\n\n return await IsoMdocDcApi.createResponse({ parsedRequest, documents }, mdocContext)\n }\n\n private async updateState(\n agentContext: AgentContext,\n verificationSession: MdocVerificationSessionRecord,\n newState: MdocVerificationSessionState\n ) {\n const previousState = verificationSession.state\n verificationSession.state = newState\n\n await this.mdocVerificationSessionRepository.update(agentContext, verificationSession)\n this.emitStateChangedEvent(agentContext, verificationSession, previousState)\n }\n\n private emitStateChangedEvent(\n agentContext: AgentContext,\n verificationSession: MdocVerificationSessionRecord,\n previousState: MdocVerificationSessionState | null\n ) {\n this.eventEmitter.emit<MdocVerificationSessionStateChangedEvent>(agentContext, {\n type: MdocEventTypes.MdocVerificationSessionStateChanged,\n payload: {\n verificationSession: verificationSession.clone(),\n previousState,\n },\n })\n }\n}\n"],"mappings":"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"}