Skip to content

Commit

Permalink
updates to verify
Browse files Browse the repository at this point in the history
  • Loading branch information
nitro-neal committed Feb 21, 2024
1 parent 80c59fd commit e11ea2d
Show file tree
Hide file tree
Showing 5 changed files with 77 additions and 7 deletions.
47 changes: 45 additions & 2 deletions packages/credentials/src/jwt.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import type {
import { Convert } from '@web5/common';
import { LocalKeyManager as CryptoApi } from '@web5/crypto';
import { DidDht, DidIon, DidKey, DidJwk, DidWeb, DidResolver, utils as didUtils } from '@web5/dids';
import { VcDataModel } from './verifiable-credential.js';
import { VpDataModel } from './verifiable-presentation.js';

const crypto = new CryptoApi();

Expand Down Expand Up @@ -116,14 +118,55 @@ export class Jwt {
* const verifiedJwt = await Jwt.verify({ jwt: myJwt });
* ```
*
* exp MUST represent the expirationDate property, encoded as a UNIX timestamp (NumericDate).
* iss MUST represent the issuer property of a verifiable credential or the holder property of a verifiable presentation.
* nbf MUST represent issuanceDate, encoded as a UNIX timestamp (NumericDate).
* jti MUST represent the id property of the verifiable credential or verifiable presentation.
* sub MUST represent the id property contained in the credentialSubject.
* @param options - Parameters for JWT verification
* @returns Verified JWT information including signer DID, header, and payload.
*/
static async verify(options: VerifyJwtOptions): Promise<JwtVerifyResult> {
const { decoded: decodedJwt, encoded: encodedJwt } = Jwt.parse({ jwt: options.jwt });

if (decodedJwt.payload.exp && Math.floor(Date.now() / 1000) > decodedJwt.payload.exp) {
throw new Error(`Verification failed: JWT is expired`);
// Ensure decodedJwt is defined and is an object
if (!decodedJwt || typeof decodedJwt !== 'object' || !decodedJwt.payload) {
throw new Error('Invalid JWT structure');
}

Check warning on line 136 in packages/credentials/src/jwt.ts

View check run for this annotation

Codecov / codecov/patch

packages/credentials/src/jwt.ts#L135-L136

Added lines #L135 - L136 were not covered by tests

// Extract relevant properties for easier access
const { exp, iss, nbf, jti, sub, vc, vp } = decodedJwt.payload;

const vcTyped = vc as VcDataModel;
const vpTyped = vp as VpDataModel;

if (exp && Math.floor(Date.now() / 1000) > exp) {
throw new Error('Verification failed: JWT is expired');
}

if (!iss) throw new Error('Verification failed: iss claim is required');

if ((vcTyped && iss !== vcTyped.issuer) || (vpTyped && iss !== vpTyped.holder)) {
throw new Error('Verification failed: iss claim does not match expected issuer/holder');
}

if (nbf) {
if (vcTyped && nbf !== Math.floor(new Date(vcTyped.issuanceDate).getTime() / 1000)) {
throw new Error('Verification failed: nbf claim does not match the expected issuanceDate');
}

Check warning on line 157 in packages/credentials/src/jwt.ts

View check run for this annotation

Codecov / codecov/patch

packages/credentials/src/jwt.ts#L156-L157

Added lines #L156 - L157 were not covered by tests
// Note: VP does not have issuanceDate
}

if (jti && ((vcTyped && jti !== vcTyped.id) || (vpTyped && jti !== vpTyped.id))) {
throw new Error('Verification failed: jti claim does not match the expected id');
}

Check warning on line 163 in packages/credentials/src/jwt.ts

View check run for this annotation

Codecov / codecov/patch

packages/credentials/src/jwt.ts#L162-L163

Added lines #L162 - L163 were not covered by tests

if (!sub) throw new Error('Verification failed: sub claim is required');
const credentialSubject = vcTyped?.credentialSubject as any;
if (vcTyped && sub !== credentialSubject.id) {
throw new Error('Verification failed: sub claim does not match the expected subject id');
// Note: VP does not have subject id validation

Check warning on line 169 in packages/credentials/src/jwt.ts

View check run for this annotation

Codecov / codecov/patch

packages/credentials/src/jwt.ts#L168-L169

Added lines #L168 - L169 were not covered by tests
}

// TODO: should really be looking for verificationMethod with authentication verification relationship
Expand Down
8 changes: 7 additions & 1 deletion packages/credentials/src/verifiable-credential.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,8 +91,14 @@ export class VerifiableCredential {
signerDid : options.did,
payload : {
vc : this.vcDataModel,
iss : this.issuer,
nbf : Math.floor(new Date(this.vcDataModel.issuanceDate).getTime() / 1000),
jti : this.vcDataModel.id,
iss : options.did.uri,
sub : this.subject,
iat : Math.floor(Date.now() / 1000),
...(this.vcDataModel.expirationDate && {
exp: Math.floor(new Date(this.vcDataModel.expirationDate).getTime() / 1000),

Check warning on line 100 in packages/credentials/src/verifiable-credential.ts

View check run for this annotation

Codecov / codecov/patch

packages/credentials/src/verifiable-credential.ts#L100

Added line #L100 was not covered by tests
}),
}
});

Expand Down
2 changes: 2 additions & 0 deletions packages/credentials/src/verifiable-presentation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,8 @@ export class VerifiablePresentation {
vp : this.vpDataModel,
iss : options.did.uri,
sub : options.did.uri,
jti : this.vpDataModel.id,
iat : Math.floor(Date.now() / 1000)
}
});

Expand Down
6 changes: 3 additions & 3 deletions packages/credentials/tests/jwt.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ describe('Jwt', () => {
const header: JwtHeaderParams = { typ: 'JWT', alg: 'ES256K', kid: did.uri };
const base64UrlEncodedHeader = Convert.object(header).toBase64Url();

const payload: JwtPayload = { iat: Math.floor(Date.now() / 1000) };
const payload: JwtPayload = { iat: Math.floor(Date.now() / 1000), iss: did.uri, sub: did.uri };
const base64UrlEncodedPayload = Convert.object(payload).toBase64Url();

try {
Expand All @@ -105,7 +105,7 @@ describe('Jwt', () => {
const header: JwtHeaderParams = { typ: 'JWT', alg: 'ES256', kid: did.document.verificationMethod![0].id };
const base64UrlEncodedHeader = Convert.object(header).toBase64Url();

const payload: JwtPayload = { iat: Math.floor(Date.now() / 1000) };
const payload: JwtPayload = { iat: Math.floor(Date.now() / 1000), iss: did.uri, sub: did.uri };
const base64UrlEncodedPayload = Convert.object(payload).toBase64Url();

try {
Expand Down Expand Up @@ -155,7 +155,7 @@ describe('Jwt', () => {
const header: JwtHeaderParams = { typ: 'JWT', alg: 'EdDSA', kid: did.document.verificationMethod![0].id };
const base64UrlEncodedHeader = Convert.object(header).toBase64Url();

const payload: JwtPayload = { iat: Math.floor(Date.now() / 1000) };
const payload: JwtPayload = { iat: Math.floor(Date.now() / 1000), iss: did.uri, sub: did.uri };
const base64UrlEncodedPayload = Convert.object(payload).toBase64Url();

const toSign = `${base64UrlEncodedHeader}.${base64UrlEncodedPayload}`;
Expand Down
21 changes: 20 additions & 1 deletion packages/credentials/tests/verifiable-credential.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,25 @@ describe('Verifiable Credential Tests', async() => {
}
});

it('should throw and error if wrong issuer', async () => {
const issuerDid = await DidKey.create();
const vc = await VerifiableCredential.create({
type : 'StreetCred',
issuer : 'did:fakeissuer:123',
subject : 'did:subject:123',
data : new StreetCredibility('high', true),
});

const vcJwt = await vc.sign({ did: issuerDid });

try {
await VerifiableCredential.verify({ vcJwt });
expect.fail();
} catch(e: any) {
expect(e.message).to.include('Verification failed: iss claim does not match expected issuer/holder');
}
});

it('should throw an error if data is not parseable into a JSON object', async () => {
const issuerDid = 'did:example:issuer';
const subjectDid = 'did:example:subject';
Expand Down Expand Up @@ -324,7 +343,7 @@ describe('Verifiable Credential Tests', async() => {
const did = await DidKey.create();

const jwt = await Jwt.sign({
payload : { jti: 'hi' },
payload : { jti: 'hi', iss: did.uri, sub: did.uri },
signerDid : did
});

Expand Down

0 comments on commit e11ea2d

Please sign in to comment.