{"version":3,"file":"mdocContext.mjs","names":[],"sources":["../../../src/crypto/contexts/mdocContext.ts"],"sourcesContent":["import { CoseKey, type HpkeSuiteId, type MdocContext } from '@owf/mdoc'\nimport { AgentContext } from '../../agent'\nimport { CredoWebCrypto } from '../../crypto'\nimport { CredoError } from '../../error'\nimport { KeyManagementApi, type KmsJwkPublicEcdh, type KnownJwaHpkeAlgorithm, PublicJwk } from '../../modules/kms'\nimport { X509Certificate } from '../../modules/x509/X509Certificate'\nimport { X509Service } from '../../modules/x509/X509Service'\nimport { getMac0Context } from './mac0Context'\nimport { getSign1Context } from './sign1Context'\n\n/**\n * HPKE suites the mdoc library can request, mapped to the JOSE-HPKE style algorithm identifiers the\n * KMS uses. ISO 18013-7 Annex C only uses the first entry.\n */\nconst hpkeSuiteToJwaAlgorithm = {\n  'dhkem-p256-hkdf-sha256/hkdf-sha256/aes-128-gcm': 'HPKE-0',\n  'dhkem-p256-hkdf-sha256/hkdf-sha256/aes-256-gcm': 'HPKE-7',\n  'dhkem-x25519-hkdf-sha256/hkdf-sha256/aes-128-gcm': 'HPKE-3',\n} as const satisfies Record<HpkeSuiteId, KnownJwaHpkeAlgorithm>\n\nfunction jwaAlgorithmForHpkeSuite(suite: HpkeSuiteId) {\n  const algorithm = hpkeSuiteToJwaAlgorithm[suite]\n  if (!algorithm) throw new CredoError(`Unsupported HPKE suite '${suite}'`)\n\n  return algorithm\n}\n\nexport const getMdocContext = (agentContext: AgentContext, { now }: { now?: Date } = {}): MdocContext => {\n  const crypto = new CredoWebCrypto(agentContext)\n  const kms = agentContext.resolve(KeyManagementApi)\n\n  return {\n    fetch: agentContext.config.agentDependencies.fetch,\n    crypto: {\n      digest: async (input) => {\n        const { bytes, digestAlgorithm } = input\n\n        return new Uint8Array(\n          crypto.digest(\n            digestAlgorithm,\n            // NOTE:  extra Uint8Array wrapping is needed here, somehow if we use `bytes.buffer` directly\n            // it's not working. Maybe due to Uint8array length\n            new Uint8Array(bytes).buffer\n          )\n        )\n      },\n      random: (length) => {\n        return crypto.getRandomValues(new Uint8Array(length))\n      },\n      hdkf: async () => {\n        // Only invoked for mdoc device MAC authentication (ISO/IEC 18013-5), which Credo does not\n        // implement (device signature authentication only). Guarded because the callback contract\n        // takes the raw device private key, which is incompatible with a non-exportable, KMS-held key.\n        //\n        // TODO: implement via a KMS key-agreement operation (ECDH-ES derive-to-secret or KMS-native\n        // EMacKey/HMAC) so the key never leaves the KMS — see `keyDeriveEcdhEs` in the askar backend.\n        // No new transport needed: the reader ephemeral key is already in the SessionTranscript; the\n        // outstanding work is that KMS primitive plus a device-auth mode option on MdocDeviceResponseOptions.\n        throw new CredoError(\n          'mdoc device MAC authentication is not supported. Only device signature authentication is currently implemented.'\n        )\n      },\n      hpke: {\n        suites: Object.keys(hpkeSuiteToJwaAlgorithm) as HpkeSuiteId[],\n        seal: async (input) => {\n          const { encrypted, encapsulatedKey } = await kms.encrypt({\n            key: {\n              keyAgreement: {\n                algorithm: jwaAlgorithmForHpkeSuite(input.suite),\n                externalPublicJwk: PublicJwk.fromUnknown(input.recipientPublicKey.jwk).toJson() as KmsJwkPublicEcdh,\n                info: input.info,\n              },\n            },\n            encryption: { algorithm: 'HPKE', aad: input.aad },\n            data: input.plaintext,\n          })\n\n          if (!encapsulatedKey) {\n            throw new CredoError('Key management service did not return an encapsulated key for HPKE seal')\n          }\n\n          return { enc: encapsulatedKey, ciphertext: encrypted }\n        },\n        open: async (input) => {\n          const { keyId } = input.recipientKey\n          if (!keyId) {\n            throw new CredoError('Missing required keyId on the recipient key for HPKE open')\n          }\n\n          const { data } = await kms.decrypt({\n            key: {\n              keyAgreement: {\n                algorithm: jwaAlgorithmForHpkeSuite(input.suite),\n                keyId,\n                encapsulatedKey: input.enc,\n                info: input.info,\n              },\n            },\n            decryption: { algorithm: 'HPKE', aad: input.aad },\n            encrypted: input.ciphertext,\n          })\n\n          return data\n        },\n      },\n    },\n\n    cose: {\n      mac0: getMac0Context(agentContext),\n      sign1: getSign1Context(agentContext),\n    },\n\n    x509: {\n      getSubjectNameField: (input) => {\n        const x509Certificate = X509Certificate.fromRawCertificate(input.certificate)\n        return x509Certificate.getSubjectNameField(input.field)\n      },\n      getPublicKey: async (input) => {\n        const certificate = X509Certificate.fromRawCertificate(input.certificate)\n        return CoseKey.fromJwk(certificate.publicJwk.toJson())\n      },\n      verifyCertificateChain: async (input) => {\n        const certificateChain = input.x5chain.map((cert) => X509Certificate.fromRawCertificate(cert).toString('pem'))\n        const trustedCertificates = input.trustedCertificates.map((cert) =>\n          X509Certificate.fromRawCertificate(cert).toString('pem')\n        ) as [string, ...string[]]\n\n        const validatedChain = await X509Service.validateCertificateChain(agentContext, {\n          certificateChain,\n          trustedCertificates,\n          verificationDate: input.now ?? now,\n        })\n\n        // X509Service.validateCertificateChain returns the chain root-first. The mdoc context contract\n        // requires the chain to be leaf-first with the trust anchor (root) as the last entry, so reverse here.\n        return {\n          chain: validatedChain.map((cert) => cert.rawCertificate).reverse(),\n        }\n      },\n      getCertificateData: async (input) => {\n        const { certificate } = input\n        const x509Certificate = X509Certificate.fromRawCertificate(certificate)\n        return {\n          ...x509Certificate.data,\n          thumbprint: await x509Certificate.getThumbprintInHex(agentContext),\n        }\n      },\n    },\n  } satisfies MdocContext\n}\n"],"mappings":"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"}