From 3415550fbcd99f97babff442a4928cc827c5c9cc Mon Sep 17 00:00:00 2001 From: "Lukas.J.Han" Date: Wed, 28 Feb 2024 18:49:13 +0900 Subject: [PATCH] feat: calcuate sd_hash in kb JWT (#117) Signed-off-by: Lukas Signed-off-by: Lukas.J.Han Signed-off-by: Mirko Mollik --- examples/sd-jwt-vc-example/kb.ts | 1 - packages/core/src/index.ts | 64 +++++++++++++++++++++++++--- packages/core/src/sdjwt.ts | 3 ++ packages/core/src/test/index.spec.ts | 8 +--- packages/types/src/type.ts | 2 +- 5 files changed, 63 insertions(+), 15 deletions(-) diff --git a/examples/sd-jwt-vc-example/kb.ts b/examples/sd-jwt-vc-example/kb.ts index d5a50c9..f7fb671 100644 --- a/examples/sd-jwt-vc-example/kb.ts +++ b/examples/sd-jwt-vc-example/kb.ts @@ -31,7 +31,6 @@ import { createSignerVerifier, digest, generateSalt } from './utils'; aud: 'https://example.com', nonce: '1234', custom: 'data', - sd_hash: '1234', }; const encodedSdjwt = await sdjwt.issue( diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index f9a4463..3060948 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -1,14 +1,16 @@ -import { SDJWTException } from '@sd-jwt/utils'; +import { SDJWTException, Uint8ArrayToBase64Url } from '@sd-jwt/utils'; import { Jwt } from './jwt'; import { KBJwt } from './kbjwt'; import { SDJwt, pack } from './sdjwt'; import { DisclosureFrame, + Hasher, KBOptions, KB_JWT_TYP, SDJWTCompact, SDJWTConfig, } from '@sd-jwt/types'; +import { getSDAlgAndPayload } from '@sd-jwt/decode'; export * from './sdjwt'; export * from './kbjwt'; @@ -31,20 +33,24 @@ export abstract class SDJwtInstance { } } - private async createKBJwt(options: KBOptions): Promise { + private async createKBJwt( + options: KBOptions, + sdHash: string, + ): Promise { if (!this.userConfig.kbSigner) { throw new SDJWTException('Key Binding Signer not found'); } if (!this.userConfig.kbSignAlg) { throw new SDJWTException('Key Binding sign algorithm not specified'); } + const { payload } = options; const kbJwt = new KBJwt({ header: { typ: KB_JWT_TYP, alg: this.userConfig.kbSignAlg, }, - payload, + payload: { ...payload, sd_hash: sdHash }, }); await kbJwt.sign(this.userConfig.kbSigner); @@ -139,9 +145,24 @@ export abstract class SDJwtInstance { const hasher = this.userConfig.hasher; const sdjwt = await SDJwt.fromEncode(encodedSDJwt, hasher); - const kbJwt = options?.kb ? await this.createKBJwt(options.kb) : undefined; - sdjwt.kbJwt = kbJwt; + if (!sdjwt.jwt?.payload) throw new SDJWTException('Payload not found'); + const presentSdJwtWithoutKb = await sdjwt.present( + presentationKeys.sort(), + hasher, + ); + + if (!options?.kb) { + return presentSdJwtWithoutKb; + } + + const sdHashStr = await this.calculateSDHash( + presentSdJwtWithoutKb, + sdjwt, + hasher, + ); + + sdjwt.kbJwt = await this.createKBJwt(options.kb, sdHashStr); return sdjwt.present(presentationKeys.sort(), hasher); } @@ -159,7 +180,7 @@ export abstract class SDJwtInstance { const hasher = this.userConfig.hasher; const sdjwt = await SDJwt.fromEncode(encodedSDJwt, hasher); - if (!sdjwt.jwt) { + if (!sdjwt.jwt || !sdjwt.jwt.payload) { throw new SDJWTException('Invalid SD JWT'); } const { payload, header } = await this.validate(encodedSDJwt); @@ -185,9 +206,40 @@ export abstract class SDJwtInstance { throw new SDJWTException('Key Binding Verifier not found'); } const kb = await sdjwt.kbJwt.verify(this.userConfig.kbVerifier); + const sdHashfromKb = kb.payload.sd_hash; + const sdjwtWithoutKb = new SDJwt({ + jwt: sdjwt.jwt, + disclosures: sdjwt.disclosures, + }); + + const presentSdJwtWithoutKb = sdjwtWithoutKb.encodeSDJwt(); + const sdHashStr = await this.calculateSDHash( + presentSdJwtWithoutKb, + sdjwt, + hasher, + ); + + if (sdHashStr !== sdHashfromKb) { + throw new SDJWTException('Invalid sd_hash in Key Binding JWT'); + } + return { payload, header, kb }; } + private async calculateSDHash( + presentSdJwtWithoutKb: string, + sdjwt: SDJwt, + hasher: Hasher, + ) { + if (!sdjwt.jwt || !sdjwt.jwt.payload) { + throw new SDJWTException('Invalid SD JWT'); + } + const { _sd_alg } = getSDAlgAndPayload(sdjwt.jwt.payload); + const sdHash = await hasher(presentSdJwtWithoutKb, _sd_alg); + const sdHashStr = Uint8ArrayToBase64Url(sdHash); + return sdHashStr; + } + // This function is for validating the SD JWT // Just checking signature and return its the claims public async validate(encodedSDJwt: string) { diff --git a/packages/core/src/sdjwt.ts b/packages/core/src/sdjwt.ts index e9fdf75..bd44ae5 100644 --- a/packages/core/src/sdjwt.ts +++ b/packages/core/src/sdjwt.ts @@ -6,12 +6,15 @@ import { DisclosureFrame, Hasher, HasherAndAlg, + KBOptions, + KB_JWT_TYP, SDJWTCompact, SD_DECOY, SD_DIGEST, SD_LIST_KEY, SD_SEPARATOR, SaltGenerator, + Signer, kbHeader, kbPayload, } from '@sd-jwt/types'; diff --git a/packages/core/src/test/index.spec.ts b/packages/core/src/test/index.spec.ts index a5e1904..c672235 100644 --- a/packages/core/src/test/index.spec.ts +++ b/packages/core/src/test/index.spec.ts @@ -65,7 +65,6 @@ describe('index', () => { const presentation = await sdjwt.present(credential, ['foo'], { kb: { payload: { - sd_hash: 'sha-256', aud: '1', iat: 1, nonce: '342', @@ -176,8 +175,7 @@ describe('index', () => { const presentation = await sdjwt.present(credential, ['foo'], { kb: { payload: { - sd_hash: '', - aud: '1', + aud: '', iat: 1, nonce: '342', }, @@ -219,7 +217,6 @@ describe('index', () => { const presentation = await sdjwt.present(credential, ['foo'], { kb: { payload: { - sd_hash: 'sha-256', aud: '1', iat: 1, nonce: '342', @@ -326,7 +323,6 @@ describe('index', () => { const presentation = await sdjwt.present(credential, ['foo'], { kb: { payload: { - sd_hash: 'sha-256', aud: '1', iat: 1, nonce: '342', @@ -367,7 +363,6 @@ describe('index', () => { const presentation = await sdjwt.present(credential, ['foo'], { kb: { payload: { - sd_hash: 'sha-256', aud: '1', iat: 1, nonce: '342', @@ -406,7 +401,6 @@ describe('index', () => { const presentation = await sdjwt.present(credential, ['foo'], { kb: { payload: { - sd_hash: 'sha-256', aud: '1', iat: 1, nonce: '342', diff --git a/packages/types/src/type.ts b/packages/types/src/type.ts index 04424c5..1f38f02 100644 --- a/packages/types/src/type.ts +++ b/packages/types/src/type.ts @@ -31,7 +31,7 @@ export type kbPayload = { }; export type KBOptions = { - payload: kbPayload; + payload: Omit; }; export type OrPromise = T | Promise;