From 2fc170ba0bfbf99546c5c800e5944c1776a99e84 Mon Sep 17 00:00:00 2001 From: Kulkarni Shashank Date: Tue, 29 Oct 2024 13:27:04 +0530 Subject: [PATCH 01/13] feat: support w3c revocation Signed-off-by: Krishna Waske --- packages/core/package.json | 1 + .../W3cJsonLdCredentialService.ts | 91 ++++++++++++++++++- .../vc/models/credential/W3cJsonCredential.ts | 55 +++++++++++ 3 files changed, 144 insertions(+), 3 deletions(-) diff --git a/packages/core/package.json b/packages/core/package.json index b4f8e333b3..844ec9f999 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -46,6 +46,7 @@ "@sphereon/pex-models": "^2.3.1", "@sphereon/ssi-types": "0.30.2-next.135", "@stablelib/ed25519": "^1.0.2", + "@types/pako": "^2.0.3", "@types/ws": "^8.5.4", "abort-controller": "^3.0.0", "big-integer": "^1.6.51", diff --git a/packages/core/src/modules/vc/data-integrity/W3cJsonLdCredentialService.ts b/packages/core/src/modules/vc/data-integrity/W3cJsonLdCredentialService.ts index 74d174e793..182b4db8d3 100644 --- a/packages/core/src/modules/vc/data-integrity/W3cJsonLdCredentialService.ts +++ b/packages/core/src/modules/vc/data-integrity/W3cJsonLdCredentialService.ts @@ -9,7 +9,9 @@ import type { W3cJsonLdVerifyPresentationOptions, } from '../W3cCredentialServiceOptions' import type { W3cVerifyCredentialResult, W3cVerifyPresentationResult } from '../models' -import type { W3cJsonCredential } from '../models/credential/W3cJsonCredential' +import type { BitStringStatusListCredential, W3cJsonCredential } from '../models/credential/W3cJsonCredential' + +import * as pako from 'pako' import { createWalletKeyPairClass } from '../../../crypto/WalletKeyPair' import { CredoError } from '../../../error' @@ -109,10 +111,47 @@ export class W3cJsonLdCredentialService { credential: JsonTransformer.toJSON(options.credential), suite: suites, documentLoader: this.w3cCredentialsModuleConfig.documentLoader(agentContext), - checkStatus: ({ credential }: { credential: W3cJsonCredential }) => { + checkStatus: async ({ credential }: { credential: W3cJsonCredential }) => { // Only throw error if credentialStatus is present if (verifyCredentialStatus && 'credentialStatus' in credential) { - throw new CredoError('Verifying credential status for JSON-LD credentials is currently not supported') + if (Array.isArray(credential.credentialStatus)) { + throw new CredoError( + 'Verifying credential status as an array for JSON-LD credentials is currently not supported' + ) + } + + // Ensure credentialStatus contains the necessary properties + if (!credential.credentialStatus || credential.credentialStatus.statusListIndex === undefined) { + throw new CredoError('Invalid credential status format') + } + + const credentialStatusURL = credential.credentialStatus.statusListCredential + const bitStringStatusListCredential = await agentContext.config.agentDependencies.fetch( + credentialStatusURL, + { + method: 'GET', + } + ) + + if (!bitStringStatusListCredential.ok) { + throw new CredoError(`HTTP error! Status: ${bitStringStatusListCredential.status}`) + } + const bitStringCredential = + (await bitStringStatusListCredential.json()) as unknown as BitStringStatusListCredential + const encodedBitString = bitStringCredential.credential.credentialSubject.encodedList + const compressedBuffer = Uint8Array.from(atob(encodedBitString), (c) => c.charCodeAt(0)) + + // Decompress using pako + const decodedBitString = pako.ungzip(compressedBuffer, { to: 'string' }) + const statusListIndex = Number(credential.credentialStatus.statusListIndex) + + if (statusListIndex < 0 || statusListIndex >= decodedBitString.length) { + throw new CredoError('Index out of bounds') + } + + if (decodedBitString[statusListIndex] === '1') { + throw new CredoError(`Credential at index ${credential.credentialStatus.statusListIndex} is revoked.`) + } } return { verified: true, @@ -265,6 +304,52 @@ export class W3cJsonLdCredentialService { challenge: options.challenge, domain: options.domain, documentLoader: this.w3cCredentialsModuleConfig.documentLoader(agentContext), + checkStatus: async ({ credential }: { credential: W3cJsonCredential }) => { + // Only throw error if credentialStatus is present + if ('credentialStatus' in credential) { + if (Array.isArray(credential.credentialStatus)) { + throw new CredoError( + 'Verifying credential status as an array for JSON-LD credentials is currently not supported' + ) + } + + // Ensure credentialStatus contains the necessary properties + if (!credential.credentialStatus || credential.credentialStatus.statusListIndex === undefined) { + throw new CredoError('Invalid credential status format') + } + + const credentialStatusURL = credential.credentialStatus.statusListCredential + const bitStringStatusListCredential = await agentContext.config.agentDependencies.fetch( + credentialStatusURL, + { + method: 'GET', + } + ) + + if (!bitStringStatusListCredential.ok) { + throw new CredoError(`HTTP error! Status: ${bitStringStatusListCredential.status}`) + } + const bitStringCredential = + (await bitStringStatusListCredential.json()) as unknown as BitStringStatusListCredential + const encodedBitString = bitStringCredential.credential.credentialSubject.encodedList + const compressedBuffer = Uint8Array.from(atob(encodedBitString), (c) => c.charCodeAt(0)) + + // Decompress using pako + const decodedBitString = pako.ungzip(compressedBuffer, { to: 'string' }) + const statusListIndex = Number(credential.credentialStatus.statusListIndex) + + if (statusListIndex < 0 || statusListIndex >= decodedBitString.length) { + throw new CredoError('Index out of bounds') + } + + if (decodedBitString[statusListIndex] === '1') { + throw new CredoError(`Credential at index ${credential.credentialStatus.statusListIndex} is revoked.`) + } + } + return { + verified: true, + } + }, } // this is a hack because vcjs throws if purpose is passed as undefined or null diff --git a/packages/core/src/modules/vc/models/credential/W3cJsonCredential.ts b/packages/core/src/modules/vc/models/credential/W3cJsonCredential.ts index fa43911d43..abd16ad6de 100644 --- a/packages/core/src/modules/vc/models/credential/W3cJsonCredential.ts +++ b/packages/core/src/modules/vc/models/credential/W3cJsonCredential.ts @@ -10,4 +10,59 @@ export interface W3cJsonCredential { expirationDate?: string credentialSubject: SingleOrArray [key: string]: unknown + credentialStatus?: SingleOrArray +} + +type CredentialStatusType = 'BitstringStatusListEntry' +// The purpose can be anything apart from this as well +export enum CredentialStatusPurpose { + 'revocation' = 'revocation', + 'suspension' = 'suspension', + 'message' = 'message', +} + +export interface StatusMessage { + // a string representing the hexadecimal value of the status prefixed with 0x + status: string + // a string used by software developers to assist with debugging which SHOULD NOT be displayed to end users + message?: string + // We can have some key value pairs as well + [key: string]: unknown +} + +export interface CredentialStatus { + id: string + // Since currenlty we are only trying to support 'BitStringStatusListEntry' + type: CredentialStatusType + statusPurpose: CredentialStatusPurpose + // Unique identifier for the specific credential + statusListIndex: string + // Must be url referencing to a VC of type 'BitstringStatusListCredential' + statusListCredential: string + // The statusSize indicates the size of the status entry in bits + statusSize?: number + // Must be preset if statusPurpose is message + /** + * the length of which MUST equal the number of possible status messages indicated by statusSize + * (e.g., statusMessage array MUST have 2 elements if statusSize has 1 bit, + * 4 elements if statusSize has 2 bits, 8 elements if statusSize has 3 bits, etc.). + */ + statusMessage?: StatusMessage[] + // An implementer MAY include the statusReference property. If present, its value MUST be a URL or an array of URLs [URL] which dereference to material related to the status + statusReference?: SingleOrArray +} + +// Define an interface for `credentialSubject` +export interface CredentialSubject { + encodedList: string +} + +// Define an interface for the `credential` object that uses `CredentialSubject` +export interface Credential { + credentialSubject: CredentialSubject +} + +// Use the `Credential` interface within `BitStringStatusListCredential` +export interface BitStringStatusListCredential { + credential: Credential } From 02c3d922596cea9fd8bd0f8b2fa35608c53ed888 Mon Sep 17 00:00:00 2001 From: KulkarniShashank Date: Tue, 29 Oct 2024 13:40:53 +0530 Subject: [PATCH 02/13] feat: support w3c revocation Signed-off-by: Krishna Waske --- packages/core/package.json | 3 +- .../src/modules/credentials/CredentialsApi.ts | 110 +++++++++++++++++- .../credentials/CredentialsApiOptions.ts | 27 +++++ .../formats/jsonld/JsonLdCredentialFormat.ts | 9 ++ .../VerifyBitStringCredentialStatus.ts | 71 +++++++++++ .../W3cJsonLdCredentialService.ts | 85 +------------- .../models/credential/W3cCredentialStatus.ts | 44 ++++++- .../vc/models/credential/W3cJsonCredential.ts | 55 +-------- .../credential/W3cJsonCredentialStatus.ts | 55 +++++++++ 9 files changed, 319 insertions(+), 140 deletions(-) create mode 100644 packages/core/src/modules/vc/data-integrity/VerifyBitStringCredentialStatus.ts create mode 100644 packages/core/src/modules/vc/models/credential/W3cJsonCredentialStatus.ts diff --git a/packages/core/package.json b/packages/core/package.json index 844ec9f999..82033c7eeb 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -46,7 +46,6 @@ "@sphereon/pex-models": "^2.3.1", "@sphereon/ssi-types": "0.30.2-next.135", "@stablelib/ed25519": "^1.0.2", - "@types/pako": "^2.0.3", "@types/ws": "^8.5.4", "abort-controller": "^3.0.0", "big-integer": "^1.6.51", @@ -60,6 +59,7 @@ "luxon": "^3.5.0", "make-error": "^1.3.6", "object-inspect": "^1.10.3", + "pako": "^2.1.0", "query-string": "^7.0.1", "reflect-metadata": "^0.1.13", "rxjs": "^7.8.0", @@ -73,6 +73,7 @@ "@types/events": "^3.0.0", "@types/luxon": "^3.2.0", "@types/object-inspect": "^1.8.0", + "@types/pako": "^2.0.3", "@types/uuid": "^9.0.1", "@types/varint": "^6.0.0", "rimraf": "^4.4.0", diff --git a/packages/core/src/modules/credentials/CredentialsApi.ts b/packages/core/src/modules/credentials/CredentialsApi.ts index 5a5618b4fc..ad1eb1f4ef 100644 --- a/packages/core/src/modules/credentials/CredentialsApi.ts +++ b/packages/core/src/modules/credentials/CredentialsApi.ts @@ -17,6 +17,9 @@ import type { DeleteCredentialOptions, SendRevocationNotificationOptions, DeclineCredentialOfferOptions, + RevokeCredentialOption, + BitStringCredential, + JsonLdRevocationStatus, } from './CredentialsApiOptions' import type { CredentialProtocol } from './protocol/CredentialProtocol' import type { CredentialFormatsFromProtocols } from './protocol/CredentialProtocolOptions' @@ -24,6 +27,8 @@ import type { CredentialExchangeRecord } from './repository/CredentialExchangeRe import type { AgentMessage } from '../../agent/AgentMessage' import type { Query, QueryOptions } from '../../storage/StorageService' +import * as pako from 'pako' + import { AgentContext } from '../../agent' import { MessageSender } from '../../agent/MessageSender' import { getOutboundMessageContext } from '../../agent/getOutboundMessageContext' @@ -32,10 +37,12 @@ import { CredoError } from '../../error' import { Logger } from '../../logger' import { inject, injectable } from '../../plugins' import { DidCommMessageRepository } from '../../storage/didcomm/DidCommMessageRepository' +import { Buffer } from '../../utils' import { ConnectionService } from '../connections/services' import { RoutingService } from '../routing/services/RoutingService' import { CredentialsModuleConfig } from './CredentialsModuleConfig' +import { BitstringStatusListEntry, JsonLdCredentialFormat } from './formats' import { CredentialState } from './models/CredentialState' import { RevocationNotificationService } from './protocol/revocation-notification/services' import { CredentialRepository } from './repository/CredentialRepository' @@ -62,9 +69,12 @@ export interface CredentialsApi { // Issue Credential Methods acceptCredential(options: AcceptCredentialOptions): Promise - // Revoke Credential Methods + // Revoke JSON-LD credential Methods sendRevocationNotification(options: SendRevocationNotificationOptions): Promise + // Revoke Credential Methods + revokeJsonLdCredential(options: RevokeCredentialOption): Promise<{ message: string }> + // out of band createOffer(options: CreateCredentialOfferOptions): Promise<{ message: AgentMessage @@ -515,6 +525,87 @@ export class CredentialsApi implements Credent return credentialRecord } + /** + * Revoke a credential by issuer + * associated with the credential record. + * + * @param credentialRecordId The id of the credential record for which to revoke the credential + * @returns Revoke credential notification message + * + */ + public async revokeJsonLdCredential(options: RevokeCredentialOption): Promise<{ message: string }> { + // Default to '1' (revoked) + const revocationStatus = '1' as JsonLdRevocationStatus + + const credentialRecord = await this.getCredentialRecord(options.credentialRecordId) + const credentialStatus = this.validateCredentialStatus(credentialRecord) + + const { statusListIndex: credentialIndex, statusListCredential: statusListCredentialURL } = credentialStatus + const bitStringCredential = await this.fetchAndValidateBitStringCredential(statusListCredentialURL) + const decodedBitString = await this.decodeBitSting(bitStringCredential.credential.credentialSubject.encodedList) + + if (decodedBitString.charAt(Number(credentialIndex)) === revocationStatus) { + throw new CredoError('The JSON-LD credential is already revoked') + } + + // Update the bit string with the revocation status + const updatedBitString = this.updateBitString(decodedBitString, credentialIndex, revocationStatus) + bitStringCredential.credential.credentialSubject.encodedList = await this.encodeBitString(updatedBitString) + + await this.postUpdatedBitString(statusListCredentialURL, bitStringCredential) + + return { message: 'The JSON-LD credential has been successfully revoked.' } + } + + private async getCredentialRecord( + credentialRecordId: string + ): Promise> { + return this.getFormatData(credentialRecordId) + } + + private validateCredentialStatus( + credentialRecord: GetCredentialFormatDataReturn + ): BitstringStatusListEntry { + const credentialStatus = credentialRecord.offer?.jsonld?.credential?.credentialStatus + + if (Array.isArray(credentialStatus)) { + throw new CredoError('This credential status as an array for JSON-LD credentials is currently not supported') + } + + if (!credentialStatus) { + throw new CredoError('This JSON-LD credential is non-revocable') + } + + return credentialStatus + } + + private async fetchAndValidateBitStringCredential(statusListCredentialURL: string): Promise { + const response = await fetch(statusListCredentialURL) + if (!response.ok) { + throw new CredoError(`Failed to fetch credential: ${response.statusText}`) + } + return response.json() as Promise + } + + private updateBitString(decodedBitString: string, credentialIndex: string, revocationStatus: string): string { + return [ + decodedBitString.slice(0, Number(credentialIndex)), + revocationStatus, + decodedBitString.slice(Number(credentialIndex) + 1), + ].join('') + } + + private async postUpdatedBitString( + statusListCredentialURL: string, + bitStringCredential: BitStringCredential + ): Promise { + await fetch(statusListCredentialURL, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ credentialsData: bitStringCredential }), + }) + } + /** * Send a revocation notification for a credential exchange record. Currently Revocation Notification V2 protocol is supported * @@ -705,4 +796,21 @@ export class CredentialsApi implements Credent return this.getProtocol(credentialExchangeRecord.protocolVersion) } + + private async encodeBitString(bitString: string): Promise { + // Convert the bitString to a Uint8Array + const buffer = new TextEncoder().encode(bitString) + const compressedBuffer = pako.gzip(buffer) + // Convert the compressed buffer to a base64 string + return Buffer.from(compressedBuffer).toString('base64') + } + + private async decodeBitSting(bitString: string): Promise { + // Decode base64 string to Uint8Array + const compressedBuffer = Uint8Array.from(atob(bitString), (c) => c.charCodeAt(0)) + + // Decompress using pako + const decompressedBuffer = pako.ungzip(compressedBuffer, { to: 'string' }) + return decompressedBuffer + } } diff --git a/packages/core/src/modules/credentials/CredentialsApiOptions.ts b/packages/core/src/modules/credentials/CredentialsApiOptions.ts index 98d3f4ace3..783a896d4a 100644 --- a/packages/core/src/modules/credentials/CredentialsApiOptions.ts +++ b/packages/core/src/modules/credentials/CredentialsApiOptions.ts @@ -44,6 +44,8 @@ interface BaseOptions { goal?: string } +export type JsonLdRevocationStatus = '0' | '1' + /** * Interface for CredentialsApi.proposeCredential. Will send a proposal. */ @@ -171,3 +173,28 @@ export interface DeclineCredentialOfferOptions { */ problemReportDescription?: string } + +/** + * Interface for CredentialsApi.revokeCredential. revoke a jsonld credential by Issuer. + */ +export interface RevokeCredentialOption { + credentialRecordId: string +} + +export interface CredentialSubject { + id: string + type: string + encodedList: string + statusPurpose: string +} + +export interface Credential { + credentialSubject: CredentialSubject +} + +/** + * Interface for bit string credential. Representing the bit string credential status. + */ +export interface BitStringCredential { + credential: Credential +} diff --git a/packages/core/src/modules/credentials/formats/jsonld/JsonLdCredentialFormat.ts b/packages/core/src/modules/credentials/formats/jsonld/JsonLdCredentialFormat.ts index 298961370a..f4d003fa5b 100644 --- a/packages/core/src/modules/credentials/formats/jsonld/JsonLdCredentialFormat.ts +++ b/packages/core/src/modules/credentials/formats/jsonld/JsonLdCredentialFormat.ts @@ -11,9 +11,18 @@ export interface JsonCredential { issuanceDate: string expirationDate?: string credentialSubject: SingleOrArray + credentialStatus?: SingleOrArray [key: string]: unknown } +export interface BitstringStatusListEntry { + id: string + type: string + statusPurpose: string + statusListIndex: string + statusListCredential: string +} + /** * Format for creating a jsonld proposal, offer or request. */ diff --git a/packages/core/src/modules/vc/data-integrity/VerifyBitStringCredentialStatus.ts b/packages/core/src/modules/vc/data-integrity/VerifyBitStringCredentialStatus.ts new file mode 100644 index 0000000000..b467ab3ee9 --- /dev/null +++ b/packages/core/src/modules/vc/data-integrity/VerifyBitStringCredentialStatus.ts @@ -0,0 +1,71 @@ +import type { AgentContext } from '../../../agent/context' +import type { W3cJsonCredential } from '../models/credential/W3cJsonCredential' +import type { BitStringStatusListCredential } from '../models/credential/W3cJsonCredentialStatus' + +import * as pako from 'pako' + +import { CredoError } from '../../../error' +import { validateStatus } from '../models/credential/W3cCredentialStatus' + +// Function to fetch and parse the bit string status list credential +const fetchBitStringStatusListCredential = async ( + agentContext: AgentContext, + url: string +): Promise => { + const response = await agentContext.config.agentDependencies.fetch(url, { method: 'GET' }) + + if (!response.ok) { + throw new CredoError(`Failed to fetch bit string status list. HTTP Status: ${response.status}`) + } + + try { + return (await response.json()) as BitStringStatusListCredential + } catch (error) { + throw new CredoError('Failed to parse the bit string status list credential') + } +} + +export const verifyBitStringCredentialStatus = async (credential: W3cJsonCredential, agentContext: AgentContext) => { + const { credentialStatus } = credential + + if (Array.isArray(credentialStatus)) { + throw new CredoError('Verifying credential status as an array for JSON-LD credentials is currently not supported') + } + + if (!credentialStatus || credentialStatus.statusListIndex === undefined) { + throw new CredoError('Invalid credential status format') + } + + // Validate credentialStatus using the class-based approach + const isValid = await validateStatus(credentialStatus, agentContext) + + if (!isValid) { + throw new CredoError('Invalid credential status type. Expected BitstringStatusList') + } + + // Fetch the bit string status list credential + const bitStringStatusListCredential = await fetchBitStringStatusListCredential( + agentContext, + credentialStatus.statusListCredential + ) + + // Decode the encoded bit string + const encodedBitString = bitStringStatusListCredential.credential.credentialSubject.encodedList + const compressedBuffer = Uint8Array.from(atob(encodedBitString), (char) => char.charCodeAt(0)) + + // Decompress the bit string using pako + const decodedBitString = pako.ungzip(compressedBuffer, { to: 'string' }) + const statusListIndex = Number(credentialStatus.statusListIndex) + + // Ensure the statusListIndex is within bounds + if (statusListIndex < 0 || statusListIndex >= decodedBitString.length) { + throw new CredoError('Status list index is out of bounds') + } + + // Check if the credential is revoked + if (decodedBitString[statusListIndex] === '1') { + throw new CredoError(`Credential at index ${credentialStatus.statusListIndex} is revoked.`) + } + + return true +} diff --git a/packages/core/src/modules/vc/data-integrity/W3cJsonLdCredentialService.ts b/packages/core/src/modules/vc/data-integrity/W3cJsonLdCredentialService.ts index 182b4db8d3..27011edf64 100644 --- a/packages/core/src/modules/vc/data-integrity/W3cJsonLdCredentialService.ts +++ b/packages/core/src/modules/vc/data-integrity/W3cJsonLdCredentialService.ts @@ -9,9 +9,7 @@ import type { W3cJsonLdVerifyPresentationOptions, } from '../W3cCredentialServiceOptions' import type { W3cVerifyCredentialResult, W3cVerifyPresentationResult } from '../models' -import type { BitStringStatusListCredential, W3cJsonCredential } from '../models/credential/W3cJsonCredential' - -import * as pako from 'pako' +import type { W3cJsonCredential } from '../models/credential/W3cJsonCredential' import { createWalletKeyPairClass } from '../../../crypto/WalletKeyPair' import { CredoError } from '../../../error' @@ -23,6 +21,7 @@ import { W3cCredentialsModuleConfig } from '../W3cCredentialsModuleConfig' import { w3cDate } from '../util' import { SignatureSuiteRegistry } from './SignatureSuiteRegistry' +import { verifyBitStringCredentialStatus } from './VerifyBitStringCredentialStatus' import { deriveProof } from './deriveProof' import { assertOnlyW3cJsonLdVerifiableCredentials } from './jsonldUtil' import jsonld from './libraries/jsonld' @@ -112,46 +111,8 @@ export class W3cJsonLdCredentialService { suite: suites, documentLoader: this.w3cCredentialsModuleConfig.documentLoader(agentContext), checkStatus: async ({ credential }: { credential: W3cJsonCredential }) => { - // Only throw error if credentialStatus is present if (verifyCredentialStatus && 'credentialStatus' in credential) { - if (Array.isArray(credential.credentialStatus)) { - throw new CredoError( - 'Verifying credential status as an array for JSON-LD credentials is currently not supported' - ) - } - - // Ensure credentialStatus contains the necessary properties - if (!credential.credentialStatus || credential.credentialStatus.statusListIndex === undefined) { - throw new CredoError('Invalid credential status format') - } - - const credentialStatusURL = credential.credentialStatus.statusListCredential - const bitStringStatusListCredential = await agentContext.config.agentDependencies.fetch( - credentialStatusURL, - { - method: 'GET', - } - ) - - if (!bitStringStatusListCredential.ok) { - throw new CredoError(`HTTP error! Status: ${bitStringStatusListCredential.status}`) - } - const bitStringCredential = - (await bitStringStatusListCredential.json()) as unknown as BitStringStatusListCredential - const encodedBitString = bitStringCredential.credential.credentialSubject.encodedList - const compressedBuffer = Uint8Array.from(atob(encodedBitString), (c) => c.charCodeAt(0)) - - // Decompress using pako - const decodedBitString = pako.ungzip(compressedBuffer, { to: 'string' }) - const statusListIndex = Number(credential.credentialStatus.statusListIndex) - - if (statusListIndex < 0 || statusListIndex >= decodedBitString.length) { - throw new CredoError('Index out of bounds') - } - - if (decodedBitString[statusListIndex] === '1') { - throw new CredoError(`Credential at index ${credential.credentialStatus.statusListIndex} is revoked.`) - } + await verifyBitStringCredentialStatus(credential, agentContext) } return { verified: true, @@ -305,46 +266,8 @@ export class W3cJsonLdCredentialService { domain: options.domain, documentLoader: this.w3cCredentialsModuleConfig.documentLoader(agentContext), checkStatus: async ({ credential }: { credential: W3cJsonCredential }) => { - // Only throw error if credentialStatus is present if ('credentialStatus' in credential) { - if (Array.isArray(credential.credentialStatus)) { - throw new CredoError( - 'Verifying credential status as an array for JSON-LD credentials is currently not supported' - ) - } - - // Ensure credentialStatus contains the necessary properties - if (!credential.credentialStatus || credential.credentialStatus.statusListIndex === undefined) { - throw new CredoError('Invalid credential status format') - } - - const credentialStatusURL = credential.credentialStatus.statusListCredential - const bitStringStatusListCredential = await agentContext.config.agentDependencies.fetch( - credentialStatusURL, - { - method: 'GET', - } - ) - - if (!bitStringStatusListCredential.ok) { - throw new CredoError(`HTTP error! Status: ${bitStringStatusListCredential.status}`) - } - const bitStringCredential = - (await bitStringStatusListCredential.json()) as unknown as BitStringStatusListCredential - const encodedBitString = bitStringCredential.credential.credentialSubject.encodedList - const compressedBuffer = Uint8Array.from(atob(encodedBitString), (c) => c.charCodeAt(0)) - - // Decompress using pako - const decodedBitString = pako.ungzip(compressedBuffer, { to: 'string' }) - const statusListIndex = Number(credential.credentialStatus.statusListIndex) - - if (statusListIndex < 0 || statusListIndex >= decodedBitString.length) { - throw new CredoError('Index out of bounds') - } - - if (decodedBitString[statusListIndex] === '1') { - throw new CredoError(`Credential at index ${credential.credentialStatus.statusListIndex} is revoked.`) - } + await verifyBitStringCredentialStatus(credential, agentContext) } return { verified: true, diff --git a/packages/core/src/modules/vc/models/credential/W3cCredentialStatus.ts b/packages/core/src/modules/vc/models/credential/W3cCredentialStatus.ts index cf1de83151..2ccda58103 100644 --- a/packages/core/src/modules/vc/models/credential/W3cCredentialStatus.ts +++ b/packages/core/src/modules/vc/models/credential/W3cCredentialStatus.ts @@ -1,10 +1,18 @@ -import { IsString } from 'class-validator' +import { plainToInstance } from 'class-transformer' +import { IsEnum, IsString, validateOrReject } from 'class-validator' +import { AgentContext } from '../../../../agent/context' +import { CredoError } from '../../../../error' import { IsUri } from '../../../../utils/validators' +import { CredentialStatus, CredentialStatusPurpose, CredentialStatusType } from './W3cJsonCredentialStatus' + export interface W3cCredentialStatusOptions { id: string - type: string + type: CredentialStatusType + statusPurpose: CredentialStatusPurpose + statusListIndex: string + statusListCredential: string } export class W3cCredentialStatus { @@ -12,12 +20,42 @@ export class W3cCredentialStatus { if (options) { this.id = options.id this.type = options.type + this.statusPurpose = options.statusPurpose + this.statusListIndex = options.statusListIndex + this.statusListCredential = options.statusListCredential } } @IsUri() + @IsString() public id!: string + @IsEnum(['BitstringStatusListEntry']) + @IsString() + public type!: CredentialStatusType + + @IsEnum(CredentialStatusPurpose) + @IsString() + public statusPurpose!: CredentialStatusPurpose + @IsString() - public type!: string + public statusListIndex!: string + + @IsString() + public statusListCredential!: string +} + +// Function to validate the status using the updated method +export const validateStatus = async (status: CredentialStatus, agentContext: AgentContext): Promise => { + const entry = plainToInstance(W3cCredentialStatus, status) + + try { + await validateOrReject(entry) + return true + } catch (errors) { + agentContext.config.logger.debug(`Credential status validation failed: ${errors}`, { + stack: errors, + }) + throw new CredoError(`Invalid credential status type: ${errors}`) + } } diff --git a/packages/core/src/modules/vc/models/credential/W3cJsonCredential.ts b/packages/core/src/modules/vc/models/credential/W3cJsonCredential.ts index abd16ad6de..b9cea6a4fe 100644 --- a/packages/core/src/modules/vc/models/credential/W3cJsonCredential.ts +++ b/packages/core/src/modules/vc/models/credential/W3cJsonCredential.ts @@ -1,3 +1,4 @@ +import type { CredentialStatus } from './W3cJsonCredentialStatus' import type { JsonObject } from '../../../../types' import type { SingleOrArray } from '../../../../utils' @@ -9,60 +10,6 @@ export interface W3cJsonCredential { issuanceDate: string expirationDate?: string credentialSubject: SingleOrArray - [key: string]: unknown credentialStatus?: SingleOrArray -} - -type CredentialStatusType = 'BitstringStatusListEntry' -// The purpose can be anything apart from this as well -export enum CredentialStatusPurpose { - 'revocation' = 'revocation', - 'suspension' = 'suspension', - 'message' = 'message', -} - -export interface StatusMessage { - // a string representing the hexadecimal value of the status prefixed with 0x - status: string - // a string used by software developers to assist with debugging which SHOULD NOT be displayed to end users - message?: string - // We can have some key value pairs as well [key: string]: unknown } - -export interface CredentialStatus { - id: string - // Since currenlty we are only trying to support 'BitStringStatusListEntry' - type: CredentialStatusType - statusPurpose: CredentialStatusPurpose - // Unique identifier for the specific credential - statusListIndex: string - // Must be url referencing to a VC of type 'BitstringStatusListCredential' - statusListCredential: string - // The statusSize indicates the size of the status entry in bits - statusSize?: number - // Must be preset if statusPurpose is message - /** - * the length of which MUST equal the number of possible status messages indicated by statusSize - * (e.g., statusMessage array MUST have 2 elements if statusSize has 1 bit, - * 4 elements if statusSize has 2 bits, 8 elements if statusSize has 3 bits, etc.). - */ - statusMessage?: StatusMessage[] - // An implementer MAY include the statusReference property. If present, its value MUST be a URL or an array of URLs [URL] which dereference to material related to the status - statusReference?: SingleOrArray -} - -// Define an interface for `credentialSubject` -export interface CredentialSubject { - encodedList: string -} - -// Define an interface for the `credential` object that uses `CredentialSubject` -export interface Credential { - credentialSubject: CredentialSubject -} - -// Use the `Credential` interface within `BitStringStatusListCredential` -export interface BitStringStatusListCredential { - credential: Credential -} diff --git a/packages/core/src/modules/vc/models/credential/W3cJsonCredentialStatus.ts b/packages/core/src/modules/vc/models/credential/W3cJsonCredentialStatus.ts new file mode 100644 index 0000000000..71d53b187e --- /dev/null +++ b/packages/core/src/modules/vc/models/credential/W3cJsonCredentialStatus.ts @@ -0,0 +1,55 @@ +import type { SingleOrArray } from '../../../../utils' + +export type CredentialStatusType = 'BitstringStatusListEntry' +// The purpose can be anything apart from this as well +export enum CredentialStatusPurpose { + 'revocation' = 'revocation', + 'suspension' = 'suspension', + 'message' = 'message', +} + +export interface StatusMessage { + // a string representing the hexadecimal value of the status prefixed with 0x + status: string + // a string used by software developers to assist with debugging which SHOULD NOT be displayed to end users + message?: string + // We can have some key value pairs as well + [key: string]: unknown +} + +export interface CredentialStatus { + id: string + // Since currenlty we are only trying to support 'BitStringStatusListEntry' + type: CredentialStatusType + statusPurpose: CredentialStatusPurpose + // Unique identifier for the specific credential + statusListIndex: string + // Must be url referencing to a VC of type 'BitstringStatusListCredential' + statusListCredential: string + // The statusSize indicates the size of the status entry in bits + statusSize?: number + // Must be preset if statusPurpose is message + /** + * the length of which MUST equal the number of possible status messages indicated by statusSize + * (e.g., statusMessage array MUST have 2 elements if statusSize has 1 bit, + * 4 elements if statusSize has 2 bits, 8 elements if statusSize has 3 bits, etc.). + */ + statusMessage?: StatusMessage[] + // An implementer MAY include the statusReference property. If present, its value MUST be a URL or an array of URLs [URL] which dereference to material related to the status + statusReference?: SingleOrArray +} + +// Define an interface for `credentialSubject` +export interface CredentialSubject { + encodedList: string +} + +// Define an interface for the `credential` object that uses `CredentialSubject` +export interface Credential { + credentialSubject: CredentialSubject +} + +// Use the `Credential` interface within `BitStringStatusListCredential` +export interface BitStringStatusListCredential { + credential: Credential +} From 7c089ae600b6fc3ee90722b4d13c761c7b18722c Mon Sep 17 00:00:00 2001 From: Krishna Waske Date: Tue, 29 Oct 2024 13:59:29 +0530 Subject: [PATCH 03/13] chore: verify credential status Signed-off-by: Krishna Waske --- .../modules/vc/data-integrity/W3cJsonLdCredentialService.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/core/src/modules/vc/data-integrity/W3cJsonLdCredentialService.ts b/packages/core/src/modules/vc/data-integrity/W3cJsonLdCredentialService.ts index 27011edf64..4dceb6a65a 100644 --- a/packages/core/src/modules/vc/data-integrity/W3cJsonLdCredentialService.ts +++ b/packages/core/src/modules/vc/data-integrity/W3cJsonLdCredentialService.ts @@ -259,6 +259,7 @@ export class W3cJsonLdCredentialService { ) const allSuites = presentationSuites.concat(...credentialSuites) + const verifyCredentialStatus = options.verifyCredentialStatus ?? true const verifyOptions: Record = { presentation: JsonTransformer.toJSON(options.presentation), suite: allSuites, @@ -266,7 +267,7 @@ export class W3cJsonLdCredentialService { domain: options.domain, documentLoader: this.w3cCredentialsModuleConfig.documentLoader(agentContext), checkStatus: async ({ credential }: { credential: W3cJsonCredential }) => { - if ('credentialStatus' in credential) { + if (verifyCredentialStatus && 'credentialStatus' in credential) { await verifyBitStringCredentialStatus(credential, agentContext) } return { From a6b5f5fc926b98805819ebdd0c1b7f73a25ae916 Mon Sep 17 00:00:00 2001 From: Krishna Waske Date: Tue, 3 Dec 2024 08:33:45 +0530 Subject: [PATCH 04/13] chore: rearrange files Signed-off-by: Krishna Waske --- .../src/modules/credentials/CredentialsApi.ts | 91 +---------- .../credentials/CredentialsApiOptions.ts | 25 --- .../src/modules/vc/W3cCredentialService.ts | 30 ++++ .../modules/vc/W3cCredentialServiceOptions.ts | 7 + .../core/src/modules/vc/W3cCredentialsApi.ts | 10 ++ .../W3cJsonLdCredentialService.ts | 24 ++- .../vc/jwt-vc/W3cJwtCredentialService.ts | 8 + .../vc/models/credential/W3cCredential.ts | 2 +- .../models/credential/W3cCredentialStatus.ts | 61 -------- .../vc/models/credential/W3cJsonCredential.ts | 4 +- .../credential/W3cJsonCredentialStatus.ts | 55 ------- .../status-list/W3cCredentialStatus.ts | 45 ++++++ .../BitStringStatusList.ts | 145 ++++++++++++++++++ .../VerifyBitStringCredentialStatus.ts | 16 +- .../bitstring-status-list/index.ts | 0 .../vc/models/credential/status-list/index.ts | 0 16 files changed, 277 insertions(+), 246 deletions(-) delete mode 100644 packages/core/src/modules/vc/models/credential/W3cCredentialStatus.ts delete mode 100644 packages/core/src/modules/vc/models/credential/W3cJsonCredentialStatus.ts create mode 100644 packages/core/src/modules/vc/models/credential/status-list/W3cCredentialStatus.ts create mode 100644 packages/core/src/modules/vc/models/credential/status-list/bitstring-status-list/BitStringStatusList.ts rename packages/core/src/modules/vc/{data-integrity => models/credential/status-list/bitstring-status-list}/VerifyBitStringCredentialStatus.ts (81%) create mode 100644 packages/core/src/modules/vc/models/credential/status-list/bitstring-status-list/index.ts create mode 100644 packages/core/src/modules/vc/models/credential/status-list/index.ts diff --git a/packages/core/src/modules/credentials/CredentialsApi.ts b/packages/core/src/modules/credentials/CredentialsApi.ts index ad1eb1f4ef..d9c145a028 100644 --- a/packages/core/src/modules/credentials/CredentialsApi.ts +++ b/packages/core/src/modules/credentials/CredentialsApi.ts @@ -17,9 +17,6 @@ import type { DeleteCredentialOptions, SendRevocationNotificationOptions, DeclineCredentialOfferOptions, - RevokeCredentialOption, - BitStringCredential, - JsonLdRevocationStatus, } from './CredentialsApiOptions' import type { CredentialProtocol } from './protocol/CredentialProtocol' import type { CredentialFormatsFromProtocols } from './protocol/CredentialProtocolOptions' @@ -42,7 +39,6 @@ import { ConnectionService } from '../connections/services' import { RoutingService } from '../routing/services/RoutingService' import { CredentialsModuleConfig } from './CredentialsModuleConfig' -import { BitstringStatusListEntry, JsonLdCredentialFormat } from './formats' import { CredentialState } from './models/CredentialState' import { RevocationNotificationService } from './protocol/revocation-notification/services' import { CredentialRepository } from './repository/CredentialRepository' @@ -69,12 +65,8 @@ export interface CredentialsApi { // Issue Credential Methods acceptCredential(options: AcceptCredentialOptions): Promise - // Revoke JSON-LD credential Methods + // Send Credential revocation notification Methods sendRevocationNotification(options: SendRevocationNotificationOptions): Promise - - // Revoke Credential Methods - revokeJsonLdCredential(options: RevokeCredentialOption): Promise<{ message: string }> - // out of band createOffer(options: CreateCredentialOfferOptions): Promise<{ message: AgentMessage @@ -525,87 +517,6 @@ export class CredentialsApi implements Credent return credentialRecord } - /** - * Revoke a credential by issuer - * associated with the credential record. - * - * @param credentialRecordId The id of the credential record for which to revoke the credential - * @returns Revoke credential notification message - * - */ - public async revokeJsonLdCredential(options: RevokeCredentialOption): Promise<{ message: string }> { - // Default to '1' (revoked) - const revocationStatus = '1' as JsonLdRevocationStatus - - const credentialRecord = await this.getCredentialRecord(options.credentialRecordId) - const credentialStatus = this.validateCredentialStatus(credentialRecord) - - const { statusListIndex: credentialIndex, statusListCredential: statusListCredentialURL } = credentialStatus - const bitStringCredential = await this.fetchAndValidateBitStringCredential(statusListCredentialURL) - const decodedBitString = await this.decodeBitSting(bitStringCredential.credential.credentialSubject.encodedList) - - if (decodedBitString.charAt(Number(credentialIndex)) === revocationStatus) { - throw new CredoError('The JSON-LD credential is already revoked') - } - - // Update the bit string with the revocation status - const updatedBitString = this.updateBitString(decodedBitString, credentialIndex, revocationStatus) - bitStringCredential.credential.credentialSubject.encodedList = await this.encodeBitString(updatedBitString) - - await this.postUpdatedBitString(statusListCredentialURL, bitStringCredential) - - return { message: 'The JSON-LD credential has been successfully revoked.' } - } - - private async getCredentialRecord( - credentialRecordId: string - ): Promise> { - return this.getFormatData(credentialRecordId) - } - - private validateCredentialStatus( - credentialRecord: GetCredentialFormatDataReturn - ): BitstringStatusListEntry { - const credentialStatus = credentialRecord.offer?.jsonld?.credential?.credentialStatus - - if (Array.isArray(credentialStatus)) { - throw new CredoError('This credential status as an array for JSON-LD credentials is currently not supported') - } - - if (!credentialStatus) { - throw new CredoError('This JSON-LD credential is non-revocable') - } - - return credentialStatus - } - - private async fetchAndValidateBitStringCredential(statusListCredentialURL: string): Promise { - const response = await fetch(statusListCredentialURL) - if (!response.ok) { - throw new CredoError(`Failed to fetch credential: ${response.statusText}`) - } - return response.json() as Promise - } - - private updateBitString(decodedBitString: string, credentialIndex: string, revocationStatus: string): string { - return [ - decodedBitString.slice(0, Number(credentialIndex)), - revocationStatus, - decodedBitString.slice(Number(credentialIndex) + 1), - ].join('') - } - - private async postUpdatedBitString( - statusListCredentialURL: string, - bitStringCredential: BitStringCredential - ): Promise { - await fetch(statusListCredentialURL, { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ credentialsData: bitStringCredential }), - }) - } - /** * Send a revocation notification for a credential exchange record. Currently Revocation Notification V2 protocol is supported * diff --git a/packages/core/src/modules/credentials/CredentialsApiOptions.ts b/packages/core/src/modules/credentials/CredentialsApiOptions.ts index 783a896d4a..38775a96a5 100644 --- a/packages/core/src/modules/credentials/CredentialsApiOptions.ts +++ b/packages/core/src/modules/credentials/CredentialsApiOptions.ts @@ -173,28 +173,3 @@ export interface DeclineCredentialOfferOptions { */ problemReportDescription?: string } - -/** - * Interface for CredentialsApi.revokeCredential. revoke a jsonld credential by Issuer. - */ -export interface RevokeCredentialOption { - credentialRecordId: string -} - -export interface CredentialSubject { - id: string - type: string - encodedList: string - statusPurpose: string -} - -export interface Credential { - credentialSubject: CredentialSubject -} - -/** - * Interface for bit string credential. Representing the bit string credential status. - */ -export interface BitStringCredential { - credential: Credential -} diff --git a/packages/core/src/modules/vc/W3cCredentialService.ts b/packages/core/src/modules/vc/W3cCredentialService.ts index 726d5eb707..8ad064941d 100644 --- a/packages/core/src/modules/vc/W3cCredentialService.ts +++ b/packages/core/src/modules/vc/W3cCredentialService.ts @@ -22,6 +22,7 @@ import type { Query, QueryOptions } from '../../storage/StorageService' import { CredoError } from '../../error' import { injectable } from '../../plugins' +import { RevokeCredentialOption } from './W3cCredentialServiceOptions' import { CREDENTIALS_CONTEXT_V1_URL } from './constants' import { W3cJsonLdVerifiableCredential } from './data-integrity' import { W3cJsonLdCredentialService } from './data-integrity/W3cJsonLdCredentialService' @@ -186,6 +187,35 @@ export class W3cCredentialService { return w3cCredentialRecord } + /** + * Revoke a credential by issuer + * associated with the credential record. + * + * @param credentialRecordId The id of the credential record for which to revoke the credential + * @returns Revoke credential notification message + * + */ + public async revokeCredential( + agentContext: AgentContext, + options: RevokeCredentialOption + ) { + const credentialRecod = await this.getCredentialRecordById(agentContext, options.credentialRecordId) + if (!credentialRecod) { + throw new CredoError(`Credential with id ${options.credentialRecordId} not found`) + } + const tags = credentialRecod.getTags() + + if (tags.claimFormat === ClaimFormat.JwtVc) { + const revoked = await this.w3cJwtCredentialService.revokeCredential(agentContext, options) + return revoked as unknown as W3cVerifiablePresentation + } else if (tags.claimFormat === ClaimFormat.LdpVc) { + const revoked = await this.w3cJsonLdCredentialService.revokeCredential(agentContext, options) + return revoked as unknown as W3cVerifiablePresentation + } else { + throw new CredoError(`Unsupported format in options. Format must be either 'jwt_vp' or 'ldp_vp'`) + } + } + public async removeCredentialRecord(agentContext: AgentContext, id: string) { await this.w3cCredentialRepository.deleteById(agentContext, id) } diff --git a/packages/core/src/modules/vc/W3cCredentialServiceOptions.ts b/packages/core/src/modules/vc/W3cCredentialServiceOptions.ts index 10e7679016..49bfcd8a98 100644 --- a/packages/core/src/modules/vc/W3cCredentialServiceOptions.ts +++ b/packages/core/src/modules/vc/W3cCredentialServiceOptions.ts @@ -205,3 +205,10 @@ export interface W3cJsonLdVerifyPresentationOptions extends W3cVerifyPresentatio export interface StoreCredentialOptions { credential: W3cVerifiableCredential } + +/** + * Interface for W3cCredentialsApi.revokeCredential. revoke a w3c credential. + */ +export interface RevokeCredentialOption { + credentialRecordId: string +} diff --git a/packages/core/src/modules/vc/W3cCredentialsApi.ts b/packages/core/src/modules/vc/W3cCredentialsApi.ts index 9db8fcf93f..5acf993d20 100644 --- a/packages/core/src/modules/vc/W3cCredentialsApi.ts +++ b/packages/core/src/modules/vc/W3cCredentialsApi.ts @@ -1,4 +1,5 @@ import type { + RevokeCredentialOption, StoreCredentialOptions, W3cCreatePresentationOptions, W3cSignCredentialOptions, @@ -14,6 +15,8 @@ import { AgentContext } from '../../agent' import { injectable } from '../../plugins' import { W3cCredentialService } from './W3cCredentialService' +import { W3cJsonLdVerifiablePresentation } from './data-integrity' +import { W3cJwtVerifiablePresentation } from './jwt-vc' /** * @public @@ -44,6 +47,13 @@ export class W3cCredentialsApi { return this.w3cCredentialService.getCredentialRecordById(this.agentContext, id) } + // Revoke Credential Methods + public async revokeCredential( + options: RevokeCredentialOption + ): Promise { + return this.w3cCredentialService.revokeCredential(this.agentContext, options) + } + public async findCredentialRecordsByQuery( query: Query, queryOptions?: QueryOptions diff --git a/packages/core/src/modules/vc/data-integrity/W3cJsonLdCredentialService.ts b/packages/core/src/modules/vc/data-integrity/W3cJsonLdCredentialService.ts index 4dceb6a65a..7b6c465f7d 100644 --- a/packages/core/src/modules/vc/data-integrity/W3cJsonLdCredentialService.ts +++ b/packages/core/src/modules/vc/data-integrity/W3cJsonLdCredentialService.ts @@ -3,6 +3,7 @@ import type { AgentContext } from '../../../agent/context' import type { Key } from '../../../crypto/Key' import type { SingleOrArray } from '../../../utils' import type { + RevokeCredentialOption, W3cJsonLdSignCredentialOptions, W3cJsonLdSignPresentationOptions, W3cJsonLdVerifyCredentialOptions, @@ -18,10 +19,10 @@ import { asArray, JsonTransformer } from '../../../utils' import { VerificationMethod } from '../../dids' import { getKeyFromVerificationMethod } from '../../dids/domain/key-type' import { W3cCredentialsModuleConfig } from '../W3cCredentialsModuleConfig' +import { validateStatus } from '../models/credential/status-list/W3cCredentialStatus' import { w3cDate } from '../util' import { SignatureSuiteRegistry } from './SignatureSuiteRegistry' -import { verifyBitStringCredentialStatus } from './VerifyBitStringCredentialStatus' import { deriveProof } from './deriveProof' import { assertOnlyW3cJsonLdVerifiableCredentials } from './jsonldUtil' import jsonld from './libraries/jsonld' @@ -111,8 +112,11 @@ export class W3cJsonLdCredentialService { suite: suites, documentLoader: this.w3cCredentialsModuleConfig.documentLoader(agentContext), checkStatus: async ({ credential }: { credential: W3cJsonCredential }) => { - if (verifyCredentialStatus && 'credentialStatus' in credential) { - await verifyBitStringCredentialStatus(credential, agentContext) + if (verifyCredentialStatus && credential.credentialStatus) { + // await verifyBitStringCredentialStatus(credential, agentContext) + // Add a verification function that then checks which type of credentailStatus we have + // If the type is supported, we validate it and return the result + await validateStatus(credential.credentialStatus, agentContext) } return { verified: true, @@ -267,8 +271,11 @@ export class W3cJsonLdCredentialService { domain: options.domain, documentLoader: this.w3cCredentialsModuleConfig.documentLoader(agentContext), checkStatus: async ({ credential }: { credential: W3cJsonCredential }) => { - if (verifyCredentialStatus && 'credentialStatus' in credential) { - await verifyBitStringCredentialStatus(credential, agentContext) + if (verifyCredentialStatus && credential.credentialStatus) { + // await verifyBitStringCredentialStatus(credential, agentContext) + // Add a verification function that then checks which type of credentailStatus we have + // If the type is supported, we validate it and return the result + await validateStatus(credential.credentialStatus, agentContext) } return { verified: true, @@ -325,6 +332,13 @@ export class W3cJsonLdCredentialService { return proof } + // temporarily disable no unused var + // eslint-disable-next-line @typescript-eslint/no-unused-vars + public async revokeCredential(_agentContext: AgentContext, _options: RevokeCredentialOption) { + // revoke jwt cred + throw new CredoError(`Revocation support not implemented for JsonLd`) + } + public getVerificationMethodTypesByProofType(proofType: string): string[] { return this.signatureSuiteRegistry.getByProofType(proofType).verificationMethodTypes } diff --git a/packages/core/src/modules/vc/jwt-vc/W3cJwtCredentialService.ts b/packages/core/src/modules/vc/jwt-vc/W3cJwtCredentialService.ts index a03a07528d..5b9d1e7127 100644 --- a/packages/core/src/modules/vc/jwt-vc/W3cJwtCredentialService.ts +++ b/packages/core/src/modules/vc/jwt-vc/W3cJwtCredentialService.ts @@ -2,6 +2,7 @@ import type { AgentContext } from '../../../agent/context' import type { VerifyJwsResult } from '../../../crypto/JwsService' import type { DidPurpose, VerificationMethod } from '../../dids' import type { + RevokeCredentialOption, W3cJwtSignCredentialOptions, W3cJwtSignPresentationOptions, W3cJwtVerifyCredentialOptions, @@ -538,4 +539,11 @@ export class W3cJwtCredentialService { return verificationMethod } + + // temporarily disable no unused var + // eslint-disable-next-line @typescript-eslint/no-unused-vars + public async revokeCredential(_agentContext: AgentContext, _options: RevokeCredentialOption) { + // revoke jwt cred + throw new CredoError(`Revocation support not implemented for jwtVc`) + } } diff --git a/packages/core/src/modules/vc/models/credential/W3cCredential.ts b/packages/core/src/modules/vc/models/credential/W3cCredential.ts index e62cd75ad0..e211cb073b 100644 --- a/packages/core/src/modules/vc/models/credential/W3cCredential.ts +++ b/packages/core/src/modules/vc/models/credential/W3cCredential.ts @@ -13,9 +13,9 @@ import { CREDENTIALS_CONTEXT_V1_URL, VERIFIABLE_CREDENTIAL_TYPE } from '../../co import { IsCredentialJsonLdContext } from '../../validators' import { W3cCredentialSchema } from './W3cCredentialSchema' -import { W3cCredentialStatus } from './W3cCredentialStatus' import { IsW3cCredentialSubject, W3cCredentialSubject, W3cCredentialSubjectTransformer } from './W3cCredentialSubject' import { IsW3cIssuer, W3cIssuer, W3cIssuerTransformer } from './W3cIssuer' +import { W3cCredentialStatus } from './status-list/W3cCredentialStatus' export interface W3cCredentialOptions { context?: Array diff --git a/packages/core/src/modules/vc/models/credential/W3cCredentialStatus.ts b/packages/core/src/modules/vc/models/credential/W3cCredentialStatus.ts deleted file mode 100644 index 2ccda58103..0000000000 --- a/packages/core/src/modules/vc/models/credential/W3cCredentialStatus.ts +++ /dev/null @@ -1,61 +0,0 @@ -import { plainToInstance } from 'class-transformer' -import { IsEnum, IsString, validateOrReject } from 'class-validator' - -import { AgentContext } from '../../../../agent/context' -import { CredoError } from '../../../../error' -import { IsUri } from '../../../../utils/validators' - -import { CredentialStatus, CredentialStatusPurpose, CredentialStatusType } from './W3cJsonCredentialStatus' - -export interface W3cCredentialStatusOptions { - id: string - type: CredentialStatusType - statusPurpose: CredentialStatusPurpose - statusListIndex: string - statusListCredential: string -} - -export class W3cCredentialStatus { - public constructor(options: W3cCredentialStatusOptions) { - if (options) { - this.id = options.id - this.type = options.type - this.statusPurpose = options.statusPurpose - this.statusListIndex = options.statusListIndex - this.statusListCredential = options.statusListCredential - } - } - - @IsUri() - @IsString() - public id!: string - - @IsEnum(['BitstringStatusListEntry']) - @IsString() - public type!: CredentialStatusType - - @IsEnum(CredentialStatusPurpose) - @IsString() - public statusPurpose!: CredentialStatusPurpose - - @IsString() - public statusListIndex!: string - - @IsString() - public statusListCredential!: string -} - -// Function to validate the status using the updated method -export const validateStatus = async (status: CredentialStatus, agentContext: AgentContext): Promise => { - const entry = plainToInstance(W3cCredentialStatus, status) - - try { - await validateOrReject(entry) - return true - } catch (errors) { - agentContext.config.logger.debug(`Credential status validation failed: ${errors}`, { - stack: errors, - }) - throw new CredoError(`Invalid credential status type: ${errors}`) - } -} diff --git a/packages/core/src/modules/vc/models/credential/W3cJsonCredential.ts b/packages/core/src/modules/vc/models/credential/W3cJsonCredential.ts index b9cea6a4fe..e5b9f7ff93 100644 --- a/packages/core/src/modules/vc/models/credential/W3cJsonCredential.ts +++ b/packages/core/src/modules/vc/models/credential/W3cJsonCredential.ts @@ -1,4 +1,4 @@ -import type { CredentialStatus } from './W3cJsonCredentialStatus' +import type { W3cCredentialStatusOptions } from './status-list/W3cCredentialStatus' import type { JsonObject } from '../../../../types' import type { SingleOrArray } from '../../../../utils' @@ -10,6 +10,6 @@ export interface W3cJsonCredential { issuanceDate: string expirationDate?: string credentialSubject: SingleOrArray - credentialStatus?: SingleOrArray + credentialStatus?: SingleOrArray [key: string]: unknown } diff --git a/packages/core/src/modules/vc/models/credential/W3cJsonCredentialStatus.ts b/packages/core/src/modules/vc/models/credential/W3cJsonCredentialStatus.ts deleted file mode 100644 index 71d53b187e..0000000000 --- a/packages/core/src/modules/vc/models/credential/W3cJsonCredentialStatus.ts +++ /dev/null @@ -1,55 +0,0 @@ -import type { SingleOrArray } from '../../../../utils' - -export type CredentialStatusType = 'BitstringStatusListEntry' -// The purpose can be anything apart from this as well -export enum CredentialStatusPurpose { - 'revocation' = 'revocation', - 'suspension' = 'suspension', - 'message' = 'message', -} - -export interface StatusMessage { - // a string representing the hexadecimal value of the status prefixed with 0x - status: string - // a string used by software developers to assist with debugging which SHOULD NOT be displayed to end users - message?: string - // We can have some key value pairs as well - [key: string]: unknown -} - -export interface CredentialStatus { - id: string - // Since currenlty we are only trying to support 'BitStringStatusListEntry' - type: CredentialStatusType - statusPurpose: CredentialStatusPurpose - // Unique identifier for the specific credential - statusListIndex: string - // Must be url referencing to a VC of type 'BitstringStatusListCredential' - statusListCredential: string - // The statusSize indicates the size of the status entry in bits - statusSize?: number - // Must be preset if statusPurpose is message - /** - * the length of which MUST equal the number of possible status messages indicated by statusSize - * (e.g., statusMessage array MUST have 2 elements if statusSize has 1 bit, - * 4 elements if statusSize has 2 bits, 8 elements if statusSize has 3 bits, etc.). - */ - statusMessage?: StatusMessage[] - // An implementer MAY include the statusReference property. If present, its value MUST be a URL or an array of URLs [URL] which dereference to material related to the status - statusReference?: SingleOrArray -} - -// Define an interface for `credentialSubject` -export interface CredentialSubject { - encodedList: string -} - -// Define an interface for the `credential` object that uses `CredentialSubject` -export interface Credential { - credentialSubject: CredentialSubject -} - -// Use the `Credential` interface within `BitStringStatusListCredential` -export interface BitStringStatusListCredential { - credential: Credential -} diff --git a/packages/core/src/modules/vc/models/credential/status-list/W3cCredentialStatus.ts b/packages/core/src/modules/vc/models/credential/status-list/W3cCredentialStatus.ts new file mode 100644 index 0000000000..adac951e71 --- /dev/null +++ b/packages/core/src/modules/vc/models/credential/status-list/W3cCredentialStatus.ts @@ -0,0 +1,45 @@ +import { plainToInstance } from 'class-transformer' +import { IsString, validateOrReject } from 'class-validator' + +import { AgentContext } from '../../../../../agent/context' +import { CredoError } from '../../../../../error' +import { IsUri } from '../../../../../utils/validators' + +export interface W3cCredentialStatusOptions { + id: string + type: string +} + +export class W3cCredentialStatus { + public constructor(options: W3cCredentialStatusOptions) { + if (options) { + this.id = options.id + this.type = options.type + } + } + + @IsUri() + @IsString() + public id!: string + + @IsString() + public type!: string +} + +// Function to validate the status using the updated method +export const validateStatus = async ( + status: W3cCredentialStatus | W3cCredentialStatus[], + agentContext: AgentContext +): Promise => { + const entry = plainToInstance(W3cCredentialStatus, status) + + try { + await validateOrReject(entry) + return true + } catch (errors) { + agentContext.config.logger.debug(`Credential status validation failed: ${errors}`, { + stack: errors, + }) + throw new CredoError(`Invalid credential status type: ${errors}`) + } +} diff --git a/packages/core/src/modules/vc/models/credential/status-list/bitstring-status-list/BitStringStatusList.ts b/packages/core/src/modules/vc/models/credential/status-list/bitstring-status-list/BitStringStatusList.ts new file mode 100644 index 0000000000..00223665cb --- /dev/null +++ b/packages/core/src/modules/vc/models/credential/status-list/bitstring-status-list/BitStringStatusList.ts @@ -0,0 +1,145 @@ +import { Type } from 'class-transformer' +import { IsEnum, IsNumber, IsString } from 'class-validator' + +import { JsonLdCredentialDetail, W3cCredentialSubject } from '../../../../../../..' +import { W3cCredential } from '../../W3cCredential' +import { W3cCredentialStatus } from '../W3cCredentialStatus' + +// The purpose can be anything apart from this as well +export enum BitstringStatusListCredentialStatusPurpose { + 'revocation' = 'revocation', + 'suspension' = 'suspension', +} + +export interface BitStringStatusListMessageOptions { + // a string representing the hexadecimal value of the status prefixed with 0x + status: string + // a string used by software developers to assist with debugging which SHOULD NOT be displayed to end users + message?: string + [key: string]: unknown +} + +export class BitStringStatusListMessage { + public constructor(options: BitStringStatusListMessageOptions) { + if (options) { + this.status = options.status + this.message = options.message + } + } + + @IsString() + public status!: string + + @IsString() + public message?: string; + + [key: string]: unknown | undefined +} + +export class BitStringStatusListEntry extends W3cCredentialStatus { + public constructor(options: { + id: string + serviceEndpoint: string + recipientKeys: string[] + routingKeys?: string[] + accept?: string[] + priority?: number + }) { + super({ ...options, type: BitStringStatusListEntry.type }) + } + public static type = 'BitstringStatusListEntry' + + @IsEnum(BitstringStatusListCredentialStatusPurpose) + @IsString() + public statusPurpose!: BitstringStatusListCredentialStatusPurpose + + @IsString() + public statusListIndex!: string + + @IsString() + public statusListCredential!: string + + @IsNumber() + public statusSize?: number + + @Type(() => BitStringStatusListMessage) + public statusMessage?: BitStringStatusListStatusMessage[] +} + +export interface BitStringStatusListStatusMessage { + // a string representing the hexadecimal value of the status prefixed with 0x + status: string + // a string used by software developers to assist with debugging which SHOULD NOT be displayed to end users + message?: string + // We can have some key value pairs as well + [key: string]: unknown +} + +export interface IBitStringStatusListCredentialStatus { + id: string + // Since currenlty we are only trying to support 'BitStringStatusListEntry' + type: 'BitstringStatusListEntry' + statusPurpose: BitstringStatusListCredentialStatusPurpose + // Unique identifier for the specific credential + statusListIndex: string + // Must be url referencing to a VC of type 'BitstringStatusListCredential' + statusListCredential: string + // The statusSize indicates the size of the status entry in bits + statusSize?: number + // Must be preset if statusPurpose is message + /** + * the length of which MUST equal the number of possible status messages indicated by statusSize + * (e.g., statusMessage array MUST have 2 elements if statusSize has 1 bit, + * 4 elements if statusSize has 2 bits, 8 elements if statusSize has 3 bits, etc.). + */ + statusMessage?: BitStringStatusListStatusMessage[] + // An implementer MAY include the statusReference property. If present, its value MUST be a URL or an array of URLs [URL] which dereference to material related to the status + statusReference?: string | string[] +} + +export class BitStringStatusListCredentialStatus extends W3cCredential { + public credentialStatus?: BitStringStatusListEntry | undefined +} + +export class BitStringStatusListCredentialDetail extends JsonLdCredentialDetail { + public credential!: BitStringStatusListCredentialStatus +} + +export class BitStringStatusListCredential extends W3cCredential { + public constructor(options: { + id: string + serviceEndpoint: string + recipientKeys: string[] + routingKeys?: string[] | undefined + accept?: string[] + priority?: number + issuer: string + issuanceDate: string + credentialSubject: BitStringStatusListCredentialSubject + }) { + super({ + ...options, + type: ['VerifiableCredential', BitStringStatusListEntry.type], + }) + } + + @IsString() + public credentialSubject!: BitStringStatusListCredentialSubject +} + +// Define an interface for `credentialSubject` +export interface BitStringStatusListCredentialSubject extends W3cCredentialSubject { + type: 'BitstringStatusList' + statusPurpose: BitstringStatusListCredentialStatusPurpose + encodedList: string +} + +// // Define an interface for the `credential` object that uses `CredentialSubject` +// export interface Credential { +// credentialSubject: CredentialSubject +// } + +// // Use the `Credential` interface within `BitStringStatusListCredential` +// export interface BitStringStatusListCredential { +// credential: Credential +// } diff --git a/packages/core/src/modules/vc/data-integrity/VerifyBitStringCredentialStatus.ts b/packages/core/src/modules/vc/models/credential/status-list/bitstring-status-list/VerifyBitStringCredentialStatus.ts similarity index 81% rename from packages/core/src/modules/vc/data-integrity/VerifyBitStringCredentialStatus.ts rename to packages/core/src/modules/vc/models/credential/status-list/bitstring-status-list/VerifyBitStringCredentialStatus.ts index b467ab3ee9..a7c35886f8 100644 --- a/packages/core/src/modules/vc/data-integrity/VerifyBitStringCredentialStatus.ts +++ b/packages/core/src/modules/vc/models/credential/status-list/bitstring-status-list/VerifyBitStringCredentialStatus.ts @@ -1,11 +1,10 @@ -import type { AgentContext } from '../../../agent/context' -import type { W3cJsonCredential } from '../models/credential/W3cJsonCredential' -import type { BitStringStatusListCredential } from '../models/credential/W3cJsonCredentialStatus' +import type { BitStringStatusListCredentialStatus, BitStringStatusListCredential } from './BitStringStatusList' +import type { AgentContext } from '../../../../../../agent/context' import * as pako from 'pako' -import { CredoError } from '../../../error' -import { validateStatus } from '../models/credential/W3cCredentialStatus' +import { CredoError } from '../../../../../../error' +import { validateStatus } from '../W3cCredentialStatus' // Function to fetch and parse the bit string status list credential const fetchBitStringStatusListCredential = async ( @@ -25,7 +24,10 @@ const fetchBitStringStatusListCredential = async ( } } -export const verifyBitStringCredentialStatus = async (credential: W3cJsonCredential, agentContext: AgentContext) => { +export const verifyBitStringCredentialStatus = async ( + credential: BitStringStatusListCredentialStatus, + agentContext: AgentContext +) => { const { credentialStatus } = credential if (Array.isArray(credentialStatus)) { @@ -50,7 +52,7 @@ export const verifyBitStringCredentialStatus = async (credential: W3cJsonCredent ) // Decode the encoded bit string - const encodedBitString = bitStringStatusListCredential.credential.credentialSubject.encodedList + const encodedBitString = bitStringStatusListCredential.credentialSubject.encodedList const compressedBuffer = Uint8Array.from(atob(encodedBitString), (char) => char.charCodeAt(0)) // Decompress the bit string using pako diff --git a/packages/core/src/modules/vc/models/credential/status-list/bitstring-status-list/index.ts b/packages/core/src/modules/vc/models/credential/status-list/bitstring-status-list/index.ts new file mode 100644 index 0000000000..e69de29bb2 diff --git a/packages/core/src/modules/vc/models/credential/status-list/index.ts b/packages/core/src/modules/vc/models/credential/status-list/index.ts new file mode 100644 index 0000000000..e69de29bb2 From 46b87bfd0ec4b7862751135141e9d18be4129bae Mon Sep 17 00:00:00 2001 From: Krishna Waske Date: Tue, 3 Dec 2024 08:37:28 +0530 Subject: [PATCH 05/13] chore: remove unnecessary code from credentials API Signed-off-by: Krishna Waske --- .../src/modules/credentials/CredentialsApi.ts | 20 ------------------- 1 file changed, 20 deletions(-) diff --git a/packages/core/src/modules/credentials/CredentialsApi.ts b/packages/core/src/modules/credentials/CredentialsApi.ts index d9c145a028..0b0927d0cb 100644 --- a/packages/core/src/modules/credentials/CredentialsApi.ts +++ b/packages/core/src/modules/credentials/CredentialsApi.ts @@ -24,8 +24,6 @@ import type { CredentialExchangeRecord } from './repository/CredentialExchangeRe import type { AgentMessage } from '../../agent/AgentMessage' import type { Query, QueryOptions } from '../../storage/StorageService' -import * as pako from 'pako' - import { AgentContext } from '../../agent' import { MessageSender } from '../../agent/MessageSender' import { getOutboundMessageContext } from '../../agent/getOutboundMessageContext' @@ -34,7 +32,6 @@ import { CredoError } from '../../error' import { Logger } from '../../logger' import { inject, injectable } from '../../plugins' import { DidCommMessageRepository } from '../../storage/didcomm/DidCommMessageRepository' -import { Buffer } from '../../utils' import { ConnectionService } from '../connections/services' import { RoutingService } from '../routing/services/RoutingService' @@ -707,21 +704,4 @@ export class CredentialsApi implements Credent return this.getProtocol(credentialExchangeRecord.protocolVersion) } - - private async encodeBitString(bitString: string): Promise { - // Convert the bitString to a Uint8Array - const buffer = new TextEncoder().encode(bitString) - const compressedBuffer = pako.gzip(buffer) - // Convert the compressed buffer to a base64 string - return Buffer.from(compressedBuffer).toString('base64') - } - - private async decodeBitSting(bitString: string): Promise { - // Decode base64 string to Uint8Array - const compressedBuffer = Uint8Array.from(atob(bitString), (c) => c.charCodeAt(0)) - - // Decompress using pako - const decompressedBuffer = pako.ungzip(compressedBuffer, { to: 'string' }) - return decompressedBuffer - } } From ed8a3a817aa7a4dccfe31d022419648d33f9ff08 Mon Sep 17 00:00:00 2001 From: Krishna Waske Date: Tue, 3 Dec 2024 08:38:57 +0530 Subject: [PATCH 06/13] chore: remove unnecessary code from credentials API Signed-off-by: Krishna Waske --- packages/core/src/modules/credentials/CredentialsApi.ts | 1 + packages/core/src/modules/credentials/CredentialsApiOptions.ts | 2 -- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/core/src/modules/credentials/CredentialsApi.ts b/packages/core/src/modules/credentials/CredentialsApi.ts index 0b0927d0cb..ea75caab92 100644 --- a/packages/core/src/modules/credentials/CredentialsApi.ts +++ b/packages/core/src/modules/credentials/CredentialsApi.ts @@ -64,6 +64,7 @@ export interface CredentialsApi { // Send Credential revocation notification Methods sendRevocationNotification(options: SendRevocationNotificationOptions): Promise + // out of band createOffer(options: CreateCredentialOfferOptions): Promise<{ message: AgentMessage diff --git a/packages/core/src/modules/credentials/CredentialsApiOptions.ts b/packages/core/src/modules/credentials/CredentialsApiOptions.ts index 38775a96a5..98d3f4ace3 100644 --- a/packages/core/src/modules/credentials/CredentialsApiOptions.ts +++ b/packages/core/src/modules/credentials/CredentialsApiOptions.ts @@ -44,8 +44,6 @@ interface BaseOptions { goal?: string } -export type JsonLdRevocationStatus = '0' | '1' - /** * Interface for CredentialsApi.proposeCredential. Will send a proposal. */ From 26be6f2545084bd4f8115af734c81d3c9c521990 Mon Sep 17 00:00:00 2001 From: Krishna Waske Date: Tue, 3 Dec 2024 08:42:22 +0530 Subject: [PATCH 07/13] chore: remove bitstring specific credential status from jsonld cred formats Signed-off-by: Krishna Waske --- .../formats/jsonld/JsonLdCredentialFormat.ts | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/packages/core/src/modules/credentials/formats/jsonld/JsonLdCredentialFormat.ts b/packages/core/src/modules/credentials/formats/jsonld/JsonLdCredentialFormat.ts index f4d003fa5b..71a5dcfa44 100644 --- a/packages/core/src/modules/credentials/formats/jsonld/JsonLdCredentialFormat.ts +++ b/packages/core/src/modules/credentials/formats/jsonld/JsonLdCredentialFormat.ts @@ -1,6 +1,7 @@ import type { JsonObject } from '../../../../types' import type { SingleOrArray } from '../../../../utils' import type { W3cIssuerOptions } from '../../../vc/models/credential/W3cIssuer' +import type { W3cCredentialStatusOptions } from '../../../vc/models/credential/status-list/W3cCredentialStatus' import type { CredentialFormat } from '../CredentialFormat' export interface JsonCredential { @@ -11,18 +12,10 @@ export interface JsonCredential { issuanceDate: string expirationDate?: string credentialSubject: SingleOrArray - credentialStatus?: SingleOrArray + credentialStatus?: SingleOrArray [key: string]: unknown } -export interface BitstringStatusListEntry { - id: string - type: string - statusPurpose: string - statusListIndex: string - statusListCredential: string -} - /** * Format for creating a jsonld proposal, offer or request. */ From 444551d3fe5ae4b8b6421807cb623e405a64393d Mon Sep 17 00:00:00 2001 From: Krishna Waske Date: Tue, 3 Dec 2024 08:44:10 +0530 Subject: [PATCH 08/13] chore: add appropriate format based error Signed-off-by: Krishna Waske --- packages/core/src/modules/vc/W3cCredentialService.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/core/src/modules/vc/W3cCredentialService.ts b/packages/core/src/modules/vc/W3cCredentialService.ts index 8ad064941d..55bfad4461 100644 --- a/packages/core/src/modules/vc/W3cCredentialService.ts +++ b/packages/core/src/modules/vc/W3cCredentialService.ts @@ -212,7 +212,7 @@ export class W3cCredentialService { const revoked = await this.w3cJsonLdCredentialService.revokeCredential(agentContext, options) return revoked as unknown as W3cVerifiablePresentation } else { - throw new CredoError(`Unsupported format in options. Format must be either 'jwt_vp' or 'ldp_vp'`) + throw new CredoError(`Unsupported format in options. Format must be either 'jwt_vc' or 'ldp_vc'`) } } From 078c43c44d7d15886b092ce3ba93de0e655c26fb Mon Sep 17 00:00:00 2001 From: Krishna Waske Date: Tue, 3 Dec 2024 08:47:02 +0530 Subject: [PATCH 09/13] chore: rename symbol Signed-off-by: Krishna Waske --- packages/core/src/modules/vc/W3cCredentialService.ts | 4 ++-- packages/core/src/modules/vc/W3cCredentialServiceOptions.ts | 2 +- packages/core/src/modules/vc/W3cCredentialsApi.ts | 4 ++-- .../modules/vc/data-integrity/W3cJsonLdCredentialService.ts | 4 ++-- .../core/src/modules/vc/jwt-vc/W3cJwtCredentialService.ts | 4 ++-- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/packages/core/src/modules/vc/W3cCredentialService.ts b/packages/core/src/modules/vc/W3cCredentialService.ts index 55bfad4461..5a38ed147b 100644 --- a/packages/core/src/modules/vc/W3cCredentialService.ts +++ b/packages/core/src/modules/vc/W3cCredentialService.ts @@ -22,7 +22,7 @@ import type { Query, QueryOptions } from '../../storage/StorageService' import { CredoError } from '../../error' import { injectable } from '../../plugins' -import { RevokeCredentialOption } from './W3cCredentialServiceOptions' +import { RevokeCredentialOptions } from './W3cCredentialServiceOptions' import { CREDENTIALS_CONTEXT_V1_URL } from './constants' import { W3cJsonLdVerifiableCredential } from './data-integrity' import { W3cJsonLdCredentialService } from './data-integrity/W3cJsonLdCredentialService' @@ -197,7 +197,7 @@ export class W3cCredentialService { */ public async revokeCredential( agentContext: AgentContext, - options: RevokeCredentialOption + options: RevokeCredentialOptions ) { const credentialRecod = await this.getCredentialRecordById(agentContext, options.credentialRecordId) if (!credentialRecod) { diff --git a/packages/core/src/modules/vc/W3cCredentialServiceOptions.ts b/packages/core/src/modules/vc/W3cCredentialServiceOptions.ts index 49bfcd8a98..ac30243f6d 100644 --- a/packages/core/src/modules/vc/W3cCredentialServiceOptions.ts +++ b/packages/core/src/modules/vc/W3cCredentialServiceOptions.ts @@ -209,6 +209,6 @@ export interface StoreCredentialOptions { /** * Interface for W3cCredentialsApi.revokeCredential. revoke a w3c credential. */ -export interface RevokeCredentialOption { +export interface RevokeCredentialOptions { credentialRecordId: string } diff --git a/packages/core/src/modules/vc/W3cCredentialsApi.ts b/packages/core/src/modules/vc/W3cCredentialsApi.ts index 5acf993d20..47c01b9a05 100644 --- a/packages/core/src/modules/vc/W3cCredentialsApi.ts +++ b/packages/core/src/modules/vc/W3cCredentialsApi.ts @@ -1,5 +1,5 @@ import type { - RevokeCredentialOption, + RevokeCredentialOptions, StoreCredentialOptions, W3cCreatePresentationOptions, W3cSignCredentialOptions, @@ -49,7 +49,7 @@ export class W3cCredentialsApi { // Revoke Credential Methods public async revokeCredential( - options: RevokeCredentialOption + options: RevokeCredentialOptions ): Promise { return this.w3cCredentialService.revokeCredential(this.agentContext, options) } diff --git a/packages/core/src/modules/vc/data-integrity/W3cJsonLdCredentialService.ts b/packages/core/src/modules/vc/data-integrity/W3cJsonLdCredentialService.ts index 7b6c465f7d..8d2edca8ac 100644 --- a/packages/core/src/modules/vc/data-integrity/W3cJsonLdCredentialService.ts +++ b/packages/core/src/modules/vc/data-integrity/W3cJsonLdCredentialService.ts @@ -3,7 +3,7 @@ import type { AgentContext } from '../../../agent/context' import type { Key } from '../../../crypto/Key' import type { SingleOrArray } from '../../../utils' import type { - RevokeCredentialOption, + RevokeCredentialOptions, W3cJsonLdSignCredentialOptions, W3cJsonLdSignPresentationOptions, W3cJsonLdVerifyCredentialOptions, @@ -334,7 +334,7 @@ export class W3cJsonLdCredentialService { // temporarily disable no unused var // eslint-disable-next-line @typescript-eslint/no-unused-vars - public async revokeCredential(_agentContext: AgentContext, _options: RevokeCredentialOption) { + public async revokeCredential(_agentContext: AgentContext, _options: RevokeCredentialOptions) { // revoke jwt cred throw new CredoError(`Revocation support not implemented for JsonLd`) } diff --git a/packages/core/src/modules/vc/jwt-vc/W3cJwtCredentialService.ts b/packages/core/src/modules/vc/jwt-vc/W3cJwtCredentialService.ts index 5b9d1e7127..e4b5738200 100644 --- a/packages/core/src/modules/vc/jwt-vc/W3cJwtCredentialService.ts +++ b/packages/core/src/modules/vc/jwt-vc/W3cJwtCredentialService.ts @@ -2,7 +2,7 @@ import type { AgentContext } from '../../../agent/context' import type { VerifyJwsResult } from '../../../crypto/JwsService' import type { DidPurpose, VerificationMethod } from '../../dids' import type { - RevokeCredentialOption, + RevokeCredentialOptions, W3cJwtSignCredentialOptions, W3cJwtSignPresentationOptions, W3cJwtVerifyCredentialOptions, @@ -542,7 +542,7 @@ export class W3cJwtCredentialService { // temporarily disable no unused var // eslint-disable-next-line @typescript-eslint/no-unused-vars - public async revokeCredential(_agentContext: AgentContext, _options: RevokeCredentialOption) { + public async revokeCredential(_agentContext: AgentContext, _options: RevokeCredentialOptions) { // revoke jwt cred throw new CredoError(`Revocation support not implemented for jwtVc`) } From f1dc6eef24dd4d2e77719361d7307ad7e2b85245 Mon Sep 17 00:00:00 2001 From: Krishna Waske Date: Sun, 22 Dec 2024 14:24:54 +0530 Subject: [PATCH 10/13] chore: update folder name Signed-off-by: Krishna Waske --- .../credentials/formats/jsonld/JsonLdCredentialFormat.ts | 2 +- .../src/modules/vc/data-integrity/W3cJsonLdCredentialService.ts | 2 +- packages/core/src/modules/vc/models/credential/W3cCredential.ts | 2 +- .../core/src/modules/vc/models/credential/W3cJsonCredential.ts | 2 +- .../W3cCredentialStatus.ts | 0 .../bitstring-status-list/BitStringStatusList.ts | 2 +- .../bitstring-status-list/VerifyBitStringCredentialStatus.ts | 0 .../bitstring-status-list/index.ts | 0 .../credential/{status-list => w3c-credential-status}/index.ts | 0 9 files changed, 5 insertions(+), 5 deletions(-) rename packages/core/src/modules/vc/models/credential/{status-list => w3c-credential-status}/W3cCredentialStatus.ts (100%) rename packages/core/src/modules/vc/models/credential/{status-list => w3c-credential-status}/bitstring-status-list/BitStringStatusList.ts (99%) rename packages/core/src/modules/vc/models/credential/{status-list => w3c-credential-status}/bitstring-status-list/VerifyBitStringCredentialStatus.ts (100%) rename packages/core/src/modules/vc/models/credential/{status-list => w3c-credential-status}/bitstring-status-list/index.ts (100%) rename packages/core/src/modules/vc/models/credential/{status-list => w3c-credential-status}/index.ts (100%) diff --git a/packages/core/src/modules/credentials/formats/jsonld/JsonLdCredentialFormat.ts b/packages/core/src/modules/credentials/formats/jsonld/JsonLdCredentialFormat.ts index 71a5dcfa44..b472dc3fe9 100644 --- a/packages/core/src/modules/credentials/formats/jsonld/JsonLdCredentialFormat.ts +++ b/packages/core/src/modules/credentials/formats/jsonld/JsonLdCredentialFormat.ts @@ -1,7 +1,7 @@ import type { JsonObject } from '../../../../types' import type { SingleOrArray } from '../../../../utils' import type { W3cIssuerOptions } from '../../../vc/models/credential/W3cIssuer' -import type { W3cCredentialStatusOptions } from '../../../vc/models/credential/status-list/W3cCredentialStatus' +import type { W3cCredentialStatusOptions } from '../../../vc/models/credential/w3c-credential-status/W3cCredentialStatus' import type { CredentialFormat } from '../CredentialFormat' export interface JsonCredential { diff --git a/packages/core/src/modules/vc/data-integrity/W3cJsonLdCredentialService.ts b/packages/core/src/modules/vc/data-integrity/W3cJsonLdCredentialService.ts index 8d2edca8ac..6b56163102 100644 --- a/packages/core/src/modules/vc/data-integrity/W3cJsonLdCredentialService.ts +++ b/packages/core/src/modules/vc/data-integrity/W3cJsonLdCredentialService.ts @@ -19,7 +19,7 @@ import { asArray, JsonTransformer } from '../../../utils' import { VerificationMethod } from '../../dids' import { getKeyFromVerificationMethod } from '../../dids/domain/key-type' import { W3cCredentialsModuleConfig } from '../W3cCredentialsModuleConfig' -import { validateStatus } from '../models/credential/status-list/W3cCredentialStatus' +import { validateStatus } from '../models/credential/w3c-credential-status/W3cCredentialStatus' import { w3cDate } from '../util' import { SignatureSuiteRegistry } from './SignatureSuiteRegistry' diff --git a/packages/core/src/modules/vc/models/credential/W3cCredential.ts b/packages/core/src/modules/vc/models/credential/W3cCredential.ts index e211cb073b..b92ad99a70 100644 --- a/packages/core/src/modules/vc/models/credential/W3cCredential.ts +++ b/packages/core/src/modules/vc/models/credential/W3cCredential.ts @@ -15,7 +15,7 @@ import { IsCredentialJsonLdContext } from '../../validators' import { W3cCredentialSchema } from './W3cCredentialSchema' import { IsW3cCredentialSubject, W3cCredentialSubject, W3cCredentialSubjectTransformer } from './W3cCredentialSubject' import { IsW3cIssuer, W3cIssuer, W3cIssuerTransformer } from './W3cIssuer' -import { W3cCredentialStatus } from './status-list/W3cCredentialStatus' +import { W3cCredentialStatus } from './w3c-credential-status/W3cCredentialStatus' export interface W3cCredentialOptions { context?: Array diff --git a/packages/core/src/modules/vc/models/credential/W3cJsonCredential.ts b/packages/core/src/modules/vc/models/credential/W3cJsonCredential.ts index e5b9f7ff93..bfe8500acc 100644 --- a/packages/core/src/modules/vc/models/credential/W3cJsonCredential.ts +++ b/packages/core/src/modules/vc/models/credential/W3cJsonCredential.ts @@ -1,4 +1,4 @@ -import type { W3cCredentialStatusOptions } from './status-list/W3cCredentialStatus' +import type { W3cCredentialStatusOptions } from './w3c-credential-status/W3cCredentialStatus' import type { JsonObject } from '../../../../types' import type { SingleOrArray } from '../../../../utils' diff --git a/packages/core/src/modules/vc/models/credential/status-list/W3cCredentialStatus.ts b/packages/core/src/modules/vc/models/credential/w3c-credential-status/W3cCredentialStatus.ts similarity index 100% rename from packages/core/src/modules/vc/models/credential/status-list/W3cCredentialStatus.ts rename to packages/core/src/modules/vc/models/credential/w3c-credential-status/W3cCredentialStatus.ts diff --git a/packages/core/src/modules/vc/models/credential/status-list/bitstring-status-list/BitStringStatusList.ts b/packages/core/src/modules/vc/models/credential/w3c-credential-status/bitstring-status-list/BitStringStatusList.ts similarity index 99% rename from packages/core/src/modules/vc/models/credential/status-list/bitstring-status-list/BitStringStatusList.ts rename to packages/core/src/modules/vc/models/credential/w3c-credential-status/bitstring-status-list/BitStringStatusList.ts index 00223665cb..d1869e27a6 100644 --- a/packages/core/src/modules/vc/models/credential/status-list/bitstring-status-list/BitStringStatusList.ts +++ b/packages/core/src/modules/vc/models/credential/w3c-credential-status/bitstring-status-list/BitStringStatusList.ts @@ -1,7 +1,7 @@ import { Type } from 'class-transformer' import { IsEnum, IsNumber, IsString } from 'class-validator' -import { JsonLdCredentialDetail, W3cCredentialSubject } from '../../../../../../..' +import { JsonLdCredentialDetail, W3cCredentialSubject } from '../../../../../..' import { W3cCredential } from '../../W3cCredential' import { W3cCredentialStatus } from '../W3cCredentialStatus' diff --git a/packages/core/src/modules/vc/models/credential/status-list/bitstring-status-list/VerifyBitStringCredentialStatus.ts b/packages/core/src/modules/vc/models/credential/w3c-credential-status/bitstring-status-list/VerifyBitStringCredentialStatus.ts similarity index 100% rename from packages/core/src/modules/vc/models/credential/status-list/bitstring-status-list/VerifyBitStringCredentialStatus.ts rename to packages/core/src/modules/vc/models/credential/w3c-credential-status/bitstring-status-list/VerifyBitStringCredentialStatus.ts diff --git a/packages/core/src/modules/vc/models/credential/status-list/bitstring-status-list/index.ts b/packages/core/src/modules/vc/models/credential/w3c-credential-status/bitstring-status-list/index.ts similarity index 100% rename from packages/core/src/modules/vc/models/credential/status-list/bitstring-status-list/index.ts rename to packages/core/src/modules/vc/models/credential/w3c-credential-status/bitstring-status-list/index.ts diff --git a/packages/core/src/modules/vc/models/credential/status-list/index.ts b/packages/core/src/modules/vc/models/credential/w3c-credential-status/index.ts similarity index 100% rename from packages/core/src/modules/vc/models/credential/status-list/index.ts rename to packages/core/src/modules/vc/models/credential/w3c-credential-status/index.ts From e72e89af4aee4873503877264eb6e39cea7a8de8 Mon Sep 17 00:00:00 2001 From: Krishna Waske Date: Fri, 27 Dec 2024 07:18:44 +0530 Subject: [PATCH 11/13] fix: typing and other minor issues while verifying Bitstring status list credential Signed-off-by: Krishna Waske --- .../W3cCredentialStatus.ts | 48 ++++++++++++-- .../BitStringStatusList.ts | 63 +++++++++++-------- .../VerifyBitStringCredentialStatus.ts | 25 +++----- 3 files changed, 86 insertions(+), 50 deletions(-) diff --git a/packages/core/src/modules/vc/models/credential/w3c-credential-status/W3cCredentialStatus.ts b/packages/core/src/modules/vc/models/credential/w3c-credential-status/W3cCredentialStatus.ts index adac951e71..9fef35d387 100644 --- a/packages/core/src/modules/vc/models/credential/w3c-credential-status/W3cCredentialStatus.ts +++ b/packages/core/src/modules/vc/models/credential/w3c-credential-status/W3cCredentialStatus.ts @@ -1,15 +1,22 @@ import { plainToInstance } from 'class-transformer' -import { IsString, validateOrReject } from 'class-validator' +import { IsEnum, IsString, validateOrReject } from 'class-validator' import { AgentContext } from '../../../../../agent/context' import { CredoError } from '../../../../../error' import { IsUri } from '../../../../../utils/validators' +import { BitStringStatusListEntry } from './bitstring-status-list/BitStringStatusList' +import { verifyBitStringCredentialStatus } from './bitstring-status-list/VerifyBitStringCredentialStatus' + export interface W3cCredentialStatusOptions { id: string type: string } +export enum W3cCredentialStatusSupportedTypes { + BitstringStatusListEntry = 'BitstringStatusListEntry', +} + export class W3cCredentialStatus { public constructor(options: W3cCredentialStatusOptions) { if (options) { @@ -23,6 +30,7 @@ export class W3cCredentialStatus { public id!: string @IsString() + @IsEnum(W3cCredentialStatusSupportedTypes, { message: 'Invalid credential status type' }) public type!: string } @@ -31,15 +39,43 @@ export const validateStatus = async ( status: W3cCredentialStatus | W3cCredentialStatus[], agentContext: AgentContext ): Promise => { - const entry = plainToInstance(W3cCredentialStatus, status) + let entry + + if (Array.isArray(status)) { + agentContext.config.logger.debug('Credential status type is array') + throw new CredoError( + 'Invalid credential status type. Currently only a single credentialStatus is supported per credential' + ) + } else entry = status + + switch (entry.type) { + case W3cCredentialStatusSupportedTypes.BitstringStatusListEntry: + agentContext.config.logger.debug('Credential status type is BitstringStatusListEntry') + entry = plainToInstance(BitStringStatusListEntry, entry) + break + default: + throw new CredoError( + `Invalid credential status type. Supported types are: ${Object.values(W3cCredentialStatusSupportedTypes).join( + ', ' + )}` + ) + } try { await validateOrReject(entry) + switch (entry.type) { + case W3cCredentialStatusSupportedTypes.BitstringStatusListEntry: + await verifyBitStringCredentialStatus(entry, agentContext) + break + default: + throw new CredoError( + `Invalid credential status type. Supported types are: ${Object.values(W3cCredentialStatusSupportedTypes).join( + ', ' + )}` + ) + } return true } catch (errors) { - agentContext.config.logger.debug(`Credential status validation failed: ${errors}`, { - stack: errors, - }) - throw new CredoError(`Invalid credential status type: ${errors}`) + throw new CredoError(`Error while validating credential status`, errors) } } diff --git a/packages/core/src/modules/vc/models/credential/w3c-credential-status/bitstring-status-list/BitStringStatusList.ts b/packages/core/src/modules/vc/models/credential/w3c-credential-status/bitstring-status-list/BitStringStatusList.ts index d1869e27a6..0683bf9718 100644 --- a/packages/core/src/modules/vc/models/credential/w3c-credential-status/bitstring-status-list/BitStringStatusList.ts +++ b/packages/core/src/modules/vc/models/credential/w3c-credential-status/bitstring-status-list/BitStringStatusList.ts @@ -2,13 +2,13 @@ import { Type } from 'class-transformer' import { IsEnum, IsNumber, IsString } from 'class-validator' import { JsonLdCredentialDetail, W3cCredentialSubject } from '../../../../../..' -import { W3cCredential } from '../../W3cCredential' +import { W3cCredential, W3cCredentialOptions } from '../../W3cCredential' import { W3cCredentialStatus } from '../W3cCredentialStatus' // The purpose can be anything apart from this as well export enum BitstringStatusListCredentialStatusPurpose { - 'revocation' = 'revocation', - 'suspension' = 'suspension', + Revocation = 'revocation', + Suspension = 'suspension', } export interface BitStringStatusListMessageOptions { @@ -36,16 +36,23 @@ export class BitStringStatusListMessage { [key: string]: unknown | undefined } +/** + * Status list Entry, used to check status of a credential being issued or verified during presentaton. + * + * @see https://www.w3.org/TR/vc-bitstring-status-list/#bitstringstatuslistentry + */ export class BitStringStatusListEntry extends W3cCredentialStatus { - public constructor(options: { - id: string - serviceEndpoint: string - recipientKeys: string[] - routingKeys?: string[] - accept?: string[] - priority?: number - }) { + public constructor(options: IBitStringStatusListEntryOptions) { super({ ...options, type: BitStringStatusListEntry.type }) + + if (options) { + this.statusPurpose = options.statusPurpose + this.statusListCredential = options.statusListCredential + this.statusListIndex = options.statusListIndex + + if (options.statusSize) this.statusSize = options.statusSize + if (options.statusMessage) this.statusMessage = options.statusMessage + } } public static type = 'BitstringStatusListEntry' @@ -75,12 +82,11 @@ export interface BitStringStatusListStatusMessage { [key: string]: unknown } -export interface IBitStringStatusListCredentialStatus { +export interface IBitStringStatusListEntryOptions { id: string - // Since currenlty we are only trying to support 'BitStringStatusListEntry' type: 'BitstringStatusListEntry' statusPurpose: BitstringStatusListCredentialStatusPurpose - // Unique identifier for the specific credential + // Unique identifier for specific credential statusListIndex: string // Must be url referencing to a VC of type 'BitstringStatusListCredential' statusListCredential: string @@ -105,28 +111,33 @@ export class BitStringStatusListCredentialDetail extends JsonLdCredentialDetail public credential!: BitStringStatusListCredentialStatus } +/** + * StatusListCredential describes the format of the verifiable credential that encapsulates the status list. + * + * @see https://www.w3.org/TR/vc-bitstring-status-list/#bitstringstatuslistcredential + */ export class BitStringStatusListCredential extends W3cCredential { - public constructor(options: { - id: string - serviceEndpoint: string - recipientKeys: string[] - routingKeys?: string[] | undefined - accept?: string[] - priority?: number - issuer: string - issuanceDate: string - credentialSubject: BitStringStatusListCredentialSubject - }) { + public constructor(options: IBitStringStatusListCredentialOptions) { super({ ...options, - type: ['VerifiableCredential', BitStringStatusListEntry.type], + type: BitStringStatusListCredential.type, }) + + if (options) { + this.credentialSubject = options.credentialSubject + } } + public static type = ['VerifiableCredential', 'BitstringStatusListCredential'] @IsString() public credentialSubject!: BitStringStatusListCredentialSubject } +export interface IBitStringStatusListCredentialOptions extends Omit { + type: ['VerifiableCredential', 'BitstringStatusListCredential'] + credentialSubject: BitStringStatusListCredentialSubject +} + // Define an interface for `credentialSubject` export interface BitStringStatusListCredentialSubject extends W3cCredentialSubject { type: 'BitstringStatusList' diff --git a/packages/core/src/modules/vc/models/credential/w3c-credential-status/bitstring-status-list/VerifyBitStringCredentialStatus.ts b/packages/core/src/modules/vc/models/credential/w3c-credential-status/bitstring-status-list/VerifyBitStringCredentialStatus.ts index a7c35886f8..d9568c8ea2 100644 --- a/packages/core/src/modules/vc/models/credential/w3c-credential-status/bitstring-status-list/VerifyBitStringCredentialStatus.ts +++ b/packages/core/src/modules/vc/models/credential/w3c-credential-status/bitstring-status-list/VerifyBitStringCredentialStatus.ts @@ -1,10 +1,9 @@ -import type { BitStringStatusListCredentialStatus, BitStringStatusListCredential } from './BitStringStatusList' +import type { BitStringStatusListCredential, BitStringStatusListEntry } from './BitStringStatusList' import type { AgentContext } from '../../../../../../agent/context' import * as pako from 'pako' import { CredoError } from '../../../../../../error' -import { validateStatus } from '../W3cCredentialStatus' // Function to fetch and parse the bit string status list credential const fetchBitStringStatusListCredential = async ( @@ -14,7 +13,7 @@ const fetchBitStringStatusListCredential = async ( const response = await agentContext.config.agentDependencies.fetch(url, { method: 'GET' }) if (!response.ok) { - throw new CredoError(`Failed to fetch bit string status list. HTTP Status: ${response.status}`) + throw new CredoError(`Failed to fetch BitStringStatusListCredential status list. HTTP Status: ${response.status}`) } try { @@ -25,24 +24,14 @@ const fetchBitStringStatusListCredential = async ( } export const verifyBitStringCredentialStatus = async ( - credential: BitStringStatusListCredentialStatus, + credentialStatus: BitStringStatusListEntry, agentContext: AgentContext ) => { - const { credentialStatus } = credential - if (Array.isArray(credentialStatus)) { - throw new CredoError('Verifying credential status as an array for JSON-LD credentials is currently not supported') - } - - if (!credentialStatus || credentialStatus.statusListIndex === undefined) { - throw new CredoError('Invalid credential status format') - } - - // Validate credentialStatus using the class-based approach - const isValid = await validateStatus(credentialStatus, agentContext) - - if (!isValid) { - throw new CredoError('Invalid credential status type. Expected BitstringStatusList') + agentContext.config.logger.debug('Credential status type is array') + throw new CredoError( + 'Invalid credential status type. Currently only a single BitstringStatusListEntry is supported per credential' + ) } // Fetch the bit string status list credential From cb92fdf6dd9a9420a29802cd9e3a824f801f561a Mon Sep 17 00:00:00 2001 From: Krishna Waske Date: Sun, 29 Dec 2024 15:33:39 +0530 Subject: [PATCH 12/13] chore: add named imports from pako Signed-off-by: Krishna Waske --- .../bitstring-status-list/VerifyBitStringCredentialStatus.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/core/src/modules/vc/models/credential/w3c-credential-status/bitstring-status-list/VerifyBitStringCredentialStatus.ts b/packages/core/src/modules/vc/models/credential/w3c-credential-status/bitstring-status-list/VerifyBitStringCredentialStatus.ts index d9568c8ea2..bf60500b66 100644 --- a/packages/core/src/modules/vc/models/credential/w3c-credential-status/bitstring-status-list/VerifyBitStringCredentialStatus.ts +++ b/packages/core/src/modules/vc/models/credential/w3c-credential-status/bitstring-status-list/VerifyBitStringCredentialStatus.ts @@ -1,7 +1,7 @@ import type { BitStringStatusListCredential, BitStringStatusListEntry } from './BitStringStatusList' import type { AgentContext } from '../../../../../../agent/context' -import * as pako from 'pako' +import { ungzip } from 'pako' import { CredoError } from '../../../../../../error' @@ -45,7 +45,7 @@ export const verifyBitStringCredentialStatus = async ( const compressedBuffer = Uint8Array.from(atob(encodedBitString), (char) => char.charCodeAt(0)) // Decompress the bit string using pako - const decodedBitString = pako.ungzip(compressedBuffer, { to: 'string' }) + const decodedBitString = ungzip(compressedBuffer, { to: 'string' }) const statusListIndex = Number(credentialStatus.statusListIndex) // Ensure the statusListIndex is within bounds From 535cdbd42b68a0f9c4386ccd0cea45b49652f4ac Mon Sep 17 00:00:00 2001 From: Krishna Waske Date: Sun, 29 Dec 2024 15:34:22 +0530 Subject: [PATCH 13/13] chore: update error for verifying bit string status list credential Signed-off-by: Krishna Waske --- .../bitstring-status-list/VerifyBitStringCredentialStatus.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/core/src/modules/vc/models/credential/w3c-credential-status/bitstring-status-list/VerifyBitStringCredentialStatus.ts b/packages/core/src/modules/vc/models/credential/w3c-credential-status/bitstring-status-list/VerifyBitStringCredentialStatus.ts index bf60500b66..b2aefe289d 100644 --- a/packages/core/src/modules/vc/models/credential/w3c-credential-status/bitstring-status-list/VerifyBitStringCredentialStatus.ts +++ b/packages/core/src/modules/vc/models/credential/w3c-credential-status/bitstring-status-list/VerifyBitStringCredentialStatus.ts @@ -55,7 +55,10 @@ export const verifyBitStringCredentialStatus = async ( // Check if the credential is revoked if (decodedBitString[statusListIndex] === '1') { - throw new CredoError(`Credential at index ${credentialStatus.statusListIndex} is revoked.`) + // To do: The error can be updated once we add support for status messages + throw new CredoError( + `Credential at index ${credentialStatus.statusListIndex} is in ${bitStringStatusListCredential.credentialSubject.statusPurpose} state.` + ) } return true