Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix credential formatting in presentationToClaims #16

Merged
merged 1 commit into from
Aug 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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");
});
Loading