@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
298 lines (297 loc) • 14.5 kB
JavaScript
import { CredoError } from "../../../error/CredoError.mjs";
import { RecordNotFoundError } from "../../../error/RecordNotFoundError.mjs";
import "../../../error/index.mjs";
import { asArray } from "../../../utils/array.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 { parseDid } from "../../dids/domain/parse.mjs";
import { DidsApi } from "../../dids/DidsApi.mjs";
import "../../dids/index.mjs";
import { TrustedIssuerContext } from "../../../agent/TrustedIssuerContext.mjs";
import "../anoncreds-w3c-credential/IAnonCredsW3cCredentialService.mjs";
import "../anoncreds-w3c-credential/index.mjs";
import { W3cJsonLdVerifiableCredential } from "./models/W3cJsonLdVerifiableCredential.mjs";
import { validateCredentialSubjectAuthentication, w3cDate } from "../util.mjs";
import jsonld_default from "../jsonld/jsonld.mjs";
import { JsonLdModuleConfig } from "../jsonld/JsonLdModuleConfig.mjs";
import vc_adapter_default from "./adapters/vc-adapter.mjs";
import "../jsonld/index.mjs";
import { W3cJsonLdVerifiablePresentation } from "./models/W3cJsonLdVerifiablePresentation.mjs";
import { assertOnlyW3cJsonLdVerifiableCredentials } from "./proof-ops/jsonldUtil.mjs";
import { SignatureSuiteRegistry } from "./SignatureSuiteRegistry.mjs";
import { createKmsKeyPairClass } from "../../../crypto/KmsKeyPair.mjs";
import { W3cJsonLdCredentialSigningNotSupportedError } from "./W3cJsonLdCredentialSigningNotSupportedError.mjs";
//#region src/modules/vc/linked-data-proofs/W3cJsonLdCredentialService.ts
let W3cJsonLdCredentialService = class W3cJsonLdCredentialService {
constructor(signatureSuiteRegistry, jsonLdModuleConfig) {
this.signatureSuiteRegistry = signatureSuiteRegistry;
this.jsonLdModuleConfig = jsonLdModuleConfig;
}
/**
* Signs a credential
*/
async signCredential(agentContext, options) {
const { suite } = await this.prepareSigningSuite(agentContext, {
proofType: options.proofType,
verificationMethodId: options.verificationMethod,
allowedPurposes: options.proofPurpose?.term ? [options.proofPurpose.term] : ["assertionMethod"],
date: options.created ?? w3cDate(),
controller: options.credential.issuerId
});
try {
const result = await vc_adapter_default.issue({
credential: JsonTransformer.toJSON(options.credential),
suite,
purpose: options.proofPurpose,
documentLoader: this.jsonLdModuleConfig.documentLoader(agentContext)
});
return JsonTransformer.fromJSON(result, W3cJsonLdVerifiableCredential);
} catch (error) {
throw new CredoError(`Error issuing W3C JSON-LD VC. ${error.message}`, { cause: error });
}
}
/**
* Validate that the agent can prepare to sign a credential with the given proof type and verification method.
*/
async assertCanSignCredential(agentContext, options) {
await this.prepareSigningSuite(agentContext, {
proofType: options.proofType,
verificationMethodId: options.verificationMethod,
allowedPurposes: ["assertionMethod"],
controller: options.controller
});
}
/**
* Verifies the signature(s) of a credential
*
* @param credential the credential to be verified
* @returns the verification result
*/
async verifyCredential(agentContext, options) {
try {
this.assertNoAnonCredsW3cCredentialProof(options.credential.proof);
const verifyCredentialStatus = options.verifyCredentialStatus ?? true;
await this.ensureCredentialIssuerTrusted(agentContext, options.credential, options.trustedIssuers);
const suites = this.getSignatureSuitesForCredential(agentContext, options.credential);
const verifyOptions = {
credential: JsonTransformer.toJSON(options.credential),
suite: suites,
documentLoader: this.jsonLdModuleConfig.documentLoader(agentContext),
checkStatus: ({ credential }) => {
if (verifyCredentialStatus && "credentialStatus" in credential) throw new CredoError("Verifying credential status for JSON-LD credentials is currently not supported");
return { verified: true };
}
};
if (options.proofPurpose) verifyOptions.purpose = options.proofPurpose;
const result = await vc_adapter_default.verifyCredential(verifyOptions);
const { verified: isValid, ...remainingResult } = result;
if (!isValid) agentContext.config.logger.debug(`Credential verification failed: ${result.error?.message}`, { stack: result.error?.stack });
return {
isValid,
validations: { vcJs: {
isValid,
...remainingResult
} },
error: result.error
};
} catch (error) {
return {
isValid: false,
validations: {},
error
};
}
}
/**
* Signs a presentation including the credentials it includes
*
* @param presentation the presentation to be signed
* @returns the signed presentation
*/
async signPresentation(agentContext, options) {
const { suite } = await this.prepareSigningSuite(agentContext, {
proofType: options.proofType,
verificationMethodId: options.verificationMethod,
allowedPurposes: options.proofPurpose?.term ? [options.proofPurpose.term] : ["authentication"],
date: (/* @__PURE__ */ new Date()).toISOString()
});
const signOptions = {
presentation: JsonTransformer.toJSON(options.presentation),
suite,
challenge: options.challenge,
domain: options.domain,
documentLoader: this.jsonLdModuleConfig.documentLoader(agentContext)
};
if (options.proofPurpose) signOptions.purpose = options.proofPurpose;
const result = await vc_adapter_default.signPresentation(signOptions);
return JsonTransformer.fromJSON(result, W3cJsonLdVerifiablePresentation);
}
/**
* Verifies a presentation including the credentials it includes
*
* @param presentation the presentation to be verified
* @returns the verification result
*/
async verifyPresentation(agentContext, options) {
try {
this.assertNoAnonCredsW3cCredentialProof(options.presentation.proof);
const WalletKeyPair = createKmsKeyPairClass(agentContext);
let proofs = options.presentation.proof;
if (!Array.isArray(proofs)) proofs = [proofs];
if (options.purpose) proofs = proofs.filter((proof) => proof.proofPurpose === options.purpose.term);
const presentationSuites = proofs.map((proof) => {
const SuiteClass = this.signatureSuiteRegistry.getByProofType(proof.type).suiteClass;
return new SuiteClass({
LDKeyClass: WalletKeyPair,
proof: { verificationMethod: proof.verificationMethod },
date: proof.created,
useNativeCanonize: false
});
});
const credentials = asArray(options.presentation.verifiableCredential);
assertOnlyW3cJsonLdVerifiableCredentials(credentials);
for (const credential of credentials) {
this.assertNoAnonCredsW3cCredentialProof(credential.proof);
await this.ensureCredentialIssuerTrusted(agentContext, credential, options.trustedIssuers);
}
const credentialSuites = credentials.map((credential) => this.getSignatureSuitesForCredential(agentContext, credential));
const allSuites = presentationSuites.concat(...credentialSuites);
const verifyOptions = {
presentation: JsonTransformer.toJSON(options.presentation),
suite: allSuites,
challenge: options.challenge,
domain: options.domain,
documentLoader: this.jsonLdModuleConfig.documentLoader(agentContext)
};
if (options.purpose) verifyOptions.presentationPurpose = options.purpose;
const result = await vc_adapter_default.verify(verifyOptions);
const { verified: isValid, ...remainingResult } = result;
const holderProofPurpose = options.purpose?.term ?? "authentication";
const holderVerificationMethods = asArray(options.presentation.proof).filter((proof) => proof.proofPurpose === holderProofPurpose).map((proof) => proof.verificationMethod);
const credentialValidations = credentials.map((credential) => {
const credentialSubjectIds = credential.credentialSubjectIds;
const credentialSubjectAuthentication = credentialSubjectIds.length === 0 || holderVerificationMethods.some((verificationMethod) => validateCredentialSubjectAuthentication(credentialSubjectIds, verificationMethod).isValid) ? { isValid: true } : {
isValid: false,
error: new CredoError("Presentation does not authenticate the credentialSubject of one or more included credentials")
};
return {
isValid: credentialSubjectAuthentication.isValid,
validations: { credentialSubjectAuthentication }
};
});
return {
isValid: isValid && credentialValidations.every((credential) => credential.isValid),
validations: {
vcJs: {
isValid,
...remainingResult
},
credentials: credentialValidations
},
error: result.error ?? credentialValidations.find((credential) => !credential.isValid)?.validations.credentialSubjectAuthentication.error
};
} catch (error) {
return {
isValid: false,
validations: {},
error
};
}
}
getVerificationMethodTypesByProofType(proofType) {
return this.getSigningSuiteInfo(proofType).verificationMethodTypes;
}
async getExpandedTypesForCredential(agentContext, credential) {
const expandedTypes = (await jsonld_default.expand(JsonTransformer.toJSON(credential), { documentLoader: this.jsonLdModuleConfig.documentLoader(agentContext) }))[0]["@type"];
return asArray(expandedTypes);
}
async ensureCredentialIssuerTrusted(agentContext, credential, trustedIssuers) {
const issuerId = credential.issuerId;
if (!issuerId.startsWith("did:")) return;
const issuerDid = parseDid(issuerId).did;
for (const proof of asArray(credential.proof)) {
const verificationMethod = proof.verificationMethod;
if (!verificationMethod.startsWith("did:") || parseDid(verificationMethod).did !== issuerDid) throw new CredoError(`The proof verification method '${verificationMethod}' does not match the credential issuer did '${issuerDid}'.`);
await TrustedIssuerContext.ensureTrustedSigner(agentContext, {
signer: {
method: "did",
didUrl: verificationMethod
},
verification: {
type: "credential",
credential
}
}, trustedIssuers);
}
}
getSigningSuiteInfo(proofType) {
if (!this.signatureSuiteRegistry.supportedProofTypes.includes(proofType)) throw new W3cJsonLdCredentialSigningNotSupportedError(`The requested proofType ${proofType} is not supported`, "unsupported-proof-type");
return this.signatureSuiteRegistry.getByProofType(proofType);
}
async prepareSigningSuite(agentContext, options) {
const suiteInfo = this.getSigningSuiteInfo(options.proofType);
const dids = agentContext.resolve(DidsApi);
let resolved;
try {
resolved = await dids.resolveVerificationMethodFromCreatedDidRecord(options.verificationMethodId, options.allowedPurposes);
} catch (error) {
if (error instanceof RecordNotFoundError) throw new W3cJsonLdCredentialSigningNotSupportedError(error.message, "verification-method-not-controlled", error);
if (error instanceof CredoError && error.message === `Unable to locate verification method with id '${options.verificationMethodId}' in purposes ${options.allowedPurposes}`) throw new W3cJsonLdCredentialSigningNotSupportedError(error.message, "verification-method-not-found", error);
throw error;
}
const { verificationMethod: nativeVerificationMethod, publicJwk } = resolved;
if (!suiteInfo.verificationMethodTypes.includes(nativeVerificationMethod.type)) throw new W3cJsonLdCredentialSigningNotSupportedError(`Unsupported verification method type '${nativeVerificationMethod.type}' for proof type '${options.proofType}'. Supported types are: ${suiteInfo.verificationMethodTypes.join(", ")}`, "unsupported-verification-method-type");
const loaderResult = await this.jsonLdModuleConfig.documentLoader(agentContext)(options.verificationMethodId);
const framedVerificationMethod = typeof loaderResult.document === "string" ? JSON.parse(loaderResult.document) : loaderResult.document;
const WalletKeyPair = createKmsKeyPairClass(agentContext);
const SuiteClass = suiteInfo.suiteClass;
await new SuiteClass({
LDKeyClass: WalletKeyPair,
proof: { verificationMethod: options.verificationMethodId },
useNativeCanonize: false,
date: options.date ?? w3cDate()
}).assertVerificationMethod(framedVerificationMethod);
if (!this.signatureSuiteRegistry.getAllByPublicJwkType(publicJwk).some(({ suiteClass }) => suiteClass === suiteInfo.suiteClass)) throw new W3cJsonLdCredentialSigningNotSupportedError("The key type of the verification method does not match the suite", "unsupported-key-type");
const keyPair = new WalletKeyPair({
controller: options.controller ?? framedVerificationMethod.controller ?? nativeVerificationMethod.controller,
id: options.verificationMethodId,
publicJwk
});
return {
suite: new SuiteClass({
key: keyPair,
LDKeyClass: WalletKeyPair,
proof: { verificationMethod: options.verificationMethodId },
useNativeCanonize: false,
date: options.date ?? w3cDate()
}),
suiteInfo,
keyPair,
WalletKeyPair
};
}
getSignatureSuitesForCredential(agentContext, credential) {
const WalletKeyPair = createKmsKeyPairClass(agentContext);
let proofs = credential.proof;
if (!Array.isArray(proofs)) proofs = [proofs];
return proofs.map((proof) => {
const SuiteClass = this.signatureSuiteRegistry.getByProofType(proof.type).suiteClass;
return new SuiteClass({
LDKeyClass: WalletKeyPair,
proof: { verificationMethod: proof.verificationMethod },
date: proof.created,
useNativeCanonize: false
});
});
}
assertNoAnonCredsW3cCredentialProof(proofsInput) {
if (asArray(proofsInput).some((proof) => proof.type === "DataIntegrityProof" && "cryptosuite" in proof && proof.cryptosuite === "anoncreds-2023")) throw new CredoError("W3C credential proof with cryptosuite anoncreds-2023 must be verified through the anoncreds W3C credential path, not the generic linked-data verifier");
}
};
W3cJsonLdCredentialService = __decorate([injectable(), __decorateMetadata("design:paramtypes", [typeof SignatureSuiteRegistry === "undefined" ? Object : SignatureSuiteRegistry, typeof JsonLdModuleConfig === "undefined" ? Object : JsonLdModuleConfig])], W3cJsonLdCredentialService);
//#endregion
export { W3cJsonLdCredentialService };
//# sourceMappingURL=W3cJsonLdCredentialService.mjs.map