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

360 lines (359 loc) • 22.8 kB
import { CredoError } from "../../error/CredoError.mjs"; import "../../error/index.mjs"; import { JsonTransformer } from "../../utils/JsonTransformer.mjs"; import "../../utils/index.mjs"; import { injectable } from "../../plugins/index.mjs"; import __decorateMetadata from "../../_virtual/_@oxc-project_runtime@0.151.0/helpers/esm/decorateMetadata.mjs"; import __decorate from "../../_virtual/_@oxc-project_runtime@0.151.0/helpers/esm/decorate.mjs"; import { getJwkHumanDescription } from "../kms/jwk/humanDescription.mjs"; import "../kms/index.mjs"; import { uuid } from "../../utils/uuid.mjs"; import { getPublicJwkFromVerificationMethod } from "../dids/domain/key-type/keyDidMapping.mjs"; import { DidsApi } from "../dids/DidsApi.mjs"; import "../dids/index.mjs"; import { ANONCREDS_W3C_CREDENTIAL_CRYPTOSUITE, AnonCredsW3cCredentialServiceSymbol } from "../vc/anoncreds-w3c-credential/IAnonCredsW3cCredentialService.mjs"; import "../vc/anoncreds-w3c-credential/index.mjs"; import "../vc/models/ClaimFormat.mjs"; import { W3cPresentation } from "../vc/models/presentation/W3cPresentation.mjs"; import { W3cCredentialRepository } from "../vc/repository/W3cCredentialRepository.mjs"; import { purposes } from "../vc/linked-data-proofs/adapters/jsonld-signatures-adapter.mjs"; import { SignatureSuiteRegistry } from "../vc/linked-data-proofs/SignatureSuiteRegistry.mjs"; import { W3cCredentialService } from "../vc/W3cCredentialService.mjs"; import "../vc/index.mjs"; import { MdocDeviceResponse } from "../mdoc/MdocDeviceResponse.mjs"; import { MdocApi } from "../mdoc/MdocApi.mjs"; import "../mdoc/index.mjs"; import { SdJwtVcApi } from "../sd-jwt-vc/SdJwtVcApi.mjs"; import "../sd-jwt-vc/index.mjs"; import { DifPresentationExchangeError } from "./DifPresentationExchangeError.mjs"; import { DifPresentationExchangeSubmissionLocation as PresentationSubmissionLocation } from "./models/index.mjs"; import { getSphereonOriginalVerifiableCredential, getSphereonOriginalVerifiablePresentation, getVerifiablePresentationFromEncoded } from "./utils/transform.mjs"; import { getCredentialsForRequest } from "./utils/credentialSelection.mjs"; import { getPresentationsToCreate } from "./utils/presentationsToCreate.mjs"; import "./utils/index.mjs"; import { PEX, Status } from "@animo-id/pex"; import { PEVersion } from "@animo-id/pex/dist/main/lib/index.js"; //#region src/modules/dif-presentation-exchange/DifPresentationExchangeService.ts let DifPresentationExchangeService = class DifPresentationExchangeService { constructor(w3cCredentialService) { this.w3cCredentialService = w3cCredentialService; this.pex = new PEX(); } async getCredentialsForRequest(agentContext, presentationDefinition) { const credentialRecords = await this.queryCredentialForPresentationDefinition(agentContext, presentationDefinition); return getCredentialsForRequest(agentContext, this.pex, presentationDefinition, credentialRecords); } /** * Selects the credentials to use based on the output from `getCredentialsForRequest` * Use this method if you don't want to manually select the credentials yourself. */ selectCredentialsForRequest(credentialsForRequest) { if (!credentialsForRequest.areRequirementsSatisfied) throw new CredoError("Could not find the required credentials for the presentation submission"); const credentials = {}; for (const requirement of credentialsForRequest.requirements) for (const submission of requirement.submissionEntry.slice(0, requirement.needsCount)) { if (!credentials[submission.inputDescriptorId]) credentials[submission.inputDescriptorId] = []; credentials[submission.inputDescriptorId].push(submission.verifiableCredentials[0]); } return credentials; } validatePresentationDefinition(presentationDefinition) { const validation = PEX.validateDefinition(presentationDefinition); const errorMessages = this.formatValidated(validation); if (errorMessages.length > 0) throw new DifPresentationExchangeError("Invalid presentation definition", { additionalMessages: errorMessages }); } validatePresentationSubmission(presentationSubmission) { const validation = PEX.validateSubmission(presentationSubmission); const errorMessages = this.formatValidated(validation); if (errorMessages.length > 0) throw new DifPresentationExchangeError("Invalid presentation submission", { additionalMessages: errorMessages }); } validatePresentation(presentationDefinition, presentations, presentationSubmission) { const result = this.pex.evaluatePresentation(presentationDefinition, Array.isArray(presentations) ? presentations.map(getSphereonOriginalVerifiablePresentation) : getSphereonOriginalVerifiablePresentation(presentations), { limitDisclosureSignatureSuites: ["DataIntegrityProof.anoncreds-2023"], presentationSubmission }); if (result.areRequiredCredentialsPresent === Status.ERROR) { const errorMessages = this.formatValidated(result.errors); throw new DifPresentationExchangeError("Invalid presentation", { additionalMessages: errorMessages }); } } formatValidated(v) { return (Array.isArray(v) ? v : v ? [v] : []).filter((r) => r.status === Status.ERROR).map((r) => r.message).filter((r) => Boolean(r)); } async createPresentation(agentContext, options) { const { presentationDefinition, domain, challenge, mdocSessionTranscript } = options; const presentationSubmissionLocation = options.presentationSubmissionLocation ?? PresentationSubmissionLocation.PRESENTATION; const verifiablePresentationResultsWithFormat = []; const presentationsToCreate = getPresentationsToCreate(options.credentialsForInputDescriptor); for (const presentationToCreate of presentationsToCreate) { const inputDescriptorIds = presentationToCreate.verifiableCredentials.map((c) => c.inputDescriptorId); const inputDescriptorsForPresentation = presentationDefinition.input_descriptors.filter((inputDescriptor) => inputDescriptorIds.includes(inputDescriptor.id)); const presentationDefinitionForSubject = { ...presentationDefinition, input_descriptors: inputDescriptorsForPresentation, submission_requirements: void 0 }; if (presentationToCreate.claimFormat === "mso_mdoc") { if (presentationToCreate.verifiableCredentials.length !== 1) throw new DifPresentationExchangeError("Currently a Mdoc presentation can only be created from a single credential"); if (!mdocSessionTranscript) throw new DifPresentationExchangeError("Missing mdoc session transcript options for creating MDOC presentation."); const { credential, inputDescriptorId } = presentationToCreate.verifiableCredentials[0]; const deviceResponse = await MdocDeviceResponse.createDeviceResponseWithPresentationDefinition(agentContext, { mdocs: [credential.firstCredential], presentationDefinition, sessionTranscriptOptions: mdocSessionTranscript }); if (presentationSubmissionLocation !== PresentationSubmissionLocation.EXTERNAL) throw new DifPresentationExchangeError("Only EXTERNAL DifPresentationExchangeSubmissionLocation supported for mdoc presentations"); verifiablePresentationResultsWithFormat.push({ verifiablePresentationResult: { presentationSubmission: { id: `MdocPresentationSubmission ${uuid()}`, definition_id: presentationDefinition.id, descriptor_map: [{ id: inputDescriptorId, format: "mso_mdoc", path: "$" }] }, verifiablePresentations: [deviceResponse.encoded], presentationSubmissionLocation }, claimFormat: presentationToCreate.claimFormat }); } else { const credentialsForPresentation = presentationToCreate.verifiableCredentials.map((c) => getSphereonOriginalVerifiableCredential(c.credential)); const extraProofOptions = this.shouldSignWithAnonCredsW3cService(presentationToCreate) ? { typeSupportsSelectiveDisclosure: true, type: `DataIntegrityProof.${ANONCREDS_W3C_CREDENTIAL_CRYPTOSUITE}` } : {}; const verifiablePresentationResult = await this.pex.verifiablePresentationFrom(presentationDefinitionForSubject, credentialsForPresentation, this.getPresentationSignCallback(agentContext, presentationToCreate), { proofOptions: { challenge, domain, ...extraProofOptions }, presentationSubmissionLocation }); verifiablePresentationResultsWithFormat.push({ verifiablePresentationResult, claimFormat: presentationToCreate.claimFormat }); } } if (verifiablePresentationResultsWithFormat.length === 0) throw new DifPresentationExchangeError("No verifiable presentations created"); if (presentationsToCreate.length !== verifiablePresentationResultsWithFormat.length) throw new DifPresentationExchangeError("Invalid amount of verifiable presentations created"); const presentationSubmission = { id: verifiablePresentationResultsWithFormat[0].verifiablePresentationResult.presentationSubmission.id, definition_id: verifiablePresentationResultsWithFormat[0].verifiablePresentationResult.presentationSubmission.definition_id, descriptor_map: [] }; verifiablePresentationResultsWithFormat.forEach(({ verifiablePresentationResult }, index) => { const descriptorMap = verifiablePresentationResult.presentationSubmission.descriptor_map.map((d) => { const descriptor = { ...d }; if (presentationSubmissionLocation === PresentationSubmissionLocation.EXTERNAL && verifiablePresentationResultsWithFormat.length > 1) descriptor.path = `$[${index}]`; return descriptor; }); presentationSubmission.descriptor_map.push(...descriptorMap); }); return { verifiablePresentations: verifiablePresentationResultsWithFormat.flatMap((resultWithFormat) => resultWithFormat.verifiablePresentationResult.verifiablePresentations.map((vp) => getVerifiablePresentationFromEncoded(agentContext, vp))), encodedVerifiablePresentations: verifiablePresentationResultsWithFormat.flatMap((resultWithFormat) => resultWithFormat.verifiablePresentationResult.verifiablePresentations), presentationSubmission, presentationSubmissionLocation: verifiablePresentationResultsWithFormat[0].verifiablePresentationResult.presentationSubmissionLocation }; } getSigningAlgorithmFromVerificationMethod(verificationMethod, suitableAlgorithms) { const publicJwk = getPublicJwkFromVerificationMethod(verificationMethod); if (suitableAlgorithms) { const possibleAlgorithms = publicJwk.supportedSignatureAlgorithms.filter((alg) => suitableAlgorithms?.includes(alg)); if (!possibleAlgorithms || possibleAlgorithms.length === 0) throw new DifPresentationExchangeError([ "Found no suitable signing algorithm.", `Algorithms supported by Verification method: ${publicJwk.supportedSignatureAlgorithms.join(", ")}`, `Suitable algorithms: ${suitableAlgorithms.join(", ")}` ].join("\n")); return possibleAlgorithms[0]; } return publicJwk.signatureAlgorithm; } getSigningAlgorithmsForPresentationDefinitionAndInputDescriptors(algorithmsSatisfyingDefinition, inputDescriptorAlgorithms) { const allDescriptorAlgorithms = inputDescriptorAlgorithms.flat(); const algorithmsSatisfyingDescriptors = allDescriptorAlgorithms.filter((alg) => inputDescriptorAlgorithms.every((descriptorAlgorithmSet) => descriptorAlgorithmSet.includes(alg))); const algorithmsSatisfyingPdAndDescriptorRestrictions = algorithmsSatisfyingDefinition.filter((alg) => algorithmsSatisfyingDescriptors.includes(alg)); if (algorithmsSatisfyingDefinition.length > 0 && algorithmsSatisfyingDescriptors.length > 0 && algorithmsSatisfyingPdAndDescriptorRestrictions.length === 0) throw new DifPresentationExchangeError("No signature algorithm found for satisfying restrictions of the presentation definition and input descriptors"); if (allDescriptorAlgorithms.length > 0 && algorithmsSatisfyingDescriptors.length === 0) throw new DifPresentationExchangeError("No signature algorithm found for satisfying restrictions of the input descriptors"); let suitableAlgorithms; if (algorithmsSatisfyingPdAndDescriptorRestrictions.length > 0) suitableAlgorithms = algorithmsSatisfyingPdAndDescriptorRestrictions; else if (algorithmsSatisfyingDescriptors.length > 0) suitableAlgorithms = algorithmsSatisfyingDescriptors; else if (algorithmsSatisfyingDefinition.length > 0) suitableAlgorithms = algorithmsSatisfyingDefinition; return suitableAlgorithms; } getSigningAlgorithmForJwtVc(presentationDefinition, verificationMethod) { const algorithmsSatisfyingDefinition = presentationDefinition.format?.jwt_vc?.alg ?? []; const inputDescriptorAlgorithms = presentationDefinition.input_descriptors.map((descriptor) => descriptor.format?.jwt_vc?.alg ?? []).filter((alg) => alg.length > 0); const suitableAlgorithms = this.getSigningAlgorithmsForPresentationDefinitionAndInputDescriptors(algorithmsSatisfyingDefinition, inputDescriptorAlgorithms); return this.getSigningAlgorithmFromVerificationMethod(verificationMethod, suitableAlgorithms); } getProofTypeForLdpVc(agentContext, presentationDefinition, verificationMethod) { const algorithmsSatisfyingDefinition = presentationDefinition.format?.ldp_vc?.proof_type ?? []; const inputDescriptorAlgorithms = presentationDefinition.input_descriptors.map((descriptor) => descriptor.format?.ldp_vc?.proof_type ?? []).filter((alg) => alg.length > 0); const suitableSignatureSuites = this.getSigningAlgorithmsForPresentationDefinitionAndInputDescriptors(algorithmsSatisfyingDefinition, inputDescriptorAlgorithms); const signatureSuiteRegistry = agentContext.dependencyManager.resolve(SignatureSuiteRegistry); const publicJwk = getPublicJwkFromVerificationMethod(verificationMethod); const supportedSignatureSuites = signatureSuiteRegistry.getAllByPublicJwkType(publicJwk); if (supportedSignatureSuites.length === 0) throw new DifPresentationExchangeError(`Couldn't find a supported signature suite for the given jwk ${getJwkHumanDescription(publicJwk.toJson())}`); if (suitableSignatureSuites) { const foundSignatureSuite = supportedSignatureSuites.find((suite) => suitableSignatureSuites.includes(suite.proofType)); if (!foundSignatureSuite) throw new DifPresentationExchangeError([ "No possible signature suite found for the given verification method.", `Verification method type: ${verificationMethod.type}`, `jwk type: ${getJwkHumanDescription(publicJwk.toJson())}`, `SupportedSignatureSuites: '${supportedSignatureSuites.map((s) => s.proofType).join(", ")}'`, `SuitableSignatureSuites: ${suitableSignatureSuites.join(", ")}` ].join("\n")); return foundSignatureSuite.proofType; } return supportedSignatureSuites[0].proofType; } /** * if all submission descriptors have a format of di | ldp, * and all credentials have an anoncreds W3C credential proof we default to * signing the presentation using the ANONCREDS_W3C_CREDENTIAL_CRYPTOSUITE */ shouldSignWithAnonCredsW3cService(presentationToCreate, presentationSubmission) { if (presentationToCreate.claimFormat !== "ldp_vp") return void 0; const validDescriptorFormat = !presentationSubmission || presentationSubmission.descriptor_map.every((descriptor) => [ "di_vc", "di_vp", "ldp_vc", "ldp_vp" ].includes(descriptor.format)); const credentialsAreSignedWithAnonCredsW3c = presentationToCreate.verifiableCredentials.every(({ credential }) => { const firstCredential = credential.firstCredential; if (firstCredential.claimFormat !== "ldp_vc") return false; return firstCredential.anonCredsW3cCredentialCryptosuites.includes(ANONCREDS_W3C_CREDENTIAL_CRYPTOSUITE); }); return validDescriptorFormat && credentialsAreSignedWithAnonCredsW3c; } getPresentationSignCallback(agentContext, presentationToCreate) { return async (callBackParams) => { const { presentation: presentationInput, options, presentationDefinition, presentationSubmission } = callBackParams; const { challenge, domain } = options.proofOptions ?? {}; if (!challenge) throw new CredoError("challenge MUST be provided when signing a Verifiable Presentation"); if (presentationToCreate.claimFormat === "jwt_vp") { if (!presentationToCreate.subjectIds) throw new DifPresentationExchangeError("Cannot create presentation for credentials without subject id"); const verificationMethod = await this.getVerificationMethodForSubjectId(agentContext, presentationToCreate.subjectIds[0]); const w3cPresentation = JsonTransformer.fromJSON(presentationInput, W3cPresentation); w3cPresentation.holder = verificationMethod.controller; return (await this.w3cCredentialService.signPresentation(agentContext, { format: "jwt_vp", alg: this.getSigningAlgorithmForJwtVc(presentationDefinition, verificationMethod), verificationMethod: verificationMethod.id, presentation: w3cPresentation, challenge, domain })).encoded; } if (presentationToCreate.claimFormat === "ldp_vp") { if (this.shouldSignWithAnonCredsW3cService(presentationToCreate, presentationSubmission)) { presentationSubmission.descriptor_map = presentationSubmission.descriptor_map.map((descriptor) => ({ ...descriptor, format: "di_vp" })); return { ...(await agentContext.dependencyManager.resolve(AnonCredsW3cCredentialServiceSymbol).createPresentation(agentContext, { presentationDefinition, presentationSubmission, selectedCredentialRecords: presentationToCreate.verifiableCredentials.map((vc) => vc.credential), challenge })).toJSON(), presentation_submission: presentationSubmission }; } if (!presentationToCreate.subjectIds) throw new DifPresentationExchangeError("Cannot create presentation for credentials without subject id"); const verificationMethod = await this.getVerificationMethodForSubjectId(agentContext, presentationToCreate.subjectIds[0]); const w3cPresentation = JsonTransformer.fromJSON(presentationInput, W3cPresentation); w3cPresentation.holder = verificationMethod.controller; return (await this.w3cCredentialService.signPresentation(agentContext, { format: "ldp_vp", proofType: this.getProofTypeForLdpVc(agentContext, presentationDefinition, verificationMethod), proofPurpose: new purposes.AuthenticationProofPurpose({ challenge, domain }), verificationMethod: verificationMethod.id, presentation: w3cPresentation, challenge, domain })).encoded; } if (presentationToCreate.claimFormat === "dc+sd-jwt") { const sdJwtInput = presentationInput; if (!domain) throw new CredoError("Missing 'domain' property, unable to set required 'aud' property in SD-JWT KB-JWT"); const sdJwtVcApi = this.getSdJwtVcApi(agentContext); const originalSdJwtVc = sdJwtVcApi.fromCompact(sdJwtInput.compactSdJwtVc); originalSdJwtVc.kmsKeyId = presentationToCreate.verifiableCredentials[0].credential.credentialInstances[0].kmsKeyId; return await sdJwtVcApi.present({ sdJwtVc: originalSdJwtVc, presentationFrame: void 0, verifierMetadata: { audience: domain, nonce: challenge, issuedAt: Math.floor(Date.now() / 1e3) }, additionalPayload: presentationToCreate.verifiableCredentials[0].additionalPayload }); } throw new DifPresentationExchangeError("Only JWT, SD-JWT-VC, JSONLD credentials are supported for a single presentation"); }; } async getVerificationMethodForSubjectId(agentContext, subjectId) { const didsApi = agentContext.dependencyManager.resolve(DidsApi); if (!subjectId.startsWith("did:")) throw new DifPresentationExchangeError(`Only dids are supported as credentialSubject id. ${subjectId} is not a valid did`); const didDocument = await didsApi.resolveDidDocument(subjectId); if (!didDocument.authentication || didDocument.authentication.length === 0) throw new DifPresentationExchangeError(`No authentication verificationMethods found for did ${subjectId} in did document`); const [verificationMethod] = didDocument.findVerificationMethodsByPurpose(["authentication"]); return verificationMethod; } /** * Queries the wallet for credentials that match the given presentation definition. This only does an initial query based on the * schema of the input descriptors. It does not do any further filtering based on the constraints in the input descriptors. */ async queryCredentialForPresentationDefinition(agentContext, presentationDefinition) { const w3cCredentialRepository = agentContext.dependencyManager.resolve(W3cCredentialRepository); const w3cQuery = []; const sdJwtVcQuery = []; const mdocQuery = []; const presentationDefinitionVersion = PEX.definitionVersionDiscovery(presentationDefinition); if (!presentationDefinitionVersion.version) throw new DifPresentationExchangeError("Unable to determine the Presentation Exchange version from the presentation definition", presentationDefinitionVersion.error ? { additionalMessages: [presentationDefinitionVersion.error] } : {}); if (presentationDefinitionVersion.version === PEVersion.v1) { const pd = presentationDefinition; for (const inputDescriptor of pd.input_descriptors) for (const schema of inputDescriptor.schema) { sdJwtVcQuery.push({ vct: schema.uri }); w3cQuery.push({ $or: [ { expandedTypes: [schema.uri] }, { contexts: [schema.uri] }, { types: [schema.uri] } ] }); mdocQuery.push({ docType: inputDescriptor.id }); } } else if (presentationDefinitionVersion.version === PEVersion.v2) {} else throw new DifPresentationExchangeError(`Unsupported presentation definition version ${presentationDefinitionVersion.version}`); const allRecords = []; const w3cCredentialRecords = w3cQuery.length > 0 ? await w3cCredentialRepository.findByQuery(agentContext, { $or: w3cQuery }) : await w3cCredentialRepository.getAll(agentContext); allRecords.push(...w3cCredentialRecords); const sdJwtVcApi = this.getSdJwtVcApi(agentContext); const sdJwtVcRecords = sdJwtVcQuery.length > 0 ? await sdJwtVcApi.findAllByQuery({ $or: sdJwtVcQuery }) : await sdJwtVcApi.getAll(); allRecords.push(...sdJwtVcRecords); const mdocApi = this.getMdocApi(agentContext); const mdocRecords = mdocQuery.length > 0 ? await mdocApi.findAllByQuery({ $or: mdocQuery }) : await mdocApi.getAll(); allRecords.push(...mdocRecords); return allRecords; } getSdJwtVcApi(agentContext) { return agentContext.dependencyManager.resolve(SdJwtVcApi); } getMdocApi(agentContext) { return agentContext.dependencyManager.resolve(MdocApi); } }; DifPresentationExchangeService = __decorate([injectable(), __decorateMetadata("design:paramtypes", [typeof W3cCredentialService === "undefined" ? Object : W3cCredentialService])], DifPresentationExchangeService); //#endregion export { DifPresentationExchangeService }; //# sourceMappingURL=DifPresentationExchangeService.mjs.map