diff --git a/data/presentation_definition_jwt.json b/data/presentation_definition_jwt.json new file mode 100644 index 0000000..e218dd3 --- /dev/null +++ b/data/presentation_definition_jwt.json @@ -0,0 +1,30 @@ +{ + "id": "d49ee616-0e8d-4698-aff5-2a8a2362652d", + "name": "id-card-proof", + "format": { + "jwt_vc": { + "alg": ["ES256", "ES384"] + } + }, + "input_descriptors": [ + { + "id": "abd4acb1-1dcb-41ad-8596-ceb1401a69c7", + "format": { + "jwt_vc": { + "alg": ["ES256", "ES384"] + } + }, + "constraints": { + "fields": [ + { + "path": ["$.credentialSubject.given_name"] + }, + { + "path": ["$.credentialSubject.last_name"] + } + ] + }, + "limit_disclosure": "required" + } + ] +} diff --git a/routes/verifierRoutes.js b/routes/verifierRoutes.js index 32a8e1b..7ea3cc9 100644 --- a/routes/verifierRoutes.js +++ b/routes/verifierRoutes.js @@ -12,6 +12,7 @@ import { digest } from "@sd-jwt/crypto-nodejs"; import qr from "qr-image"; import imageDataURI from "image-data-uri"; import { streamToBuffer } from "@jorgeferrero/stream-to-buffer"; +import jwt from "jsonwebtoken"; const verifierRouter = express.Router(); @@ -23,6 +24,11 @@ const publicKeyPem = fs.readFileSync("./public-key.pem", "utf-8"); const presentation_definition = JSON.parse( fs.readFileSync("./data/presentation_definition.json", "utf-8") ); + +const presentation_definition_jwt = JSON.parse( + fs.readFileSync("./data/presentation_definition_jwt.json", "utf-8") +); + const jwks = pemToJWK(publicKeyPem, "public"); let verificationSessions = []; //TODO these should be redis or something a proper cache... @@ -124,15 +130,115 @@ verifierRouter.post("/direct_post/:id", async (req, res) => { } }); +// ******************************************************* +verifierRouter.get("/generateVPRequest-jwt", async (req, res) => { + const stateParam = req.query.id ? req.query.id : uuidv4(); + const nonce = generateNonce(16); + let request_uri = serverURL + "/vpRequestJwt/" + stateParam; + const response_uri = serverURL + "/direct_post_jwt"; //not used + const vpRequest = buildVP( + serverURL, + response_uri, + request_uri, + stateParam, + nonce, + encodeURIComponent(JSON.stringify(presentation_definition_jwt)) + ); + let code = qr.image(vpRequest, { + type: "png", + ec_level: "H", + size: 10, + margin: 10, + }); + let mediaType = "PNG"; + let encodedQR = imageDataURI.encode(await streamToBuffer(code), mediaType); + res.json({ + qr: encodedQR, + deepLink: vpRequest, + sessionId: stateParam, + }); + // res.json({ vpRequest: vpRequest }); +}); +verifierRouter.get("/vpRequestJwt/:id", async (req, res) => { + const uuid = req.params.id ? req.params.id : uuidv4(); + //url.searchParams.get("presentation_definition"); + const stateParam = uuidv4(); + const nonce = generateNonce(16); + const response_uri = serverURL + "/direct_post_jwt" + "/" + uuid; + let clientId = serverURL + "/direct_post_jwt" + "/" + uuid; + sessions.push(uuid); + verificationSessions.push({ + uuid: uuid, + status: "pending", + claims: null, + }); + let jwtToken = buildVpRequestJwt( + stateParam, + nonce, + clientId, + response_uri, + presentation_definition_jwt, + jwks, + serverURL, + privateKey + ); + res.type("text/plain").send(jwtToken); +}); +verifierRouter.post("/direct_post_jwt/:id", async (req, res) => { + const sessionId = req.params.id; + const jwtVp = req.body.vp_token; + // Log received request + console.log("Received direct_post VP for session:", sessionId); + if (!jwtVp) { + console.error("No VP token provided."); + return res.sendStatus(400); // Bad Request + } + let decodedWithHeader; + try { + decodedWithHeader = jwt.decode(jwtVp, { complete: true }); + } catch (error) { + console.error("Failed to decode JWT:", error); + return res.sendStatus(400); // Bad Request due to invalid JWT + } + const credentialsJwtArray = + decodedWithHeader?.payload?.vp?.verifiableCredential; + if (!credentialsJwtArray) { + console.error("Invalid JWT structure."); + return res.sendStatus(400); // Bad Request + } + // Convert credentials to claims + let claims; + try { + claims = await flattenCredentialsToClaims(credentialsJwtArray); + if (!claims) { + throw new Error("Claims conversion returned null or undefined."); + } + } catch (error) { + console.error("Error processing claims:", error); + return res.sendStatus(500); // Internal Server Error + } + // Update session status + const index = sessions.indexOf(sessionId); + console.log("Session index:", index); + if (index === -1) { + console.error("Session ID not found."); + return res.sendStatus(404); // Not Found + } + // Log successful verification + verificationSessions[index].status = "success"; + verificationSessions[index].claims = claims; + console.log("Verification success:", verificationSessions[index]); + res.sendStatus(200); // OK +}); verifierRouter.get(["/verificationStatus"], (req, res) => { let sessionId = req.query.sessionId; @@ -199,4 +305,19 @@ function buildVP( return result; } +async function flattenCredentialsToClaims(credentials) { + let claimsResult = {}; + credentials.forEach((credentialJwt) => { + let decodedCredential = jwt.decode(credentialJwt, { + complete: true, + }); + if (decodedCredential) { + let claims = decodedCredential.payload.vc.credentialSubject; + console.log(claims); + claimsResult = { ...claimsResult, ...claims }; + } + }); + return claimsResult; +} + export default verifierRouter;