@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
266 lines (265 loc) • 12.7 kB
JavaScript
import { CredoError } from "../../../error/CredoError.mjs";
import "../../../error/index.mjs";
import { asArray } from "../../../utils/array.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 __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 { JwsService } from "../../../crypto/JwsService.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 } from "../../sd-jwt-vc/utils.mjs";
import { W3cV2JwtVerifiableCredential } from "./W3cV2JwtVerifiableCredential.mjs";
import { W3cV2JwtVerifiablePresentation } from "./W3cV2JwtVerifiablePresentation.mjs";
import { extractHolderFromPresentationCredentials, getVerificationMethodForJwt, validateAndResolveVerificationMethod } from "../v2-jwt-utils.mjs";
//#region src/modules/vc/jwt-vc/W3cV2JwtCredentialService.ts
let W3cV2JwtCredentialService = class W3cV2JwtCredentialService {
constructor(jwsService) {
this.jwsService = jwsService;
}
/**
* Signs a credential
*/
async signCredential(agentContext, options) {
MessageValidator.validateSync(options.credential);
const jwtPayload = new JwtPayload({ additionalClaims: JsonTransformer.toJSON(options.credential) });
jwtPayload.iat = nowInSeconds();
jwtPayload.additionalClaims.cnf = options.holder ? (await extractKeyFromHolderBinding(agentContext, options.holder)).cnf : void 0;
const publicJwk = await validateAndResolveVerificationMethod(agentContext, options.verificationMethod, ["assertionMethod"]);
const jwt = await this.jwsService.createJwsCompact(agentContext, {
payload: jwtPayload,
keyId: publicJwk.keyId,
protectedHeaderOptions: {
typ: "vc+jwt",
alg: options.alg,
kid: options.verificationMethod
}
});
return W3cV2JwtVerifiableCredential.fromCompact(jwt);
}
/**
* 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: {}
};
try {
let credential;
try {
if (options.credential instanceof W3cV2JwtVerifiableCredential) options.credential.validate();
credential = options.credential instanceof W3cV2JwtVerifiableCredential ? options.credential : W3cV2JwtVerifiableCredential.fromCompact(options.credential);
credential.jwt.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: "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);
let signatureResult;
try {
signatureResult = await this.jwsService.verifyJws(agentContext, {
jws: credential.jwt.serializedJwt,
jwsSigner: {
method: "did",
jwk: issuerPublicKey,
didUrl: issuerVerificationMethod.id
}
});
if (!signatureResult.isValid) validationResults.validations.signature = {
isValid: false,
error: new CredoError("Invalid JWS signature")
};
else 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 };
if (!signatureResult?.jwsSigners.some((jwsSigner) => jwsSigner.jwk.fingerprint === issuerPublicKey.fingerprint)) validationResults.validations.issuerIsSigner = {
isValid: false,
error: new CredoError("Credential is not signed by the issuer of the credential")
};
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 jwtPayload = new JwtPayload({ additionalClaims: JsonTransformer.toJSON(options.presentation) });
jwtPayload.additionalClaims.nonce = options.challenge;
jwtPayload.aud = options.domain;
const holder = await extractHolderFromPresentationCredentials(agentContext, options.presentation);
const jwt = await this.jwsService.createJwsCompact(agentContext, {
payload: jwtPayload,
keyId: holder.publicJwk.keyId,
protectedHeaderOptions: {
typ: "vp+jwt",
alg: holder.alg,
kid: holder?.cnf?.kid
}
});
return W3cV2JwtVerifiablePresentation.fromCompact(jwt);
}
/**
* 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: []
};
try {
let presentation;
try {
if (options.presentation instanceof W3cV2JwtVerifiablePresentation) options.presentation.validate();
presentation = options.presentation instanceof W3cV2JwtVerifiablePresentation ? options.presentation : W3cV2JwtVerifiablePresentation.fromCompact(options.presentation);
presentation.jwt.payload.validate({ skewSeconds: agentContext.config.validitySkewSeconds });
this.logPresentationShouldWarnings(agentContext, presentation);
if (options.challenge !== presentation.jwt.payload.additionalClaims.nonce) throw new CredoError(`JWT payload 'nonce' does not match challenge '${options.challenge}'`);
const audArray = asArray(presentation.jwt.payload.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);
try {
if (!(await this.jwsService.verifyJws(agentContext, {
jws: presentation.jwt.serializedJwt,
allowedJwsSignerMethods: ["did"],
jwsSigner: {
method: "did",
didUrl: proverVerificationMethod.id,
jwk: proverPublicKey
},
trustedCertificates: []
})).isValid) validationResults.presentation.validations.presentationSignature = {
isValid: false,
error: new CredoError("Invalid JWS signature on presentation")
};
else 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;
}
}
logCredentialShouldWarnings(agentContext, credential) {
const payload = credential.jwt.payload;
const headerIss = credential.jwt.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+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+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+jwt",
sub: payload.sub,
credentialSubjectIds: credential.resolvedCredential.credentialSubjectIds
});
}
logPresentationShouldWarnings(agentContext, presentation) {
const payload = presentation.jwt.payload;
const headerIss = presentation.jwt.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+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+jwt",
jti: payload.jti,
presentationId: presentation.resolvedPresentation.id
});
}
};
W3cV2JwtCredentialService = __decorate([injectable(), __decorateMetadata("design:paramtypes", [typeof JwsService === "undefined" ? Object : JwsService])], W3cV2JwtCredentialService);
//#endregion
export { W3cV2JwtCredentialService };
//# sourceMappingURL=W3cV2JwtCredentialService.mjs.map