Skip to content
Snippets Groups Projects
Commit 77c7bdea authored by Berend Sliedrecht's avatar Berend Sliedrecht
Browse files

feat(ssi-abstraction): check revocation state


Signed-off-by: default avatarBerend Sliedrecht <berend@animo.id>
parent 9e561036
No related branches found
No related tags found
2 merge requests!37Draft: Modifications for manual testing,!25feat(ssi): revocation ssi-abstraction
......@@ -2,6 +2,11 @@ import type { BaseEventInput } from './baseEvents.js';
import { BaseEvent } from './baseEvents.js';
export enum RevocationState {
Issued,
Revoked,
}
export type EventAnonCredsRevocationRevokeInput = BaseEventInput<{
credentialId: string;
}>;
......@@ -100,3 +105,27 @@ export class EventAnonCredsRevocationTailsFile extends BaseEvent<{
);
}
}
export type EventAnonCredsRevocationCheckCredentialStatusInput =
BaseEventInput<{
credentialId: string;
}>;
export class EventAnonCredsRevocationCheckCredentialStatus extends BaseEvent<{
state: RevocationState;
}> {
public static token = 'anoncreds.revocation.checkCredentialStatus';
public get instance() {
return this.data;
}
public static fromEvent(e: EventAnonCredsRevocationCheckCredentialStatus) {
return new EventAnonCredsRevocationCheckCredentialStatus(
e.data,
e.tenantId,
e.id,
e.type,
e.timestamp,
);
}
}
import { Controller } from '@nestjs/common';
import { MessagePattern } from '@nestjs/microservices';
import {
EventAnonCredsRevocationCheckCredentialStatus,
EventAnonCredsRevocationCheckCredentialStatusInput,
EventAnonCredsRevocationRegisterRevocationRegistryDefinition,
EventAnonCredsRevocationRegisterRevocationRegistryDefinitionInput,
EventAnonCredsRevocationRegisterRevocationStatusList,
......@@ -60,4 +62,14 @@ export class RevocationController {
options.tenantId,
);
}
@MessagePattern(EventAnonCredsRevocationCheckCredentialStatus.token)
public async checkCredentialStatus(
options: EventAnonCredsRevocationCheckCredentialStatusInput,
): Promise<EventAnonCredsRevocationCheckCredentialStatus> {
return new EventAnonCredsRevocationCheckCredentialStatus(
await this.revocationService.checkCredentialStatus(options),
options.tenantId,
);
}
}
import type {
EventAnonCredsRevocationRegisterRevocationRegistryDefinition,
EventAnonCredsRevocationRegisterRevocationRegistryDefinitionInput,
EventAnonCredsRevocationRegisterRevocationStatusList,
EventAnonCredsRevocationRegisterRevocationStatusListInput,
EventAnonCredsRevocationRevoke,
EventAnonCredsRevocationRevokeInput,
EventAnonCredsRevocationTailsFile,
EventAnonCredsRevocationTailsFileInput,
} from '@ocm/shared';
import type { TenantAgent } from '../agent.service.js';
import { Injectable } from '@nestjs/common';
import {
RevocationState,
type EventAnonCredsRevocationCheckCredentialStatus,
type EventAnonCredsRevocationCheckCredentialStatusInput,
type EventAnonCredsRevocationRegisterRevocationRegistryDefinition,
type EventAnonCredsRevocationRegisterRevocationRegistryDefinitionInput,
type EventAnonCredsRevocationRegisterRevocationStatusList,
type EventAnonCredsRevocationRegisterRevocationStatusListInput,
type EventAnonCredsRevocationRevoke,
type EventAnonCredsRevocationRevokeInput,
type EventAnonCredsRevocationTailsFile,
type EventAnonCredsRevocationTailsFileInput,
} from '@ocm/shared';
import { readFile } from 'node:fs/promises';
import { AgentService } from '../agent.service.js';
......@@ -42,60 +46,22 @@ export class RevocationService {
> {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
return this.withTenantService.invoke(tenantId, async (t) => {
const credential = await t.credentials.getById(credentialId);
const metadata = credential.metadata.get<AnonCredsCredentialMetadata>(
'_anoncreds/credential',
);
const {
credentialRevocationId,
revocationStatusList,
revocationRegistryId,
} = await this.getRevocationParts(t, credentialId);
if (
!metadata ||
!metadata.revocationRegistryId ||
!metadata.credentialRevocationId
) {
if (revocationStatusList.revocationList[credentialRevocationId] === 1) {
throw new Error(
`credential (${credentialId}) has no metadata, likley it was issued without support for revocation`,
);
}
const { revocationRegistryDefinition } =
await t.modules.anoncreds.getRevocationRegistryDefinition(
metadata.revocationRegistryId,
);
if (!revocationRegistryDefinition) {
throw new Error(
`Could not find the revocation registry definition for id: ${metadata.revocationRegistryId}`,
);
}
const timestamp = Math.floor(Date.now() / 1000);
const { revocationStatusList } =
await t.modules.anoncreds.getRevocationStatusList(
metadata.revocationRegistryId,
timestamp,
);
if (!revocationStatusList) {
throw new Error(
`Could not find the revocation status list for revocation registry definition id: ${metadata.revocationRegistryId} and timestamp: ${timestamp}`,
);
}
if (
revocationStatusList.revocationList[
Number(metadata.credentialRevocationId)
] === 1
) {
throw new Error(
`credential (${credentialId}), with revocation id ${metadata.credentialRevocationId}, is already in a revoked state`,
`credential (${credentialId}), with revocation id ${credentialRevocationId}, is already in a revoked state`,
);
}
const result = await t.modules.anoncreds.updateRevocationStatusList({
revocationStatusList: {
revocationRegistryDefinitionId: metadata.revocationRegistryId,
revokedCredentialIndexes: [Number(metadata.credentialRevocationId)],
revocationRegistryDefinitionId: revocationRegistryId,
revokedCredentialIndexes: [credentialRevocationId],
},
options: {},
});
......@@ -249,4 +215,77 @@ export class RevocationService {
};
});
}
public async checkCredentialStatus({
tenantId,
credentialId,
}: EventAnonCredsRevocationCheckCredentialStatusInput): Promise<
EventAnonCredsRevocationCheckCredentialStatus['data']
> {
return this.withTenantService.invoke(tenantId, async (t) => {
const { credentialRevocationId, revocationStatusList } =
await this.getRevocationParts(t, credentialId);
const revocationStatus =
revocationStatusList.revocationList[credentialRevocationId];
return {
state:
revocationStatus === 0
? RevocationState.Issued
: RevocationState.Revoked,
};
});
}
private async getRevocationParts(
tenantAgent: TenantAgent,
credentialId: string,
) {
const credential = await tenantAgent.credentials.getById(credentialId);
const metadata = credential.metadata.get<AnonCredsCredentialMetadata>(
'_anoncreds/credential',
);
if (
!metadata ||
!metadata.revocationRegistryId ||
!metadata.credentialRevocationId
) {
throw new Error(
`credential (${credentialId}) has no metadata, likley it was issued without support for revocation`,
);
}
const { revocationRegistryDefinition } =
await tenantAgent.modules.anoncreds.getRevocationRegistryDefinition(
metadata.revocationRegistryId,
);
if (!revocationRegistryDefinition) {
throw new Error(
`Could not find the revocation registry definition for id: ${metadata.revocationRegistryId}`,
);
}
const timestamp = Math.floor(Date.now() / 1000);
const { revocationStatusList } =
await tenantAgent.modules.anoncreds.getRevocationStatusList(
metadata.revocationRegistryId,
timestamp,
);
if (!revocationStatusList) {
throw new Error(
`Could not find the revocation status list for revocation registry definition id: ${metadata.revocationRegistryId} and timestamp: ${timestamp}`,
);
}
return {
credentialRevocationId: Number(metadata.credentialRevocationId),
revocationStatusList,
revocationRegistryId: metadata.revocationRegistryId,
};
}
}
import type { INestApplication } from '@nestjs/common';
import type { ClientProxy } from '@nestjs/microservices';
import type {
EventAnonCredsRevocationCheckCredentialStatusInput,
EventAnonCredsRevocationRegisterRevocationRegistryDefinitionInput,
EventAnonCredsRevocationRegisterRevocationStatusListInput,
EventAnonCredsRevocationRevokeInput,
......@@ -10,10 +11,12 @@ import type {
import { ClientsModule, Transport } from '@nestjs/microservices';
import { Test } from '@nestjs/testing';
import {
EventAnonCredsRevocationCheckCredentialStatus,
EventDidcommAnonCredsCredentialsOfferToSelf,
EventAnonCredsRevocationRegisterRevocationRegistryDefinition,
EventAnonCredsRevocationRegisterRevocationStatusList,
EventAnonCredsRevocationRevoke,
RevocationState,
} from '@ocm/shared';
import { randomBytes } from 'crypto';
import { firstValueFrom } from 'rxjs';
......@@ -253,5 +256,22 @@ describe('Revocation', () => {
const eventInstance = EventAnonCredsRevocationRevoke.fromEvent(response);
expect(eventInstance.instance).toBeNull();
}
await new Promise((r) => setTimeout(r, 2000));
// Check the revocation state
{
const response$ = client.send<
EventAnonCredsRevocationCheckCredentialStatus,
EventAnonCredsRevocationCheckCredentialStatusInput
>(EventAnonCredsRevocationCheckCredentialStatus.token, {
tenantId,
credentialId,
});
const response = await firstValueFrom(response$);
const eventInstance =
EventAnonCredsRevocationCheckCredentialStatus.fromEvent(response);
expect(eventInstance.instance.state).toBe(RevocationState.Revoked);
}
});
});
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment