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

293 lines (292 loc) • 15 kB
import { CredoError } from "../../../error/CredoError.mjs"; import "../../../error/index.mjs"; import { asArray } from "../../../utils/array.mjs"; import { TypedArrayEncoder } from "../../../utils/TypedArrayEncoder.mjs"; import { MessageValidator } from "../../../utils/MessageValidator.mjs"; import { JsonTransformer } from "../../../utils/JsonTransformer.mjs"; import { nowInSeconds } from "../../../utils/timestamp.mjs"; import "../../../utils/index.mjs"; import { JwtPayload } from "../../../crypto/jose/jwt/JwtPayload.mjs"; import { injectable } from "../../../plugins/index.mjs"; import __decorate from "../../../_virtual/_@oxc-project_runtime@0.151.0/helpers/esm/decorate.mjs"; import { KeyManagementApi } from "../../kms/KeyManagementApi.mjs"; import "../../kms/index.mjs"; import { getPublicJwkFromVerificationMethod } from "../../dids/domain/key-type/keyDidMapping.mjs"; import { TrustedIssuerContext } from "../../../agent/TrustedIssuerContext.mjs"; import { validateVc2ContextBaseline, validateVc2CredentialStatus, validateVc2CredentialValidityPeriod } from "../validators.mjs"; import "../../../crypto/index.mjs"; import { extractKeyFromHolderBinding, getSdJwtHashingAlgorithm, getSdJwtSigner, getSdJwtVerifier, parseHolderBindingFromCredential, setJwkAlgFromJwtHeader } from "../../sd-jwt-vc/utils.mjs"; import { sdJwtVcHasher } from "./W3cV2SdJwt.mjs"; import { W3cV2SdJwtVerifiableCredential } from "./W3cV2SdJwtVerifiableCredential.mjs"; import { W3cV2SdJwtVerifiablePresentation } from "./W3cV2SdJwtVerifiablePresentation.mjs"; import { extractHolderFromPresentationCredentials, getVerificationMethodForJwt, validateAndResolveVerificationMethod } from "../v2-jwt-utils.mjs"; import { validateW3cV2SdJwtDisclosureFrame } from "./disclosureFrame.mjs"; import { applyDisclosuresForPaths, applyDisclosuresForPayload } from "../../sd-jwt-vc/disclosureFrame.mjs"; import { SDJwtInstance } from "@sd-jwt/core"; //#region src/modules/vc/sd-jwt-vc/W3cV2SdJwtCredentialService.ts let W3cV2SdJwtCredentialService = class W3cV2SdJwtCredentialService { /** * Signs a credential */ async signCredential(agentContext, options) { MessageValidator.validateSync(options.credential); const payload = JsonTransformer.toJSON(options.credential); payload.iat = nowInSeconds(); payload.cnf = options.holder ? (await extractKeyFromHolderBinding(agentContext, options.holder)).cnf : void 0; const publicJwk = await validateAndResolveVerificationMethod(agentContext, options.verificationMethod, ["assertionMethod"]); const disclosureFrame = options.disclosureFrame; this.validateDisclosureFrame(disclosureFrame); publicJwk.alg = options.alg; const compact = await new SDJwtInstance({ ...this.getBaseSdJwtConfig(agentContext), signer: getSdJwtSigner(agentContext, publicJwk), hashAlg: getSdJwtHashingAlgorithm(options.hashingAlgorithm), signAlg: options.alg }).issue(payload, disclosureFrame, { header: { typ: "vc+sd-jwt", alg: options.alg, kid: options.verificationMethod } }); return W3cV2SdJwtVerifiableCredential.fromCompact(compact); } /** * Verifies the signature(s) of a credential * * @param credential the credential to be verified * @returns the verification result */ async verifyCredential(agentContext, options) { const validationResults = { isValid: false, validations: {} }; const sdJwt = new SDJwtInstance({ ...this.getBaseSdJwtConfig(agentContext) }); try { let credential; try { if (options.credential instanceof W3cV2SdJwtVerifiableCredential) options.credential.validate(); credential = options.credential instanceof W3cV2SdJwtVerifiableCredential ? options.credential : W3cV2SdJwtVerifiableCredential.fromCompact(options.credential); JwtPayload.fromJson(credential.sdJwt.payload).validate({ skewSeconds: agentContext.config.validitySkewSeconds }); this.logCredentialShouldWarnings(agentContext, credential); validationResults.validations.dataModel = validateVc2ContextBaseline(credential.resolvedCredential.context); if (!validationResults.validations.dataModel.isValid) return validationResults; validationResults.validations.dataModel = { isValid: true }; validationResults.validations.validityPeriod = validateVc2CredentialValidityPeriod({ validFrom: credential.resolvedCredential.validFrom, validUntil: credential.resolvedCredential.validUntil, skewSeconds: agentContext.config.validitySkewSeconds }); validationResults.validations.credentialStatus = validateVc2CredentialStatus({ credentialStatus: credential.resolvedCredential.credentialStatus, credentialFormat: "SD-JWT", verifyCredentialStatus: options.verifyCredentialStatus }); } catch (error) { validationResults.validations.dataModel = { isValid: false, error }; return validationResults; } const issuerVerificationMethod = await getVerificationMethodForJwt(agentContext, credential, ["assertionMethod"]); const issuerPublicKey = getPublicJwkFromVerificationMethod(issuerVerificationMethod); await TrustedIssuerContext.ensureTrustedSigner(agentContext, { signer: { method: "did", didUrl: issuerVerificationMethod.id }, verification: { type: "credential", credential } }, options.trustedIssuers); const holderBinding = parseHolderBindingFromCredential(credential.sdJwt.prettyClaims); const holder = holderBinding ? await extractKeyFromHolderBinding(agentContext, holderBinding) : void 0; setJwkAlgFromJwtHeader(issuerPublicKey, credential.sdJwt.header.alg); if (holder && credential.sdJwt.kbJwt) setJwkAlgFromJwtHeader(holder.publicJwk, credential.sdJwt.kbJwt.header?.alg); sdJwt.config({ verifier: getSdJwtVerifier(agentContext, issuerPublicKey), kbVerifier: holder ? getSdJwtVerifier(agentContext, holder.publicJwk) : void 0 }); try { await sdJwt.verify(credential.encoded, { skewSeconds: agentContext.config.validitySkewSeconds }); validationResults.validations.signature = { isValid: true }; } catch (error) { validationResults.validations.signature = { isValid: false, error }; } if (credential.resolvedCredential.issuerId !== issuerVerificationMethod.controller) validationResults.validations.issuerIsSigner = { isValid: false, error: new CredoError(`Credential is signed using verification method ${issuerVerificationMethod.id}, while the issuer of the credential is '${credential.resolvedCredential.issuerId}'`) }; else validationResults.validations.issuerIsSigner = { isValid: true }; validationResults.isValid = Object.values(validationResults.validations).every((validation) => validation?.isValid === true); return validationResults; } catch (error) { validationResults.error = error; return validationResults; } } /** * Signs a presentation including the credentials it includes * * @param presentation the presentation to be signed * @returns the signed presentation */ async signPresentation(agentContext, options) { MessageValidator.validateSync(options.presentation); const payload = JsonTransformer.toJSON(options.presentation); payload.nonce = options.challenge; payload.aud = options.domain; const holder = await extractHolderFromPresentationCredentials(agentContext, options.presentation); holder.publicJwk.alg = holder.alg; const sdJwt = new SDJwtInstance({ ...this.getBaseSdJwtConfig(agentContext), signer: getSdJwtSigner(agentContext, holder.publicJwk), hashAlg: getSdJwtHashingAlgorithm(options.hashingAlgorithm), signAlg: holder.alg }); const disclosureFrame = options.disclosureFrame; this.validateDisclosureFrame(disclosureFrame); const compact = await sdJwt.issue(payload, disclosureFrame, { header: { typ: "vp+sd-jwt", alg: holder.alg, kid: holder?.cnf?.kid } }); return W3cV2SdJwtVerifiablePresentation.fromCompact(compact); } /** * Verifies a presentation including the credentials it includes * * @param presentation the presentation to be verified * @returns the verification result */ async verifyPresentation(agentContext, options) { const validationResults = { isValid: false, presentation: { isValid: false, validations: {} }, credentialEntries: [] }; const sdjwt = new SDJwtInstance({ ...this.getBaseSdJwtConfig(agentContext) }); try { let presentation; try { if (options.presentation instanceof W3cV2SdJwtVerifiablePresentation) options.presentation.validate(); presentation = options.presentation instanceof W3cV2SdJwtVerifiablePresentation ? options.presentation : W3cV2SdJwtVerifiablePresentation.fromCompact(options.presentation); JwtPayload.fromJson(presentation.sdJwt.payload).validate({ skewSeconds: agentContext.config.validitySkewSeconds }); this.logPresentationShouldWarnings(agentContext, presentation); if (options.challenge !== presentation.sdJwt.prettyClaims.nonce) throw new CredoError(`JWT payload 'nonce' does not match challenge '${options.challenge}'`); const audArray = asArray(presentation.sdJwt.prettyClaims.aud); if (options.domain && !audArray.includes(options.domain)) throw new CredoError(`JWT payload 'aud' does not include domain '${options.domain}'`); const contextValidationResult = validateVc2ContextBaseline(presentation.resolvedPresentation.context); if (!contextValidationResult.isValid) throw contextValidationResult.error; validationResults.presentation.validations.dataModel = { isValid: true }; } catch (error) { validationResults.presentation.validations.dataModel = { isValid: false, error }; return validationResults; } const proverVerificationMethod = await getVerificationMethodForJwt(agentContext, presentation, ["authentication"]); const proverPublicKey = getPublicJwkFromVerificationMethod(proverVerificationMethod); const holderBinding = parseHolderBindingFromCredential(presentation.sdJwt.prettyClaims); const holder = holderBinding ? await extractKeyFromHolderBinding(agentContext, holderBinding) : void 0; setJwkAlgFromJwtHeader(proverPublicKey, presentation.sdJwt.header.alg); if (holder && presentation.sdJwt.kbJwt) setJwkAlgFromJwtHeader(holder.publicJwk, presentation.sdJwt.kbJwt.header?.alg); sdjwt.config({ verifier: getSdJwtVerifier(agentContext, proverPublicKey), kbVerifier: holder ? getSdJwtVerifier(agentContext, holder.publicJwk) : void 0 }); try { await sdjwt.verify(presentation.encoded, { skewSeconds: agentContext.config.validitySkewSeconds }); validationResults.presentation.validations.presentationSignature = { isValid: true }; } catch (error) { validationResults.presentation.validations.presentationSignature = { isValid: false, error }; } if (presentation.resolvedPresentation.holderId && proverVerificationMethod.controller !== presentation.resolvedPresentation.holderId) validationResults.presentation.validations.holderIsSigner = { isValid: false, error: new CredoError(`Presentation is signed using verification method ${proverVerificationMethod.id}, while the holder of the presentation is '${presentation.resolvedPresentation.holderId}'`) }; else validationResults.presentation.validations.holderIsSigner = { isValid: true }; validationResults.presentation.isValid = Object.values(validationResults.presentation.validations).every((validation) => validation.isValid); validationResults.isValid = validationResults.presentation.isValid; return validationResults; } catch (error) { validationResults.error = error; return validationResults; } } async present(agentContext, options) { const originalCompact = options.credential instanceof W3cV2SdJwtVerifiableCredential ? options.credential.encoded : options.credential; const presentationFrame = options.presentationFrame; const disclosedCompact = await new SDJwtInstance(this.getBaseSdJwtConfig(agentContext)).present(originalCompact, presentationFrame); return W3cV2SdJwtVerifiableCredential.fromCompact(disclosedCompact); } /** * @deprecated use `applyDisclosuresForPaths` instead */ applyDisclosuresForPayload(compactSdJwtVc, requestedPayload) { const sdJwt = applyDisclosuresForPayload(compactSdJwtVc, requestedPayload); return W3cV2SdJwtVerifiableCredential.fromCompact(sdJwt); } applyDisclosuresForPaths(compactSdJwtVc, disclosedPaths) { const { compact } = applyDisclosuresForPaths(compactSdJwtVc, disclosedPaths); return W3cV2SdJwtVerifiableCredential.fromCompact(compact); } validateDisclosureFrame(disclosureFrame) { validateW3cV2SdJwtDisclosureFrame(disclosureFrame); } getBaseSdJwtConfig(agentContext) { const kms = agentContext.resolve(KeyManagementApi); return { hasher: sdJwtVcHasher, saltGenerator: (length) => TypedArrayEncoder.toBase64Url(kms.randomBytes({ length })).slice(0, length) }; } logCredentialShouldWarnings(agentContext, credential) { const payload = credential.sdJwt.prettyClaims; const headerIss = credential.sdJwt.header.iss; if (typeof headerIss === "string" && typeof payload.iss === "string" && headerIss !== payload.iss) agentContext.config.logger.warn("VC-JOSE-COSE SHOULD warning: JOSE header iss conflicts with payload iss", { format: "vc+sd-jwt", headerIss, payloadIss: payload.iss }); if (typeof payload.jti === "string" && credential.resolvedCredential.id && credential.resolvedCredential.id !== payload.jti) agentContext.config.logger.warn("VC-JOSE-COSE SHOULD warning: jti claim conflicts with credential id", { format: "vc+sd-jwt", jti: payload.jti, credentialId: credential.resolvedCredential.id }); if (typeof payload.sub === "string" && !credential.resolvedCredential.credentialSubjectIds.includes(payload.sub)) agentContext.config.logger.warn("VC-JOSE-COSE SHOULD warning: sub claim does not match any credentialSubject.id", { format: "vc+sd-jwt", sub: payload.sub, credentialSubjectIds: credential.resolvedCredential.credentialSubjectIds }); } logPresentationShouldWarnings(agentContext, presentation) { const payload = presentation.sdJwt.prettyClaims; const headerIss = presentation.sdJwt.header.iss; if (typeof headerIss === "string" && typeof payload.iss === "string" && headerIss !== payload.iss) agentContext.config.logger.warn("VC-JOSE-COSE SHOULD warning: JOSE header iss conflicts with payload iss", { format: "vp+sd-jwt", headerIss, payloadIss: payload.iss }); if (typeof payload.jti === "string" && presentation.resolvedPresentation.id && presentation.resolvedPresentation.id !== payload.jti) agentContext.config.logger.warn("VC-JOSE-COSE SHOULD warning: jti claim conflicts with presentation id", { format: "vp+sd-jwt", jti: payload.jti, presentationId: presentation.resolvedPresentation.id }); } }; W3cV2SdJwtCredentialService = __decorate([injectable()], W3cV2SdJwtCredentialService); //#endregion export { W3cV2SdJwtCredentialService }; //# sourceMappingURL=W3cV2SdJwtCredentialService.mjs.map