Skip to content

Commit

Permalink
Merge pull request #16 from transmute-industries/fix-presentation-to-…
Browse files Browse the repository at this point in the history
…cliams

fix credential formatting in presentationToClaims
  • Loading branch information
OR13 authored Aug 12, 2024
2 parents 9c43578 + d63d575 commit 3feff43
Show file tree
Hide file tree
Showing 2 changed files with 101 additions and 101 deletions.
174 changes: 86 additions & 88 deletions src/presentation/holder.ts
Original file line number Diff line number Diff line change
@@ -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<string, unknown> }) => {
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
Expand All @@ -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.')
}
throw new Error("presentation type is not supported.");
};
28 changes: 15 additions & 13 deletions test/sanity/jwt.sanity.test.ts
Original file line number Diff line number Diff line change
@@ -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')
})
});
expect(protectedHeader.alg).toBe("ES256");
expect(payload.iss).toBe("urn:uuid:123");
});

0 comments on commit 3feff43

Please sign in to comment.