From d63d575a2d05f727e2648c9b2ccc6e9361324c39 Mon Sep 17 00:00:00 2001 From: Isaac Byrne Date: Mon, 12 Aug 2024 14:32:39 -0500 Subject: [PATCH] fix credential formatting in presentationToClaims --- src/presentation/holder.ts | 174 ++++++++++++++++----------------- test/sanity/jwt.sanity.test.ts | 28 +++--- 2 files changed, 101 insertions(+), 101 deletions(-) diff --git a/src/presentation/holder.ts b/src/presentation/holder.ts index 5445518..f0f682f 100644 --- a/src/presentation/holder.ts +++ b/src/presentation/holder.ts @@ -1,107 +1,103 @@ +import sd from "@transmute/vc-jwt-sd"; - - - -import sd from '@transmute/vc-jwt-sd' - -import { base64url } from 'jose' +import { base64url } from "jose"; import { RequestPresentationHolder, RequestCredentialPresentation, - SdJwt -} from '../types' - + SdJwt, +} from "../types"; -import { encoder, decoder } from '../text' +import { encoder, decoder } from "../text"; const presentationToClaims = (req: RequestCredentialPresentation) => { - const claims = req.presentation - claims.verifiableCredential = [] + const claims = req.presentation; + claims.verifiableCredential = []; for (const d of req.disclosures) { - const text = d.type.endsWith('+cose') ? `base64url,${base64url.encode(d.credential)}` : decoder.decode(d.credential) + const text = d.type.endsWith("+cose") + ? `base64url,${base64url.encode(d.credential)}` + : new TextDecoder().decode(d.credential); claims.verifiableCredential.push({ "@context": "https://www.w3.org/ns/credentials/v2", - "id": `data:${d.type};${text}`, - "type": "EnvelopedVerifiableCredential" - }) + id: `data:${d.type};${text}`, + type: "EnvelopedVerifiableCredential", + }); } - return claims -} + return claims; +}; const jwtPresentationIssuer = (holder: RequestPresentationHolder) => { return { issue: async (req: RequestCredentialPresentation) => { if (req.signer === undefined) { - throw new Error('No signer available.') + throw new Error("No signer available."); } - const claims = presentationToClaims(req) - return req.signer.sign(encoder.encode(JSON.stringify(claims))) - } - } -} - + const claims = presentationToClaims(req); + return req.signer.sign(encoder.encode(JSON.stringify(claims))); + }, + }; +}; const coseSign1PresentationIssuer = (holder: RequestPresentationHolder) => { return { issue: async (req: RequestCredentialPresentation) => { if (req.signer === undefined) { - throw new Error('No signer available.') + throw new Error("No signer available."); } - const claims = presentationToClaims(req) - return req.signer.sign(encoder.encode(JSON.stringify(claims))) - } - } -} + const claims = presentationToClaims(req); + return req.signer.sign(encoder.encode(JSON.stringify(claims))); + }, + }; +}; const sdJwtPresentationIssuer = (holder: RequestPresentationHolder) => { return { issue: async (req: RequestCredentialPresentation) => { if (!req.disclosures) { - throw new Error('disclosures are required for this presentation type') + throw new Error("disclosures are required for this presentation type"); } const sdJwsSigner = { sign: async ({ claimset }: { claimset: Record }) => { if (req.signer === undefined) { - throw new Error('signer is required for this presentation type') + throw new Error("signer is required for this presentation type"); } - const bytes = encoder.encode(JSON.stringify(claimset)) - return decoder.decode(await req.signer.sign(bytes)) - } - } - const sdJwsSalter = await sd.salter() - const sdJwsDigester = await sd.digester() + const bytes = encoder.encode(JSON.stringify(claimset)); + return decoder.decode(await req.signer.sign(bytes)); + }, + }; + const sdJwsSalter = await sd.salter(); + const sdJwsDigester = await sd.digester(); const sdHolder = await sd.holder({ alg: holder.alg, salter: sdJwsSalter, digester: sdJwsDigester, - signer: sdJwsSigner - }) + signer: sdJwsSigner, + }); // address undefined behavior for presentations of multiple dislosable credentials // with distinct disclosure choices... // https://w3c.github.io/vc-data-model/#example-basic-structure-of-a-presentation-0 - const vp = req.presentation - vp.verifiableCredential = [] + const vp = req.presentation; + vp.verifiableCredential = []; for (const d of req.disclosures) { - const sdJwtFnard = await sdHolder.issue({ + const sdJwtFnard = (await sdHolder.issue({ token: decoder.decode(d.credential), // todo for each... disclosure: decoder.decode(d.disclosure), nonce: d.nonce, audience: d.audience as any, // https://github.com/transmute-industries/vc-jwt-sd/issues/7 - }) as SdJwt + })) as SdJwt; vp.verifiableCredential.push({ "@context": "https://www.w3.org/ns/credentials/v2", - "id": `data:application/vc-ld+sd-jwt;${sdJwtFnard}`, // great job everyone. - "type": "EnvelopedVerifiableCredential" - }) + id: `data:application/vc-ld+sd-jwt;${sdJwtFnard}`, // great job everyone. + type: "EnvelopedVerifiableCredential", + }); } const sdIssuer = await sd.issuer({ alg: holder.alg, salter: sdJwsSalter, digester: sdJwsDigester, - signer: sdJwsSigner - }) + signer: sdJwsSigner, + }); const sdJwt = await sdIssuer.issue({ // its possible to bind this vp to a key for proof of posession @@ -111,68 +107,70 @@ const sdJwtPresentationIssuer = (holder: RequestPresentationHolder) => { // its possible to mark credentials disclosable here... // for now, we will assume thats not a feature. - claimset: sd.YAML.dumps(vp) - }) + claimset: sd.YAML.dumps(vp), + }); - return encoder.encode(sdJwt) - } - } -} + return encoder.encode(sdJwt); + }, + }; +}; -const unsecuredPresentationOfSecuredCredentials = (holder: RequestPresentationHolder) => { +const unsecuredPresentationOfSecuredCredentials = ( + holder: RequestPresentationHolder +) => { return { issue: async (req: RequestCredentialPresentation) => { if (req.disclosures == undefined) { - throw new Error('disclosures is REQUIRED for this presentation type.') + throw new Error("disclosures is REQUIRED for this presentation type."); } - const sdJwsSalter = await sd.salter() - const sdJwsDigester = await sd.digester() + const sdJwsSalter = await sd.salter(); + const sdJwsDigester = await sd.digester(); const sdHolder = await sd.holder({ alg: holder.alg, salter: sdJwsSalter, digester: sdJwsDigester, // note that no signer is here, since no holder binding is present. - }) - const vp = req.presentation - vp.verifiableCredential = [] + }); + const vp = req.presentation; + vp.verifiableCredential = []; for (const d of req.disclosures) { - let enveloped: any = undefined + let enveloped: any = undefined; if (d.disclosure) { - const sdJwtFnard = await sdHolder.issue({ + const sdJwtFnard = (await sdHolder.issue({ token: decoder.decode(d.credential), // todo for each... disclosure: decoder.decode(d.disclosure), - // no audience or nonce are present here, + // no audience or nonce are present here, // since there can be no key binding - }) as SdJwt + })) as SdJwt; - enveloped = `data:${d.type};${sdJwtFnard}` // great job everyone. + enveloped = `data:${d.type};${sdJwtFnard}`; // great job everyone. } else { - const token = decoder.decode(d.credential) - enveloped = `data:${d.type};${token}` + const token = decoder.decode(d.credential); + enveloped = `data:${d.type};${token}`; } if (enveloped === undefined) { - throw new Error('Unable to envelop credential for presentation') + throw new Error("Unable to envelop credential for presentation"); } vp.verifiableCredential.push({ "@context": "https://www.w3.org/ns/credentials/v2", - "id": enveloped, - "type": "EnvelopedVerifiableCredential" - }) + id: enveloped, + type: "EnvelopedVerifiableCredential", + }); } - return encoder.encode(JSON.stringify(vp)) - } - } -} + return encoder.encode(JSON.stringify(vp)); + }, + }; +}; export const holder = (holder: RequestPresentationHolder) => { - if (holder.type === 'application/vp-ld+jwt') { - return jwtPresentationIssuer(holder) - } else if (holder.type === 'application/vp-ld+sd-jwt') { - return sdJwtPresentationIssuer(holder) - } else if (holder.type === 'application/vp-ld+cose') { - return coseSign1PresentationIssuer(holder) - } else if (holder.type === 'application/vp-ld') { - return unsecuredPresentationOfSecuredCredentials(holder) + if (holder.type === "application/vp-ld+jwt") { + return jwtPresentationIssuer(holder); + } else if (holder.type === "application/vp-ld+sd-jwt") { + return sdJwtPresentationIssuer(holder); + } else if (holder.type === "application/vp-ld+cose") { + return coseSign1PresentationIssuer(holder); + } else if (holder.type === "application/vp-ld") { + return unsecuredPresentationOfSecuredCredentials(holder); } - throw new Error('presentation type is not supported.') -} \ No newline at end of file + throw new Error("presentation type is not supported."); +}; diff --git a/test/sanity/jwt.sanity.test.ts b/test/sanity/jwt.sanity.test.ts index cc7a7bd..1ed1a5e 100644 --- a/test/sanity/jwt.sanity.test.ts +++ b/test/sanity/jwt.sanity.test.ts @@ -1,19 +1,21 @@ -import * as jose from 'jose' +import * as jose from "jose"; -it('JWT sign and verify', async () => { - const { publicKey, privateKey } = await jose.generateKeyPair('ES256') +it("JWT sign and verify", async () => { + const { publicKey, privateKey } = await jose.generateKeyPair("ES256"); const jws = await new jose.CompactSign( - new TextEncoder().encode(JSON.stringify({ - iss: 'urn:uuid:123' - })), + new TextEncoder().encode( + JSON.stringify({ + iss: "urn:uuid:123", + }) + ) ) - .setProtectedHeader({ alg: 'ES256' }) - .sign(privateKey) + .setProtectedHeader({ alg: "ES256" }) + .sign(privateKey); const { payload, protectedHeader } = await jose.jwtVerify(jws, publicKey, { - issuer: 'urn:uuid:123', + issuer: "urn:uuid:123", audience: undefined, - }) - expect(protectedHeader.alg).toBe('ES256') - expect(payload.iss).toBe('urn:uuid:123') -}) \ No newline at end of file + }); + expect(protectedHeader.alg).toBe("ES256"); + expect(payload.iss).toBe("urn:uuid:123"); +});