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

1 lines • 20 kB
{"version":3,"file":"W3cV2JwtCredentialService.mjs","names":[],"sources":["../../../../src/modules/vc/jwt-vc/W3cV2JwtCredentialService.ts"],"sourcesContent":["import type { AgentContext } from '../../../agent/context'\nimport { JwsService, JwtPayload } from '../../../crypto'\nimport type { VerifyJwsResult } from '../../../crypto/JwsService'\nimport { CredoError } from '../../../error'\nimport { injectable } from '../../../plugins'\nimport { asArray, JsonTransformer, MessageValidator, nowInSeconds } from '../../../utils'\nimport { getPublicJwkFromVerificationMethod } from '../../dids/domain/key-type/keyDidMapping'\nimport { extractKeyFromHolderBinding } from '../../sd-jwt-vc/utils'\nimport type { SingleValidationResult, W3cV2VerifyCredentialResult, W3cV2VerifyPresentationResult } from '../models'\nimport {\n extractHolderFromPresentationCredentials,\n getVerificationMethodForJwt,\n validateAndResolveVerificationMethod,\n} from '../v2-jwt-utils'\nimport type {\n W3cV2JwtSignCredentialOptions,\n W3cV2JwtSignPresentationOptions,\n W3cV2JwtVerifyCredentialOptions,\n W3cV2JwtVerifyPresentationOptions,\n} from '../W3cV2CredentialServiceOptions'\nimport { W3cV2JwtVerifiableCredential } from './W3cV2JwtVerifiableCredential'\nimport { W3cV2JwtVerifiablePresentation } from './W3cV2JwtVerifiablePresentation'\n\n/**\n * Supports signing and verifying W3C Verifiable Credentials and Presentations\n * secured with JSON Web Tokens (JWT).\n *\n * @see https://www.w3.org/TR/vc-data-model/\n * @see https://www.w3.org/TR/vc-jose-cose/#with-jose\n */\n@injectable()\nexport class W3cV2JwtCredentialService {\n private jwsService: JwsService\n\n public constructor(jwsService: JwsService) {\n this.jwsService = jwsService\n }\n\n /**\n * Signs a credential\n */\n public async signCredential(\n agentContext: AgentContext,\n options: W3cV2JwtSignCredentialOptions\n ): Promise<W3cV2JwtVerifiableCredential> {\n // Validate the instance\n MessageValidator.validateSync(options.credential)\n\n // The JWT payload is simply the credential\n const jwtPayload = new JwtPayload({\n additionalClaims: JsonTransformer.toJSON(options.credential),\n })\n\n // Add iat and cnf to the payload\n jwtPayload.iat = nowInSeconds()\n jwtPayload.additionalClaims.cnf = options.holder\n ? (await extractKeyFromHolderBinding(agentContext, options.holder)).cnf\n : undefined\n\n // Validate and resolve the verification method\n const publicJwk = await validateAndResolveVerificationMethod(agentContext, options.verificationMethod, [\n 'assertionMethod',\n ])\n\n // Sign the JWT\n const jwt = await this.jwsService.createJwsCompact(agentContext, {\n payload: jwtPayload,\n keyId: publicJwk.keyId,\n protectedHeaderOptions: {\n typ: 'vc+jwt',\n alg: options.alg,\n kid: options.verificationMethod,\n },\n })\n\n return W3cV2JwtVerifiableCredential.fromCompact(jwt)\n }\n\n /**\n * Verifies the signature(s) of a credential\n *\n * @param credential the credential to be verified\n * @returns the verification result\n */\n public async verifyCredential(\n agentContext: AgentContext,\n options: W3cV2JwtVerifyCredentialOptions\n ): Promise<W3cV2VerifyCredentialResult> {\n const validationResults: W3cV2VerifyCredentialResult = {\n isValid: false,\n validations: {},\n }\n\n try {\n let credential: W3cV2JwtVerifiableCredential\n try {\n // If instance is provided as input, we want to validate the credential.\n // Otherwise, it is done by the fromCompact method below\n if (options.credential instanceof W3cV2JwtVerifiableCredential) {\n options.credential.validate()\n }\n\n credential =\n options.credential instanceof W3cV2JwtVerifiableCredential\n ? options.credential\n : W3cV2JwtVerifiableCredential.fromCompact(options.credential)\n\n // Verify the JWT payload (verifies whether it's not expired, etc...)\n credential.jwt.payload.validate({\n skewSeconds: agentContext.config.validitySkewSeconds,\n })\n\n validationResults.validations.dataModel = {\n isValid: true,\n }\n } catch (error) {\n validationResults.validations.dataModel = {\n isValid: false,\n error,\n }\n\n return validationResults\n }\n\n const issuerVerificationMethod = await getVerificationMethodForJwt(agentContext, credential, ['assertionMethod'])\n const issuerPublicKey = getPublicJwkFromVerificationMethod(issuerVerificationMethod)\n\n let signatureResult: VerifyJwsResult | undefined\n try {\n // Verify the JWS signature\n signatureResult = await this.jwsService.verifyJws(agentContext, {\n jws: credential.jwt.serializedJwt,\n // We have pre-fetched the key based on the issuer/signer of the credential\n jwsSigner: {\n method: 'did',\n jwk: issuerPublicKey,\n didUrl: issuerVerificationMethod.id,\n },\n })\n\n if (!signatureResult.isValid) {\n validationResults.validations.signature = {\n isValid: false,\n error: new CredoError('Invalid JWS signature'),\n }\n } else {\n validationResults.validations.signature = {\n isValid: true,\n }\n }\n } catch (error) {\n validationResults.validations.signature = {\n isValid: false,\n error,\n }\n }\n\n // Validate whether the credential is signed with the 'issuer' id\n // NOTE: this uses the verificationMethod.controller. We may want to use the verificationMethod.id?\n if (credential.resolvedCredential.issuerId !== issuerVerificationMethod.controller) {\n validationResults.validations.issuerIsSigner = {\n isValid: false,\n error: new CredoError(\n `Credential is signed using verification method ${issuerVerificationMethod.id}, while the issuer of the credential is '${credential.resolvedCredential.issuerId}'`\n ),\n }\n } else {\n validationResults.validations.issuerIsSigner = {\n isValid: true,\n }\n }\n\n // Validate whether the `issuer` of the credential is also the signer\n const issuerIsSigner = signatureResult?.jwsSigners.some(\n (jwsSigner) => jwsSigner.jwk.fingerprint === issuerPublicKey.fingerprint\n )\n if (!issuerIsSigner) {\n validationResults.validations.issuerIsSigner = {\n isValid: false,\n error: new CredoError('Credential is not signed by the issuer of the credential'),\n }\n } else {\n validationResults.validations.issuerIsSigner = {\n isValid: true,\n }\n }\n\n validationResults.isValid = Object.values(validationResults.validations).every((v) => v.isValid)\n\n return validationResults\n } catch (error) {\n validationResults.error = error\n return validationResults\n }\n }\n\n /**\n * Signs a presentation including the credentials it includes\n *\n * @param presentation the presentation to be signed\n * @returns the signed presentation\n */\n public async signPresentation(\n agentContext: AgentContext,\n options: W3cV2JwtSignPresentationOptions\n ): Promise<W3cV2JwtVerifiablePresentation> {\n // Validate the instance\n MessageValidator.validateSync(options.presentation)\n\n // The JWT payload is simply the presentation\n const jwtPayload = new JwtPayload({\n additionalClaims: JsonTransformer.toJSON(options.presentation),\n })\n\n // Add the nonce and aud to the payload\n jwtPayload.additionalClaims.nonce = options.challenge\n jwtPayload.aud = options.domain\n\n const holder = await extractHolderFromPresentationCredentials(agentContext, options.presentation)\n\n // Sign JWT\n const jwt = await this.jwsService.createJwsCompact(agentContext, {\n payload: jwtPayload,\n keyId: holder.publicJwk.keyId,\n protectedHeaderOptions: {\n typ: 'vp+jwt',\n alg: holder.alg,\n kid: holder?.cnf?.kid,\n },\n })\n\n return W3cV2JwtVerifiablePresentation.fromCompact(jwt)\n }\n\n /**\n * Verifies a presentation including the credentials it includes\n *\n * @param presentation the presentation to be verified\n * @returns the verification result\n */\n public async verifyPresentation(\n agentContext: AgentContext,\n options: W3cV2JwtVerifyPresentationOptions\n ): Promise<W3cV2VerifyPresentationResult> {\n const validationResults: W3cV2VerifyPresentationResult = {\n isValid: false,\n validations: {},\n }\n\n try {\n let presentation: W3cV2JwtVerifiablePresentation\n try {\n // If instance is provided as input, we want to validate the presentation\n if (options.presentation instanceof W3cV2JwtVerifiablePresentation) {\n options.presentation.validate()\n }\n\n presentation =\n options.presentation instanceof W3cV2JwtVerifiablePresentation\n ? options.presentation\n : W3cV2JwtVerifiablePresentation.fromCompact(options.presentation)\n\n // Verify the JWT payload (verifies whether it's not expired, etc...)\n presentation.jwt.payload.validate({\n skewSeconds: agentContext.config.validitySkewSeconds,\n })\n\n // Make sure challenge matches nonce\n if (options.challenge !== presentation.jwt.payload.additionalClaims.nonce) {\n throw new CredoError(`JWT payload 'nonce' does not match challenge '${options.challenge}'`)\n }\n\n const audArray = asArray(presentation.jwt.payload.aud)\n if (options.domain && !audArray.includes(options.domain)) {\n throw new CredoError(`JWT payload 'aud' does not include domain '${options.domain}'`)\n }\n\n validationResults.validations.dataModel = {\n isValid: true,\n }\n } catch (error) {\n validationResults.validations.dataModel = {\n isValid: false,\n error,\n }\n\n return validationResults\n }\n\n const proverVerificationMethod = await getVerificationMethodForJwt(agentContext, presentation, ['authentication'])\n const proverPublicKey = getPublicJwkFromVerificationMethod(proverVerificationMethod)\n\n let signatureResult: VerifyJwsResult | undefined\n try {\n // Verify the JWS signature\n signatureResult = await this.jwsService.verifyJws(agentContext, {\n jws: presentation.jwt.serializedJwt,\n allowedJwsSignerMethods: ['did'],\n jwsSigner: {\n method: 'did',\n didUrl: proverVerificationMethod.id,\n jwk: proverPublicKey,\n },\n trustedCertificates: [],\n })\n\n if (!signatureResult.isValid) {\n validationResults.validations.presentationSignature = {\n isValid: false,\n error: new CredoError('Invalid JWS signature on presentation'),\n }\n } else {\n validationResults.validations.presentationSignature = {\n isValid: true,\n }\n }\n } catch (error) {\n validationResults.validations.presentationSignature = {\n isValid: false,\n error,\n }\n }\n\n // Validate whether the presentation is signed with the 'holder' id\n // NOTE: this uses the verificationMethod.controller. We may want to use the verificationMethod.id?\n if (\n presentation.resolvedPresentation.holderId &&\n proverVerificationMethod.controller !== presentation.resolvedPresentation.holderId\n ) {\n validationResults.validations.holderIsSigner = {\n isValid: false,\n error: new CredoError(\n `Presentation is signed using verification method ${proverVerificationMethod.id}, while the holder of the presentation is '${presentation.resolvedPresentation.holderId}'`\n ),\n }\n } else {\n // If no holderId is present, this validation passes by default as there can't be\n // a mismatch between the 'holder' property and the signer of the presentation.\n validationResults.validations.holderIsSigner = {\n isValid: true,\n }\n }\n\n // To keep things simple, we only support JWT VCs in JWT VPs for now\n const credentials = asArray(presentation.resolvedPresentation.verifiableCredential)\n\n // Verify all credentials in parallel, and await the result\n validationResults.validations.credentials = await Promise.all(\n credentials.map(async (credential) => {\n if (!(credential.envelopedCredential instanceof W3cV2JwtVerifiableCredential)) {\n return {\n isValid: false,\n error: new CredoError(\n 'Credential is not of format JWT. Presentations in JWT format can only contain credentials in JWT format.'\n ),\n validations: {},\n }\n }\n\n const credentialResult = await this.verifyCredential(agentContext, {\n credential: credential.envelopedCredential,\n })\n\n let credentialSubjectAuthentication: SingleValidationResult\n\n // Check whether any of the credentialSubjectIds for each credential is the same as the controller of the verificationMethod\n // This authenticates the presentation creator controls one of the credentialSubject ids.\n // NOTE: this doesn't take into account the case where the credentialSubject is no the holder. In the\n // future we can add support for other flows, but for now this is the most common use case.\n // TODO: should this be handled on a higher level? I don't really see it being handled in the jsonld lib\n // or in the did-jwt-vc lib (it seems they don't even verify the credentials itself), but we probably need some\n // more experience on the use cases before we loosen the restrictions (as it means we need to handle it on a higher layer).\n const credentialSubjectIds = credential.resolvedCredential.credentialSubjectIds\n const presentationAuthenticatesCredentialSubject = credentialSubjectIds.some(\n (subjectId) => proverVerificationMethod.controller === subjectId\n )\n\n if (credentialSubjectIds.length > 0 && !presentationAuthenticatesCredentialSubject) {\n credentialSubjectAuthentication = {\n isValid: false,\n error: new CredoError(\n 'Credential has one or more credentialSubject ids, but presentation does not authenticate credential subject'\n ),\n }\n } else {\n credentialSubjectAuthentication = {\n isValid: true,\n }\n }\n\n return {\n ...credentialResult,\n isValid: credentialResult.isValid && credentialSubjectAuthentication.isValid,\n validations: {\n ...credentialResult.validations,\n credentialSubjectAuthentication,\n },\n }\n })\n )\n\n // Deeply nested check whether all validations have passed\n validationResults.isValid = Object.values(validationResults.validations).every((v) =>\n Array.isArray(v) ? v.every((vv) => vv.isValid) : v.isValid\n )\n\n return validationResults\n } catch (error) {\n validationResults.error = error\n return validationResults\n }\n }\n}\n"],"mappings":"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"}