Skip to content

Commit

Permalink
generate deviceResponse
Browse files Browse the repository at this point in the history
  • Loading branch information
kkmanos committed Nov 4, 2024
1 parent 4830fd4 commit 8f9b5da
Show file tree
Hide file tree
Showing 2 changed files with 85 additions and 5 deletions.
87 changes: 83 additions & 4 deletions src/lib/services/OpenID4VPRelyingParty.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { Verify } from "../utils/Verify";
import { HasherAlgorithm, HasherAndAlgorithm, SdJwt } from "@sd-jwt/core";
import { VerifiableCredentialFormat } from "../schemas/vc";
import { generateRandomIdentifier } from "../utils/generateRandomIdentifier";
import { base64url, EncryptJWT, importJWK, importX509, jwtVerify } from "jose";
import { base64url, CompactEncrypt, importJWK, importX509, jwtVerify } from "jose";
import { OpenID4VPRelyingPartyState } from "../types/OpenID4VPRelyingPartyState";
import { OpenID4VPRelyingPartyStateRepository } from "./OpenID4VPRelyingPartyStateRepository";
import { IHttpProxy } from "../interfaces/IHttpProxy";
Expand All @@ -13,6 +13,9 @@ import { extractSAN, getPublicKeyFromB64Cert } from "../utils/pki";
import axios from "axios";
import { BACKEND_URL, OPENID4VP_SAN_DNS_CHECK_SSL_CERTS, OPENID4VP_SAN_DNS_CHECK } from "../../config";
import { MDoc } from "@auth0/mdl";
import { parse } from '@auth0/mdl';
import { JSONPath } from "jsonpath-plus";
import { cborDecode, cborEncode } from "../utils/cbor";

