@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
250 lines (246 loc) • 11.9 kB
JavaScript
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.112.0/helpers/decorate.mjs";
import { KeyManagementApi } from "../../kms/KeyManagementApi.mjs";
import "../../kms/index.mjs";
import { getPublicJwkFromVerificationMethod } from "../../dids/domain/key-type/keyDidMapping.mjs";
import "../../../crypto/index.mjs";
import { sdJwtVcHasher } from "./W3cV2SdJwt.mjs";
import { W3cV2SdJwtVerifiableCredential } from "./W3cV2SdJwtVerifiableCredential.mjs";
import { extractKeyFromHolderBinding, getSdJwtSigner, getSdJwtVerifier, parseHolderBindingFromCredential } from "../../sd-jwt-vc/utils.mjs";
import { W3cV2SdJwtVerifiablePresentation } from "./W3cV2SdJwtVerifiablePresentation.mjs";
import { extractHolderFromPresentationCredentials, getVerificationMethodForJwt, validateAndResolveVerificationMethod } from "../v2-jwt-utils.mjs";
import { SDJwtInstance } from "@sd-jwt/core";
//#region src/modules/vc/sd-jwt-vc/W3cV2SdJwtCredentialService.ts
/**
* List of fields that cannot be selectively disclosed.
*
* @see https://www.w3.org/TR/vc-jose-cose/#securing-with-sd-jwt
* @see https://www.w3.org/TR/vc-jose-cose/#securing-vps-sd-jwt
*/
const NON_DISCLOSEABLE_FIELDS = [
"@context",
"type",
"credentialStatus",
"credentialSchema",
"relatedResource"
];
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);
const compact = await new SDJwtInstance({
...this.getBaseSdJwtConfig(agentContext),
signer: getSdJwtSigner(agentContext, publicJwk),
hashAlg: options.hashingAlgorithm ?? "sha-256",
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 });
validationResults.validations.dataModel = { isValid: true };
} catch (error) {
validationResults.validations.dataModel = {
isValid: false,
error
};
return validationResults;
}
const issuerVerificationMethod = await getVerificationMethodForJwt(agentContext, credential, ["assertionMethod"]);
const issuerPublicKey = getPublicJwkFromVerificationMethod(issuerVerificationMethod);
const holderBinding = parseHolderBindingFromCredential(credential.sdJwt.prettyClaims);
const holder = holderBinding ? await extractKeyFromHolderBinding(agentContext, holderBinding) : void 0;
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((v) => v.isValid);
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);
const sdJwt = new SDJwtInstance({
...this.getBaseSdJwtConfig(agentContext),
signer: getSdJwtSigner(agentContext, holder.publicJwk),
hashAlg: options.hashingAlgorithm ?? "sha-256",
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,
validations: {}
};
const sdjwt = new SDJwtInstance({ ...this.getBaseSdJwtConfig(agentContext) });
try {
let presentation;
try {
if (options.presentation instanceof W3cV2SdJwtVerifiablePresentation) MessageValidator.validateSync(options.presentation.resolvedPresentation);
presentation = options.presentation instanceof W3cV2SdJwtVerifiablePresentation ? options.presentation : W3cV2SdJwtVerifiablePresentation.fromCompact(options.presentation);
JwtPayload.fromJson(presentation.sdJwt.payload).validate({ skewSeconds: agentContext.config.validitySkewSeconds });
validationResults.validations.dataModel = { isValid: true };
} catch (error) {
validationResults.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;
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.validations.presentationSignature = { isValid: true };
} catch (error) {
validationResults.validations.presentationSignature = {
isValid: false,
error
};
}
if (presentation.resolvedPresentation.holderId && proverVerificationMethod.controller !== presentation.resolvedPresentation.holderId) validationResults.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.validations.holderIsSigner = { isValid: true };
const credentials = asArray(presentation.resolvedPresentation.verifiableCredential);
validationResults.validations.credentials = await Promise.all(credentials.map(async (credential) => {
if (!(credential.envelopedCredential instanceof W3cV2SdJwtVerifiableCredential)) return {
isValid: false,
error: new CredoError("Credential is not of format SD-JWT. Presentations in SD-JWT format can only contain credentials in SD-JWT format."),
validations: {}
};
const credentialResult = await this.verifyCredential(agentContext, { credential: credential.envelopedCredential });
let credentialSubjectAuthentication;
const credentialSubjectIds = credential.resolvedCredential.credentialSubjectIds;
const presentationAuthenticatesCredentialSubject = credentialSubjectIds.some((subjectId) => proverVerificationMethod.controller === subjectId);
if (credentialSubjectIds.length > 0 && !presentationAuthenticatesCredentialSubject) credentialSubjectAuthentication = {
isValid: false,
error: new CredoError("Credential has one or more credentialSubject ids, but presentation does not authenticate credential subject")
};
else credentialSubjectAuthentication = { isValid: true };
return {
...credentialResult,
isValid: credentialResult.isValid && credentialSubjectAuthentication.isValid,
validations: {
...credentialResult.validations,
credentialSubjectAuthentication
}
};
}));
validationResults.isValid = Object.values(validationResults.validations).every((v) => Array.isArray(v) ? v.every((vv) => vv.isValid) : v.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);
}
validateDisclosureFrame(disclosureFrame) {
if (!disclosureFrame) return;
for (const field of NON_DISCLOSEABLE_FIELDS) {
if (disclosureFrame[field]) throw new CredoError(`'${field}' property cannot be selectively disclosed`);
if (Array.isArray(disclosureFrame._sd) && disclosureFrame._sd?.includes(field)) throw new CredoError(`'${field}' property cannot be selectively disclosed`);
}
}
getBaseSdJwtConfig(agentContext) {
const kms = agentContext.resolve(KeyManagementApi);
return {
hasher: sdJwtVcHasher,
saltGenerator: (length) => TypedArrayEncoder.toBase64Url(kms.randomBytes({ length })).slice(0, length)
};
}
};
W3cV2SdJwtCredentialService = __decorate([injectable()], W3cV2SdJwtCredentialService);
//#endregion
export { W3cV2SdJwtCredentialService };
//# sourceMappingURL=W3cV2SdJwtCredentialService.mjs.map