@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
184 lines (183 loc) • 10.1 kB
JavaScript
import { CredoError } from "../../../error/CredoError.mjs";
import "../../../error/index.mjs";
import { MessageValidator } from "../../../utils/MessageValidator.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 { CREDENTIALS_CONTEXT_V2_URL } from "../constants.mjs";
import { validateVc2ContextBaseline, validateVc2CredentialStatus } from "../validators.mjs";
import { W3cV2Credential } from "../models/credential/W3cV2Credential.mjs";
import { W3cV2DataIntegrityVerifiableCredential } from "./W3cV2DataIntegrityVerifiableCredential.mjs";
import { W3cV2DataIntegrityVerifiablePresentation } from "./W3cV2DataIntegrityVerifiablePresentation.mjs";
import { W3cV2Presentation } from "../models/presentation/W3cV2Presentation.mjs";
import { createW3cDataIntegrityCredoError } from "../../w3c-di/W3cDataIntegrityError.mjs";
import { W3cDataIntegrityProofService } from "../../w3c-di/W3cDataIntegrityProofService.mjs";
import "../../w3c-di/internal.mjs";
import { W3cV2DataIntegrityContextValidator } from "./W3cV2DataIntegrityContextValidator.mjs";
import { getVerificationMethodForDataIntegrityProof } from "./v2-di-utils.mjs";
//#region src/modules/vc/data-integrity/W3cV2DataIntegrityCredentialService.ts
let W3cV2DataIntegrityCredentialService = class W3cV2DataIntegrityCredentialService {
constructor(dataIntegrityProofService, contextValidator) {
this.dataIntegrityProofService = dataIntegrityProofService;
this.contextValidator = contextValidator;
}
async signCredential(agentContext, options) {
const unsecuredCredential = JsonTransformer.toJSON(options.credential);
if (unsecuredCredential["@context"] === void 0 || unsecuredCredential["@context"] === null) unsecuredCredential["@context"] = [CREDENTIALS_CONTEXT_V2_URL];
MessageValidator.validateSync(JsonTransformer.fromJSON(unsecuredCredential, W3cV2Credential, { validate: false }));
const contextValidation = validateVc2ContextBaseline(unsecuredCredential["@context"]);
if (!contextValidation.isValid) throw contextValidation.error ?? new CredoError("VC2 credential @context validation failed");
const proofResult = await this.dataIntegrityProofService.createProof(agentContext, {
unsecuredDocument: unsecuredCredential,
verificationMethod: options.verificationMethod,
proofPurpose: "assertionMethod",
cryptosuite: options.cryptosuite
});
if (!proofResult.created) throw createW3cDataIntegrityCredoError(proofResult.errors);
return new W3cV2DataIntegrityVerifiableCredential({ securedCredential: {
...unsecuredCredential,
proof: proofResult.proof
} });
}
async verifyCredential(agentContext, options) {
const securedCredential = options.credential.securedCredential;
const verificationResult = Array.isArray(securedCredential.proof) ? await this.dataIntegrityProofService.verifyProofSetAndChain(agentContext, securedCredential, { expectedProofPurpose: "assertionMethod" }) : await this.dataIntegrityProofService.verifyProof(agentContext, securedCredential, { expectedProofPurpose: "assertionMethod" });
if (!verificationResult.verified) return this.invalidResult(verificationResult.errors, "credential");
const contextResult = await this.contextValidator.validate(agentContext, securedCredential);
if (!contextResult.validated) return this.invalidResult(contextResult.errors, "credential");
const credentialStatus = validateVc2CredentialStatus({
credentialStatus: securedCredential.credentialStatus,
credentialFormat: "DI",
verifyCredentialStatus: options.verifyCredentialStatus
});
const issuerIsSigner = await this.verifySignerIsExpectedParty(agentContext, securedCredential, "assertionMethod", options.credential.resolvedCredential.issuerId, (verificationMethodId, issuerId) => `Credential is signed using verification method ${verificationMethodId}, while the issuer of the credential is '${issuerId}'`);
return {
isValid: credentialStatus.isValid && issuerIsSigner.isValid,
validations: {
dataModel: { isValid: true },
signature: { isValid: true },
credentialStatus,
issuerIsSigner
}
};
}
async signPresentation(agentContext, options) {
const unsecuredPresentation = JsonTransformer.toJSON(options.presentation);
if (unsecuredPresentation["@context"] === void 0 || unsecuredPresentation["@context"] === null) unsecuredPresentation["@context"] = [CREDENTIALS_CONTEXT_V2_URL];
MessageValidator.validateSync(JsonTransformer.fromJSON(unsecuredPresentation, W3cV2Presentation, { validate: false }));
const contextValidation = validateVc2ContextBaseline(unsecuredPresentation["@context"]);
if (!contextValidation.isValid) throw contextValidation.error ?? new CredoError("VC2 presentation @context validation failed");
const proofResult = await this.dataIntegrityProofService.createProof(agentContext, {
unsecuredDocument: unsecuredPresentation,
verificationMethod: options.verificationMethod,
proofPurpose: "authentication",
cryptosuite: options.cryptosuite,
challenge: options.challenge,
domain: options.domain
});
if (!proofResult.created) throw createW3cDataIntegrityCredoError(proofResult.errors);
const securedPresentation = {
...unsecuredPresentation,
proof: proofResult.proof
};
return new W3cV2DataIntegrityVerifiablePresentation({
securedPresentation,
resolvedPresentation: JsonTransformer.fromJSON(securedPresentation, W3cV2Presentation, { validate: false })
});
}
async verifyPresentation(agentContext, options) {
const securedPresentation = options.presentation.securedPresentation;
const verificationResult = Array.isArray(securedPresentation.proof) ? await this.dataIntegrityProofService.verifyProofSetAndChain(agentContext, securedPresentation, {
expectedProofPurpose: "authentication",
challenge: options.challenge,
domain: options.domain
}) : await this.dataIntegrityProofService.verifyProof(agentContext, securedPresentation, {
expectedProofPurpose: "authentication",
challenge: options.challenge,
domain: options.domain
});
if (!verificationResult.verified) return this.invalidResult(verificationResult.errors, "presentation");
const presentationContextResult = await this.contextValidator.validate(agentContext, securedPresentation);
if (!presentationContextResult.validated) return this.invalidResult(presentationContextResult.errors, "presentation");
const holderIsSigner = await this.verifySignerIsExpectedParty(agentContext, options.presentation.securedPresentation, "authentication", options.presentation.resolvedPresentation.holderId, (verificationMethodId, holderId) => `Presentation is signed using verification method ${verificationMethodId}, while the holder of the presentation is '${holderId}'`);
return {
isValid: holderIsSigner.isValid,
presentation: {
isValid: holderIsSigner.isValid,
validations: {
dataModel: { isValid: true },
holderIsSigner,
presentationSignature: { isValid: true }
}
},
credentialEntries: []
};
}
/**
* Checks that a secured document was signed by the identity it names as responsible for it —
* the `issuer` of a credential, or the `holder` of a presentation.
*
* Mirrors the equivalent checks in the JWT and SD-JWT services — though those compare key
* fingerprints for the issuer, whereas Data Integrity proofs name a verification method, so the
* comparison here is by controller for both roles. It implements the association described in
* VC-DATA-INTEGRITY §"Relationship to Verifiable Credentials": that the `controller` of the
* proof's verification method matches the identifier used for the `issuer` or `holder`, and that
* the verification method is expressed under a verification relationship acceptable for the
* proof's purpose. The latter is enforced by dereferencing the key with `purpose`.
*
* Verifying the proof only establishes that *someone* signed; without this comparison a document
* could name one party and be signed by an unrelated one.
*
* @param expectedSigner the `issuer` or `holder`. `Holder` is optional in
* VCDM 2.0 so when undefined there is no claim to contradict, so the validation passes.
*/
async verifySignerIsExpectedParty(agentContext, securedDocument, purpose, expectedSigner, describeMismatch) {
if (!expectedSigner) return { isValid: true };
try {
const verificationMethod = await getVerificationMethodForDataIntegrityProof(agentContext, securedDocument, purpose, expectedSigner);
if (verificationMethod.controller !== expectedSigner) return {
isValid: false,
error: new CredoError(describeMismatch(verificationMethod.id, expectedSigner))
};
return { isValid: true };
} catch (error) {
return {
isValid: false,
error
};
}
}
invalidResult(errors, target) {
const invalidValidation = this.invalidValidation(errors);
if (target === "credential") return {
isValid: false,
validations: { signature: invalidValidation }
};
return this.invalidPresentationResult("presentationSignature", invalidValidation.error);
}
invalidValidation(errors) {
return {
isValid: false,
error: createW3cDataIntegrityCredoError(errors)
};
}
invalidPresentationResult(field, error) {
return {
isValid: false,
presentation: {
isValid: false,
validations: { [field]: {
isValid: false,
error
} }
},
credentialEntries: []
};
}
};
W3cV2DataIntegrityCredentialService = __decorate([injectable(), __decorateMetadata("design:paramtypes", [typeof W3cDataIntegrityProofService === "undefined" ? Object : W3cDataIntegrityProofService, typeof W3cV2DataIntegrityContextValidator === "undefined" ? Object : W3cV2DataIntegrityContextValidator])], W3cV2DataIntegrityCredentialService);
//#endregion
export { W3cV2DataIntegrityCredentialService };
//# sourceMappingURL=W3cV2DataIntegrityCredentialService.mjs.map