Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: support w3c revocation #2072

Draft
wants to merge 31 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
a7d1893
feat: support w3c revocation
Oct 29, 2024
37f2bd3
feat: support w3c revocation
Oct 29, 2024
091b240
chore: verify credential status
GHkrishna Oct 29, 2024
b754327
chore: rearrange files
GHkrishna Dec 3, 2024
0e1793a
chore: remove unnecessary code from credentials API
GHkrishna Dec 3, 2024
1852cbf
chore: remove unnecessary code from credentials API
GHkrishna Dec 3, 2024
0019382
chore: remove bitstring specific credential status from jsonld cred f…
GHkrishna Dec 3, 2024
a662953
chore: add appropriate format based error
GHkrishna Dec 3, 2024
bf042bc
chore: rename symbol
GHkrishna Dec 3, 2024
a722ab6
chore: update folder name
GHkrishna Dec 22, 2024
64254e4
fix: typing and other minor issues while verifying Bitstring status l…
GHkrishna Dec 27, 2024
7c6edc8
chore: add named imports from pako
GHkrishna Dec 29, 2024
f5671a7
chore: update error for verifying bit string status list credential
GHkrishna Dec 29, 2024
62a21df
chore: move validate status logic to libraries
GHkrishna Jan 20, 2025
1355114
refactor: Invalidate array of bitStringStatusListCredential
GHkrishna Jan 20, 2025
62b7855
refactor: export files from index
GHkrishna Jan 20, 2025
fdd2eb4
refactor: separate bitstring statuslist and bitstring status list cre…
GHkrishna Jan 22, 2025
323c6ad
feat: add CredentialStatusBasedOnType
GHkrishna Jan 22, 2025
87e59db
reactor: remove duplicate code
GHkrishna Jan 22, 2025
26fc98c
chore: add comment
GHkrishna Jan 22, 2025
9797014
fix: add credential status compare while verifyReceivedCredentialMatc…
GHkrishna Jan 22, 2025
094c621
fix: remove credentialStatus from unsupported fields while accepting …
GHkrishna Jan 22, 2025
758b1f1
fix: imports
GHkrishna Jan 22, 2025
6bc085a
chore: update imports
GHkrishna Jan 22, 2025
fa130ba
fix: imports
GHkrishna Jan 22, 2025
096ca1e
chore: update minor type changes
GHkrishna Feb 2, 2025
a0c2eb0
fix: remove unnecessary tranformation
GHkrishna Feb 2, 2025
d3161ee
fix: verification of bitstring status list credential after fetching
GHkrishna Feb 2, 2025
183f361
chore: push pnpm-lock file
GHkrishna Feb 2, 2025
25f4153
fix: take claimformat from options instead of from credential record,…
GHkrishna Feb 2, 2025
96dadd7
fix: completed TODO
GHkrishna Feb 2, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions packages/core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,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",
Expand All @@ -71,6 +72,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",
Expand Down
2 changes: 1 addition & 1 deletion packages/core/src/modules/credentials/CredentialsApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ export interface CredentialsApi<CPs extends CredentialProtocol[]> {
// Issue Credential Methods
acceptCredential(options: AcceptCredentialOptions): Promise<CredentialExchangeRecord>

// Revoke Credential Methods
// Send Credential revocation notification Methods
sendRevocationNotification(options: SendRevocationNotificationOptions): Promise<void>

// out of band
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import type { JsonObject } from '../../../../types'
import type { SingleOrArray } from '../../../../utils'
import type { W3cIssuerOptions } from '../../../vc/models/credential/W3cIssuer'
import type { CredentialStatusBasedOnType } from '../../../vc/models/credential/w3c-credential-status/W3cCredentialStatus'
import type { CredentialFormat } from '../CredentialFormat'

export interface JsonCredential {
Expand All @@ -11,6 +12,7 @@ export interface JsonCredential {
issuanceDate: string
expirationDate?: string
credentialSubject: SingleOrArray<JsonObject>
credentialStatus?: SingleOrArray<CredentialStatusBasedOnType>
[key: string]: unknown
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ import type {

import { Attachment, AttachmentData } from '../../../../decorators/attachment/Attachment'
import { CredoError } from '../../../../error'
import { JsonEncoder, areObjectsEqual } from '../../../../utils'
import { JsonEncoder, areObjectsEqual, deepEquality } from '../../../../utils'
import { JsonTransformer } from '../../../../utils/JsonTransformer'
import { findVerificationMethodByKeyType } from '../../../dids/domain/DidDocument'
import { DidResolverService } from '../../../dids/services/DidResolverService'
Expand Down Expand Up @@ -235,7 +235,7 @@ export class JsonLdCredentialFormatService implements CredentialFormatService<Js
const options = credentialRequest.options

// Get a list of fields found in the options that are not supported at the moment
const unsupportedFields = ['challenge', 'domain', 'credentialStatus', 'created'] as const
const unsupportedFields = ['challenge', 'domain', 'created'] as const
const foundFields = unsupportedFields.filter((field) => options[field] !== undefined)

if (foundFields.length > 0) {
Expand Down Expand Up @@ -361,12 +361,17 @@ export class JsonLdCredentialFormatService implements CredentialFormatService<Js
throw new CredoError('Received credential proof purpose does not match proof purpose from credential request')
}

if (deepEquality(credential.credentialStatus, request.credential.credentialStatus)) {
throw new CredoError(
'Received credential credentialStatus does not match credentialStatus from credential request'
)
}

// Check whether the received credential (minus the proof) matches the credential request
if (!areObjectsEqual(jsonCredential, request.credential)) {
throw new CredoError('Received credential does not match credential request')
}

// TODO: add check for the credentialStatus once this is supported in Credo
}

public supportsFormat(format: string): boolean {
Expand Down
31 changes: 31 additions & 0 deletions packages/core/src/modules/vc/W3cCredentialService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import type { Query, QueryOptions } from '../../storage/StorageService'
import { CredoError } from '../../error'
import { injectable } from '../../plugins'

import { RevokeCredentialOptions } from './W3cCredentialServiceOptions'
import { CREDENTIALS_CONTEXT_V1_URL } from './constants'
import { W3cJsonLdVerifiableCredential } from './data-integrity'
import { W3cJsonLdCredentialService } from './data-integrity/W3cJsonLdCredentialService'
Expand Down Expand Up @@ -186,6 +187,36 @@ 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<Format extends ClaimFormat.JwtVc | ClaimFormat.LdpVc>(
agentContext: AgentContext,
options: RevokeCredentialOptions<Format>
) {
// Considering a revoker of a credential can be anyone apart from the issuer
// Not sure, if we need to get Credential record, as it might not be present for ayone apart from the issuer
// const credentialRecod = await this.getCredentialRecordById(agentContext, options.credentialRecordId)
// if (!credentialRecod) {
// throw new CredoError(`Credential with id ${options.credentialRecordId} not found`)
// }

if (options.format === ClaimFormat.JwtVc) {
const revoked = await this.w3cJwtCredentialService.revokeCredential(agentContext, options)
return revoked as unknown as W3cVerifiableCredential<Format>
} else if (options.format === ClaimFormat.LdpVc) {
const revoked = await this.w3cJsonLdCredentialService.revokeCredential(agentContext, options)
return revoked as unknown as W3cVerifiableCredential<Format>
} else {
throw new CredoError(`Unsupported format in options. Format must be either 'jwt_vc' or 'ldp_vc'`)
}
}

public async removeCredentialRecord(agentContext: AgentContext, id: string) {
await this.w3cCredentialRepository.deleteById(agentContext, id)
}
Expand Down
21 changes: 21 additions & 0 deletions packages/core/src/modules/vc/W3cCredentialServiceOptions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,12 @@ export type W3cSignPresentationOptions<Format extends ClaimFormat.JwtVp | ClaimF
? W3cJsonLdSignPresentationOptions
: W3cJwtSignPresentationOptions | W3cJsonLdSignPresentationOptions
export type W3cVerifyPresentationOptions = W3cJwtVerifyPresentationOptions | W3cJsonLdVerifyPresentationOptions
export type RevokeCredentialOptions<Format extends ClaimFormat.JwtVc | ClaimFormat.LdpVc | undefined = undefined> =
Format extends ClaimFormat.JwtVc
? W3cJwtRevokeCredentialOptions
: Format extends ClaimFormat.LdpVc
? W3cJsonLdRevokeCredentialOptions
: W3cJwtRevokeCredentialOptions | W3cJsonLdRevokeCredentialOptions

interface W3cSignCredentialOptionsBase {
/**
Expand Down Expand Up @@ -205,3 +211,18 @@ export interface W3cJsonLdVerifyPresentationOptions extends W3cVerifyPresentatio
export interface StoreCredentialOptions {
credential: W3cVerifiableCredential
}

/**
* Interface for W3cCredentialsApi.revokeCredential. revoke a w3c credential.
*/
export interface RevokeCredentialOptionsBase {
credentialRecordId: string
}

export interface W3cJwtRevokeCredentialOptions extends RevokeCredentialOptionsBase {
format: ClaimFormat.JwtVc
}

export interface W3cJsonLdRevokeCredentialOptions extends RevokeCredentialOptionsBase {
format: ClaimFormat.LdpVc
}
10 changes: 10 additions & 0 deletions packages/core/src/modules/vc/W3cCredentialsApi.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type {
RevokeCredentialOptions,
StoreCredentialOptions,
W3cCreatePresentationOptions,
W3cSignCredentialOptions,
Expand All @@ -14,6 +15,8 @@ import { AgentContext } from '../../agent'
import { injectable } from '../../plugins'

import { W3cCredentialService } from './W3cCredentialService'
import { W3cJsonLdVerifiableCredential } from './data-integrity'
import { W3cJwtVerifiableCredential } from './jwt-vc'

/**
* @public
Expand Down Expand Up @@ -44,6 +47,13 @@ export class W3cCredentialsApi {
return this.w3cCredentialService.getCredentialRecordById(this.agentContext, id)
}

// Revoke Credential Methods
public async revokeCredential<Format extends ClaimFormat.JwtVc | ClaimFormat.LdpVc>(
options: RevokeCredentialOptions<Format>
): Promise<W3cJwtVerifiableCredential | W3cJsonLdVerifiableCredential> {
return this.w3cCredentialService.revokeCredential<Format>(this.agentContext, options)
}

public async findCredentialRecordsByQuery(
query: Query<W3cCredentialRecord>,
queryOptions?: QueryOptions
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import type { AgentContext } from '../../../agent/context'
import type { Key } from '../../../crypto/Key'
import type { SingleOrArray } from '../../../utils'
import type {
W3cJsonLdRevokeCredentialOptions,
W3cJsonLdSignCredentialOptions,
W3cJsonLdSignPresentationOptions,
W3cJsonLdVerifyCredentialOptions,
Expand All @@ -23,6 +24,7 @@ import { w3cDate } from '../util'
import { SignatureSuiteRegistry } from './SignatureSuiteRegistry'
import { deriveProof } from './deriveProof'
import { assertOnlyW3cJsonLdVerifiableCredentials } from './jsonldUtil'
import { validateStatus } from './libraries/credentialStatus'
import jsonld from './libraries/jsonld'
import vc from './libraries/vc'
import { W3cJsonLdVerifiableCredential } from './models'
Expand Down Expand Up @@ -109,10 +111,12 @@ export class W3cJsonLdCredentialService {
credential: JsonTransformer.toJSON(options.credential),
suite: suites,
documentLoader: this.w3cCredentialsModuleConfig.documentLoader(agentContext),
checkStatus: ({ 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')
checkStatus: async ({ credential }: { credential: W3cJsonCredential }) => {
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,
Expand Down Expand Up @@ -259,12 +263,24 @@ export class W3cJsonLdCredentialService {
)
const allSuites = presentationSuites.concat(...credentialSuites)

const verifyCredentialStatus = options.verifyCredentialStatus ?? true
const verifyOptions: Record<string, unknown> = {
presentation: JsonTransformer.toJSON(options.presentation),
suite: allSuites,
challenge: options.challenge,
domain: options.domain,
documentLoader: this.w3cCredentialsModuleConfig.documentLoader(agentContext),
checkStatus: async ({ credential }: { credential: W3cJsonCredential }) => {
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,
}
},
}

// this is a hack because vcjs throws if purpose is passed as undefined or null
Expand Down Expand Up @@ -316,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: W3cJsonLdRevokeCredentialOptions) {
// revoke jwt cred
throw new CredoError(`Revocation support not implemented for JsonLd`)
}

public getVerificationMethodTypesByProofType(proofType: string): string[] {
return this.signatureSuiteRegistry.getByProofType(proofType).verificationMethodTypes
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import type { AgentContext } from '../../../../agent'
import type { W3cCredentialStatus } from '../../models/credential/w3c-credential-status/W3cCredentialStatus'


import { CredoError } from '../../../../error/CredoError'
import {
BitStringStatusListEntry,
verifyBitStringCredentialStatus,
} from '../../models/credential/w3c-credential-status'
import { W3cCredentialStatusSupportedTypes } from '../../models/credential/w3c-credential-status/W3cCredentialStatus'
import { SingleOrArray } from '../../../../utils'

// Function to validate the status using the updated method
export const validateStatus = async (
credentialStatus: SingleOrArray<W3cCredentialStatus>,
agentContext: AgentContext
): Promise<boolean> => {

if (Array.isArray(credentialStatus)) {
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'
)
}

switch (credentialStatus.type) {
case W3cCredentialStatusSupportedTypes.BitstringStatusListEntry:
agentContext.config.logger.debug('Credential status type is BitstringStatusListEntry')
try {
await verifyBitStringCredentialStatus(credentialStatus as unknown as BitStringStatusListEntry, agentContext)
} catch (errors) {
throw new CredoError(`Error while validating credential status`, errors)
}
break
default:
throw new CredoError(
`Invalid credential status type. Supported types are: ${Object.values(W3cCredentialStatusSupportedTypes).join(
', '
)}`
)
}
return true
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import type { AgentContext } from '../../../agent/context'
import type { VerifyJwsResult } from '../../../crypto/JwsService'
import type { DidPurpose, VerificationMethod } from '../../dids'
import type {
W3cJwtRevokeCredentialOptions,
W3cJwtSignCredentialOptions,
W3cJwtSignPresentationOptions,
W3cJwtVerifyCredentialOptions,
Expand Down Expand Up @@ -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: W3cJwtRevokeCredentialOptions) {
// revoke jwt cred
throw new CredoError(`Revocation support not implemented for jwtVc`)
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import type { W3cCredentialSubjectOptions } from './W3cCredentialSubject'
import type { W3cIssuerOptions } from './W3cIssuer'
import type { JsonObject } from '../../../../types'
import type { ValidationOptions } from 'class-validator'
Expand All @@ -13,9 +12,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 { IsW3cCredentialSubject, W3cCredentialSubject, W3cCredentialSubjectOptions, W3cCredentialSubjectTransformer } from './W3cCredentialSubject'
import { IsW3cIssuer, W3cIssuer, W3cIssuerTransformer } from './W3cIssuer'
import { W3cCredentialStatus } from './w3c-credential-status/W3cCredentialStatus'

export interface W3cCredentialOptions {
context?: Array<string | JsonObject>
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import type { W3cCredentialStatusOptions } from './w3c-credential-status/W3cCredentialStatus'
import type { JsonObject } from '../../../../types'
import type { SingleOrArray } from '../../../../utils'

Expand All @@ -9,5 +10,6 @@ export interface W3cJsonCredential {
issuanceDate: string
expirationDate?: string
credentialSubject: SingleOrArray<JsonObject>
credentialStatus?: SingleOrArray<W3cCredentialStatusOptions>
[key: string]: unknown
}
Loading