Skip to content

Commit

Permalink
Merge pull request #5 from SamagraX-RCW/mulitple-issuers
Browse files Browse the repository at this point in the history
feat: Allow signing from multiple issuers
  • Loading branch information
tushar5526 authored Oct 3, 2023
2 parents d8c4418 + 70a78ca commit 658ec3a
Show file tree
Hide file tree
Showing 7 changed files with 81 additions and 30 deletions.
2 changes: 1 addition & 1 deletion docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -43,4 +43,4 @@ networks:
external: true

volumes:
data:
data:
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ CREATE TYPE "VCStatus" AS ENUM ('PENDING', 'ISSUED', 'REVOKED');
CREATE TABLE "VerifiableCredentials" (
"id" TEXT NOT NULL,
"type" TEXT[],
"issuer" TEXT NOT NULL,
"issuer" TEXT[],
"issuanceDate" TEXT NOT NULL,
"expirationDate" TEXT NOT NULL,
"credential_schema" TEXT NOT NULL,
Expand Down
2 changes: 1 addition & 1 deletion prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ enum VCStatus {
model VerifiableCredentials {
id String @id @default(uuid())
type String[]
issuer String
issuer String[]
issuanceDate String
expirationDate String
credential_schema String
Expand Down
7 changes: 6 additions & 1 deletion src/credentials/credentials.fixtures.ts
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,12 @@ export const issueCredentialReturnTypeSchema = {
verificationMethod: { type: 'string' },
},
},
issuer: { type: 'string' },
issuer: {
"type": ["string", "array"],
"items": {
"type": "string"
}
},
issuanceDate: { type: 'string' },
expirationDate: { type: 'string' },
credentialSubject: {
Expand Down
20 changes: 20 additions & 0 deletions src/credentials/credentials.service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,11 @@ describe('CredentialsService', () => {
const getCredReqValidate = ajv.compile(getCredentialByIdSchema);

let issuerDID;
let secondIssuerDID;
let subjectDID;
let credentialSchemaID;
let sampleCredReqPayload;
let sampleMultipleIssuerReqPayload;

beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
Expand All @@ -57,6 +59,11 @@ describe('CredentialsService', () => {
]);
issuerDID = issuerDID[0].id;

secondIssuerDID = await identityUtilsService.generateDID([
'VerifiableCredentialTESTINGIssuer12',
]);
secondIssuerDID = secondIssuerDID[0].id;

subjectDID = await identityUtilsService.generateDID([
'VerifiableCredentialTESTINGIssuer',
]);
Expand All @@ -75,6 +82,13 @@ describe('CredentialsService', () => {
credentialSchemaID,
schema.data.schema.version
);

sampleMultipleIssuerReqPayload = generateCredentialRequestPayload(
[issuerDID, secondIssuerDID],
subjectDID,
credentialSchemaID,
schema.data.schema.version
);
});

it('service should be defined', () => {
Expand All @@ -87,6 +101,12 @@ describe('CredentialsService', () => {
expect(validate(newCred)).toBe(true);
});

it('should issue a credential with multiple issuers', async () => {
const newCred = await service.issueCredential(sampleMultipleIssuerReqPayload);
VCValidator.parse(newCred.credential);
expect(validate(newCred)).toBe(true);
});

it('should get a credential in JSON', async () => {
const newCred: any = await service.issueCredential(sampleCredReqPayload);
const cred = await service.getCredentialById(newCred.credential?.id);
Expand Down
76 changes: 51 additions & 25 deletions src/credentials/credentials.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import { IdentityUtilsService } from './utils/identity.utils.service';
import { RenderingUtilsService } from './utils/rendering.utils.service';
// eslint-disable-next-line @typescript-eslint/no-var-requires
const ION = require('@decentralized-identity/ion-tools');
const jsonwebtoken = require('jsonwebtoken');

@Injectable()
export class CredentialsService {
Expand Down Expand Up @@ -138,16 +139,34 @@ export class CredentialsService {
}
try {
// calling identity service to verify the issuer DID
const verificationMethod = credToVerify.issuer;
const did: DIDDocument = await this.identityUtilsService.resolveDID(
verificationMethod
);

let verificationMethod;
if(typeof credToVerify.issuer === 'string')
verificationMethod = [credToVerify.issuer];
else verificationMethod = credToVerify.issuer;

// VERIFYING THE JWS
await ION.verifyJws({
jws: credToVerify?.proof?.proofValue,
publicJwk: did.verificationMethod[0].publicKeyJwk,
});
let verifedCredential = false;
let credentialPayloadToVerify = credToVerify;

// We sign using the issuers array left to right
// verify right to left
for (let i = verificationMethod.length - 1; i >= 0; i--) {
const did: DIDDocument = await this.identityUtilsService.resolveDID(
verificationMethod[i]
);
// VERIFYING THE JWS
verifedCredential = await ION.verifyJws({
jws: credentialPayloadToVerify?.proof?.proofValue,
publicJwk: did.verificationMethod[0].publicKeyJwk,
});
if (!verifedCredential) break;

// Get the signed credential payload
const decodedToken = jsonwebtoken.decode(
credentialPayloadToVerify.proof.proofValue
);
credentialPayloadToVerify = JSON.parse(JSON.parse(decodedToken));
}
this.logger.debug('Verified JWS');
return {
status: status,
Expand All @@ -159,7 +178,7 @@ export class CredentialsService {
new Date(credToVerify.expirationDate).getTime() < Date.now()
? 'NOK'
: 'OK', // NOK represents expired
proof: 'OK',
proof: verifedCredential ? 'OK' : 'NOK',
},
],
};
Expand All @@ -172,8 +191,9 @@ export class CredentialsService {
}

async issueCredential(issueRequest: IssueCredentialDTO) {
this.logger.debug(`Received issue credential request`);
this.logger.debug(`Received issue credential request`, issueRequest);
const credInReq = issueRequest.credential;

// check for issuance date
if (!credInReq.issuanceDate)
credInReq.issuanceDate = new Date(Date.now()).toISOString();
Expand Down Expand Up @@ -209,16 +229,22 @@ export class CredentialsService {
}
// sign the credential
try {
credInReq['proof'] = {
proofValue: await this.identityUtilsService.signVC(
transformCredentialInput(credInReq as CredentialPayload),
credInReq.issuer
),
type: 'Ed25519Signature2020',
created: new Date().toISOString(),
verificationMethod: credInReq.issuer,
proofPurpose: 'assertionMethod',
};
let issuers;
if (typeof credInReq.issuer === 'string') issuers = [credInReq.issuer];
else issuers = credInReq.issuer;
for (let i = 0; i < issuers.length; i++) {
credInReq['proof'] = {
proofValue: await this.identityUtilsService.signVC(
transformCredentialInput(credInReq as CredentialPayload),
issuers[i]
),
type: 'Ed25519Signature2020',
created: new Date().toISOString(),
verificationMethod: issuers[i],
proofPurpose: 'assertionMethod',
};
this.logger.debug(`signed credential using issuer ${issuers[i]}`);
}
} catch (err) {
this.logger.error('Error signing the credential');
throw new InternalServerErrorException('Problem signing the credential');
Expand All @@ -231,9 +257,9 @@ export class CredentialsService {
data: {
id: credInReq.id,
type: credInReq.type,
issuer: credInReq.issuer as IssuerType as string,
issuanceDate: credInReq.issuanceDate,
expirationDate: credInReq.expirationDate,
issuer: credInReq.issuer as unknown as string[],
issuanceDate: credInReq.issuanceDate as string,
expirationDate: credInReq.expirationDate as string,
subject: credInReq.credentialSubject as JwtCredentialSubject,
subjectId: (credInReq.credentialSubject as JwtCredentialSubject).id,
proof: credInReq.proof as Proof,
Expand Down Expand Up @@ -285,7 +311,7 @@ export class CredentialsService {
const filteringSubject = getCreds.subject;
const credentials = await this.prisma.verifiableCredentials.findMany({
where: {
issuer: getCreds.issuer?.id,
issuer: { has: getCreds.issuer?.id },
AND: filteringSubject
? Object.keys(filteringSubject).map((key: string) => ({
subject: {
Expand Down
2 changes: 1 addition & 1 deletion src/credentials/types/validators/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ export const UnsignedVCValidator = z
'@context': ContextValidator,
id: z.string().optional(),
type: z.string().array().nonempty(),
issuer: ProfileValidator,
issuer: z.union([z.string(), z.array(z.string().nonempty())]),
issuanceDate: z.string(),
expirationDate: z.string().optional(),
credentialSubject: CredentialSubjectValidator.or(
Expand Down

0 comments on commit 658ec3a

Please sign in to comment.