Skip to content

Commit

Permalink
Merge pull request #49 from wwWallet/selective-disclosure-jwt
Browse files Browse the repository at this point in the history
Selective disclosure jwt
  • Loading branch information
kkmanos committed Mar 19, 2024
2 parents c5d6562 + e3eeebd commit b680410
Show file tree
Hide file tree
Showing 6 changed files with 112 additions and 25 deletions.
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,10 @@
"author": "",
"description": "",
"dependencies": {
"@sd-jwt/core": "^0.2.1",
"@simplewebauthn/server": "^7.4.0",
"@transmute/did-key-ed25519": "^0.3.0-unstable.10",
"@wwwallet/ssi-sdk": "^1.0.7",
"@wwwallet/ssi-sdk": "^1.0.8",
"ajv": "^8.12.0",
"apn": "^2.2.0",
"axios": "^0.27.2",
Expand Down
2 changes: 1 addition & 1 deletion src/entities/VerifiableCredential.entity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ export class VerifiableCredentialEntity {
issuerFriendlyName: string = "";

@Column({ nullable: false })
format: string; // = CredentialTypes.JWT_VC; // 'ldp_vc' or 'jwt_vc'
format: string; // = CredentialTypes.JWT_VC; // 'ldp_vc' or 'jwt_vc' or "vc+sd-jwt"


@Column({ nullable: false })
Expand Down
119 changes: 102 additions & 17 deletions src/services/OpenidForPresentationService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,19 @@ import { getAllVerifiableCredentials } from "../entities/VerifiableCredential.en
import { createVerifiablePresentation } from "../entities/VerifiablePresentation.entity";
import { getUserByDID } from "../entities/user.entity";
import { VerifierRegistryService } from "./VerifierRegistryService";
import { randomUUID } from "node:crypto";
import { randomUUID, createHash } from "node:crypto";
import config from "../../config";
import { WalletKeystoreRequest, SignatureAction } from "./shared.types";

import {
HasherAlgorithm,
HasherAndAlgorithm,
SaltGenerator,
SdJwt,
} from '@sd-jwt/core';

type PresentationDefinition = {
id: string,
format: any;
input_descriptors: InputDescriptor[]
}

Expand All @@ -38,16 +44,18 @@ const authorizationRequestSchema = z.object({

type InputDescriptor = {
id: string,
constraints: Constraint[],
constraints: {
fields: Field[];
},
name?: string,
purpose?: string,
format?: any
}

type Constraint = {
fields: Field[],
limit_disclosure?: "required" | "preferred"
}
// type Constraint = {
// fields: Field[],
// limit_disclosure?: "required" | "preferred"
// }

type Field = {
path: string[],
Expand Down Expand Up @@ -76,7 +84,6 @@ export class OpenidForPresentationService implements OutboundCommunication {
states = new Map<string, VerificationState>();



constructor(
@inject(TYPES.WalletKeystoreManagerService) private walletKeystoreManagerService: WalletKeystore,
@inject(TYPES.VerifierRegistryService) private verifierRegistryService: VerifierRegistryService,
Expand Down Expand Up @@ -145,7 +152,6 @@ export class OpenidForPresentationService implements OutboundCommunication {




private async parseIdTokenRequest(userDid: string, authorizationRequestURL: string): Promise<Result<{ redirect_to: string }, WalletKeystoreRequest>> {
console.log("parseIdTokenRequest userDid:", userDid)

Expand Down Expand Up @@ -248,7 +254,7 @@ export class OpenidForPresentationService implements OutboundCommunication {
* @param authorizationRequestURL
* @returns
*/
private async parseAuthorizationRequest(userDid: string, authorizationRequestURL: string): Promise<{conformantCredentialsMap: Map<string, string[]>, verifierDomainName: string}> {
private async parseAuthorizationRequest(userDid: string, authorizationRequestURL: string): Promise<{conformantCredentialsMap: Map<string, { credentials: string[], requestedFields: string[] }>, verifierDomainName: string}> {
console.log("parseAuthorizationRequest userDid = ", userDid)
const { did } = (await getUserByDID(userDid)).unwrap();
let client_id: string,
Expand Down Expand Up @@ -317,12 +323,17 @@ export class OpenidForPresentationService implements OutboundCommunication {
console.log("VC list size = ", vcList.length)


const mapping = new Map<string, string[]>();
const mapping = new Map<string, { credentials: string[], requestedFields: string[] }>();
for (const descriptor of descriptors) {
console.log("Descriptor :")
console.dir(descriptor, { depth: null })
const conformingVcList = []
for (const vc of vcList) {
// if this vc format is not supported by the verifier, then skip this vc
if (!Object.keys(presentation_definition.format).includes(vc.format)) {
continue;
}

if (Verify.verifyVcJwtWithDescriptor(descriptor, vc.credential)) {
conformingVcList.push(vc.credentialIdentifier);
}
Expand All @@ -332,7 +343,11 @@ export class OpenidForPresentationService implements OutboundCommunication {
console.log("No conformant credentials were found");
continue;
}
mapping.set(descriptor.id, [ ...conformingVcList ]);
const requestedFieldNames = descriptor.constraints.fields
.map((field) => field.path)
.reduce((accumulator, currentValue) => [...accumulator, ...currentValue])
.map((field) => field.split('.')[field.split('.').length - 1]);
mapping.set(descriptor.id, { credentials: [ ...conformingVcList ], requestedFields: requestedFieldNames });
}
console.log("Mapping1 = ", mapping)
console.log("Redirect uri = ", response_uri)
Expand All @@ -351,18 +366,87 @@ export class OpenidForPresentationService implements OutboundCommunication {
}


private async generateVerifiablePresentation(selectedVC: string[], userDid: string): Promise<Result<string, WalletKeystoreRequest>> {
/**
* selection: (key: descriptor_id, value: credentialIdentifier from VerifiableCredential DB entity)
*/
private async generateVerifiablePresentation(selection: Map<string, string>, presentation_definition: PresentationDefinition, userDid: string): Promise<Result<string, WalletKeystoreRequest>> {

const hasherAndAlgorithm: HasherAndAlgorithm = {
hasher: (input: string) => createHash('sha256').update(input).digest(),
algorithm: HasherAlgorithm.Sha256
}

/**
*
* @param paths example: [ '$.credentialSubject.image', '$.credentialSubject.grade', '$.credentialSubject.val.x' ]
* @returns example: { credentialSubject: { image: true, grade: true, val: { x: true } } }
*/
const generatePresentationFrameForPaths = (paths) => {
const result = {};

paths.forEach((path) => {
const keys = path.split(".").slice(1); // Splitting and removing the initial '$'
let nestedObj = result;

keys.forEach((key, index) => {
if (index === keys.length - 1) {
nestedObj[key] = true; // Setting the innermost key to true
}
else {
nestedObj[key] = nestedObj[key] || {}; // Creating nested object if not exists
nestedObj = nestedObj[key]; // Moving to the next nested object
}
});
});
return result;
};
let vcListRes = await getAllVerifiableCredentials(userDid);
if (vcListRes.err) {
throw "Failed to fetch credentials";
}
const allSelectedCredentialIdentifiers = Array.from(selection.values());

const filteredVCEntities = vcListRes
.unwrap()
.filter((vc) =>
allSelectedCredentialIdentifiers.includes(vc.credentialIdentifier),
);

let selectedVCs = [];
for (const [descriptor_id, credentialIdentifier] of selection) {
const vcEntity = filteredVCEntities.filter((vc) => vc.credentialIdentifier == credentialIdentifier)[0];
if (vcEntity.format == "vc+sd-jwt") {
const descriptor = presentation_definition.input_descriptors.filter((desc) => desc.id == descriptor_id)[0];
const allPaths = descriptor.constraints.fields
.map((field) => field.path)
.reduce((accumulator, currentValue) => [...accumulator, ...currentValue]);
let presentationFrame = generatePresentationFrameForPaths(allPaths);
presentationFrame = { vc: presentationFrame }
const sdJwt = SdJwt.fromCompact<Record<string, unknown>, any>(
vcEntity.credential
).withHasher(hasherAndAlgorithm)
console.log(sdJwt);
const presentation = await sdJwt.present(presentationFrame);
selectedVCs.push(presentation);
}
else {
selectedVCs.push(vcEntity.credential);
}

}

const fetchedState = this.states.get(userDid);
console.log(fetchedState);
const { audience, nonce } = fetchedState;
const result = await this.walletKeystoreManagerService.signJwtPresentation(userDid, nonce, audience, selectedVC);


const result = await this.walletKeystoreManagerService.signJwtPresentation(userDid, nonce, audience, selectedVCs);
if (!result.ok) {
return Err({
action: SignatureAction.signJwtPresentation,
nonce,
audience,
verifiableCredentials: selectedVC,
verifiableCredentials: selectedVCs,
});
}

Expand All @@ -384,9 +468,10 @@ export class OpenidForPresentationService implements OutboundCommunication {
allSelectedCredentialIdentifiers.includes(vc.credentialIdentifier)
);
const filteredVCJwtList = filteredVCEntities.map((vc) => vc.credential);

try {
const vp_token_result = await this.generateVerifiablePresentation(filteredVCJwtList, userDid);
const fetchedState = this.states.get(userDid);
const vp_token_result = await this.generateVerifiablePresentation(selection, fetchedState.presentation_definition, userDid);
if (vp_token_result.err) {
return Err(vp_token_result.val);
}
Expand Down
2 changes: 1 addition & 1 deletion src/services/types/OutboundRequest.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
export type OutboundRequest = {
redirect_to?: string;
conformantCredentialsMap?: Map<string, string[]>
conformantCredentialsMap?: Map<string, { credentials: string[], requestedFields: string[] }>
verifierDomainName?: string
}
3 changes: 2 additions & 1 deletion src/types/oid4vci/oid4vci.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,8 @@ export type ProofPayload = {
export enum VerifiableCredentialFormat {
JWT_VC_JSON = "jwt_vc_json",
JWT_VC = "jwt_vc",
LDP_VC = "ldp_vc"
LDP_VC = "ldp_vc",
VC_SD_JWT = "vc+sd-jwt"
}

export enum ProofType {
Expand Down
8 changes: 4 additions & 4 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -1135,10 +1135,10 @@
dependencies:
"@types/node" "*"

"@wwwallet/ssi-sdk@^1.0.6":
version "1.0.6"
resolved "https://npm.pkg.github.com/download/@wwWallet/ssi-sdk/1.0.6/972fb2ac3d2d2643806d13920bc971e0bb0cc1d9#972fb2ac3d2d2643806d13920bc971e0bb0cc1d9"
integrity sha512-j7JaqTGHsYQbzPkHud8/7mQ1qC1pFeZr3vFFHcWOZg6vUFcCyCAumqyvWeVJBheeFrwEPuzrIULb2RLpY22v8g==
"@wwwallet/ssi-sdk@^1.0.8":
version "1.0.8"
resolved "https://npm.pkg.github.com/download/@wwWallet/ssi-sdk/1.0.8/157b400429bb3f238be9d454fb1526d3dfec07f1#157b400429bb3f238be9d454fb1526d3dfec07f1"
integrity sha512-92cSKHakAtSe0nkd2sjB3QRLG7lbzUJJgHFgpJPtuuujIsvXCsjAdgEr814N/vK+G3y7EAlRds1FeG4GH4G06Q==
dependencies:
"@cef-ebsi/ebsi-did-resolver" "^3.1.0"
"@cef-ebsi/key-did-resolver" "^1.0.0"
Expand Down

0 comments on commit b680410

Please sign in to comment.