export class OpenID4VPRelyingParty implements IOpenID4VPRelyingParty {

Expand Down Expand Up @@ -137,7 +140,7 @@ export class OpenID4VPRelyingParty implements IOpenID4VPRelyingParty {
for (const vc of vcList) {
try {

if (vc.format === VerifiableCredentialFormat.SD_JWT_VC && (descriptor.format === undefined || VerifiableCredentialFormat.SD_JWT_VC in descriptor.format)) {
if (vc.format === VerifiableCredentialFormat.SD_JWT_VC && (VerifiableCredentialFormat.SD_JWT_VC in descriptor.format)) {
const result = await this.credentialParserRegistry.parse(vc.credential);
if ('error' in result) {
throw new Error('Could not parse credential');
Expand All @@ -147,6 +150,40 @@ export class OpenID4VPRelyingParty implements IOpenID4VPRelyingParty {
continue;
}
}

if (vc.format == VerifiableCredentialFormat.MSO_MDOC && (VerifiableCredentialFormat.MSO_MDOC in descriptor.format)) {
const credentialBytes = base64url.decode(vc.credential);
const issuerSigned = cborDecode(credentialBytes);
// According to ISO 23220-4: The value of input descriptor id should be the doctype
const m = {
version: '1.0',
documents: [new Map([
['docType', descriptor.id],
['issuerSigned', issuerSigned]
])],
status: 0
};
const encoded = cborEncode(m);
const mdoc = parse(encoded);
const [document] = mdoc.documents;
const ns = document.getIssuerNameSpace(document.issuerSignedNameSpaces[0]);
const json = {};
json[descriptor.id] = ns;

const fieldsWithValue = descriptor.constraints.fields.map((field) => {
const values = field.path.map((possiblePath) => JSONPath({ path: possiblePath, json: json })[0]);
const val = values.filter((v) => v != undefined || v != null)[0]; // get first value that is not undefined
return { field, val };
});
console.log("Fields with value = ", fieldsWithValue)

if (fieldsWithValue.map((fwv) => fwv.val).includes(undefined)) {
continue; // there is at least one field missing from the requirements
}

conformingVcList.push(vc.credentialIdentifier);
continue;
}
}
catch (err) {
console.error("Failed to match a descriptor")
Expand Down Expand Up @@ -261,6 +298,9 @@ export class OpenID4VPRelyingParty implements IOpenID4VPRelyingParty {
const client_id = S.client_id;
const nonce = S.nonce;

let apu = undefined;
let apv = undefined;

let { verifiableCredentials } = await this.getAllStoredVerifiableCredentials();
const allSelectedCredentialIdentifiers = Array.from(selectionMap.values());
const filteredVCEntities = verifiableCredentials
Expand Down Expand Up @@ -294,6 +334,44 @@ export class OpenID4VPRelyingParty implements IOpenID4VPRelyingParty {
});
originalVCs.push(vcEntity);
}
else if (vcEntity.format === VerifiableCredentialFormat.MSO_MDOC) {
console.log("Response uri = ", response_uri);
const descriptor = presentationDefinition.input_descriptors.filter((desc) => desc.id === descriptor_id)[0];
const credentialBytes = base64url.decode(vcEntity.credential);
const issuerSigned = cborDecode(credentialBytes);

// According to ISO 23220-4: The value of input descriptor id should be the doctype
const m = {
version: '1.0',
documents: [new Map([
['docType', descriptor.id],
['issuerSigned', issuerSigned]
])],
status: 0
};
const encoded = cborEncode(m);
const mdoc = parse(encoded);

const mdocGeneratedNonce = generateRandomIdentifier(8); // mdoc generated nonce
apu = mdocGeneratedNonce; // no need to base64url encode. jose library handles it
apv = nonce; // no need to base64url encode. jose library handles it

const { deviceResponseMDoc } = await this.generateDeviceResponseFn(mdoc, presentationDefinition, mdocGeneratedNonce, nonce, client_id, response_uri);
function uint8ArrayToHexString(uint8Array) {
// @ts-ignore
return Array.from(uint8Array, byte => byte.toString(16).padStart(2, '0')).join('');
}
console.log("Device response in hex format = ", uint8ArrayToHexString(deviceResponseMDoc.encode()));
const encodedDeviceResponse = base64url.encode(deviceResponseMDoc.encode());
selectedVCs.push(encodedDeviceResponse);
generatedVPs.push(encodedDeviceResponse);
descriptorMap.push({
id: descriptor_id,
format: VerifiableCredentialFormat.MSO_MDOC,
path: `$`
});
originalVCs.push(vcEntity);
}
}

const presentationSubmission = {
Expand All @@ -307,11 +385,12 @@ export class OpenID4VPRelyingParty implements IOpenID4VPRelyingParty {
if (S.client_metadata.authorization_encrypted_response_alg && S.client_metadata.jwks.keys.length > 0) {
const rp_eph_pub_jwk = S.client_metadata.jwks.keys[0];
const rp_eph_pub = await importJWK(rp_eph_pub_jwk, S.client_metadata.authorization_encrypted_response_alg);
const jwe = await new EncryptJWT({
const jwe = await new CompactEncrypt(new TextEncoder().encode(JSON.stringify({
vp_token: generatedVPs[0],
presentation_submission: presentationSubmission,
state: S.state ?? undefined
})
})))
.setKeyManagementParameters({ apu: new TextEncoder().encode(apu), apv: new TextEncoder().encode(apv) })
.setProtectedHeader({ alg: S.client_metadata.authorization_encrypted_response_alg, enc: S.client_metadata.authorization_encrypted_response_enc, kid: rp_eph_pub_jwk.kid })
.encrypt(rp_eph_pub);

Expand Down
3 changes: 2 additions & 1 deletion src/services/keystore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { SdJwt } from "@sd-jwt/core";
import { DataItem, DeviceResponse, MDoc } from "@auth0/mdl";
import * as cbor from 'cbor-x';
import { COSEKeyToJWK } from "cose-kit";
import { SupportedAlgs } from "@auth0/mdl/lib/mdoc/model/types";


const keyDidResolver = KeyDidResolver.getResolver();
Expand Down Expand Up @@ -1181,7 +1182,7 @@ export async function generateDeviceResponse([privateData, mainKey]: [PrivateDat
const deviceResponseMDoc = await DeviceResponse.from(mdocCredential)
.usingPresentationDefinition(presentationDefinition)
.usingSessionTranscriptForOID4VP(mdocGeneratedNonce, clientId, responseUri, verifierGeneratedNonce)
.authenticateWithSignature({ ...privateKeyJwk, alg } as JWK, alg as 'ES256')
.authenticateWithSignature({ ...privateKeyJwk, alg } as JWK, alg as SupportedAlgs)
.sign();
return { deviceResponseMDoc };
}

0 comments on commit 8f9b5da

Please sign in to comment.