Skip to content

Commit

Permalink
support for PKCE
Browse files Browse the repository at this point in the history
  • Loading branch information
endimion committed Apr 15, 2024
1 parent 158c966 commit 1ec21da
Show file tree
Hide file tree
Showing 2 changed files with 99 additions and 16 deletions.
94 changes: 78 additions & 16 deletions routes.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
import express from "express";
import fs from "fs";
import { v4 as uuidv4 } from "uuid";
import { pemToJWK, generateNonce } from "./utils/cryptoUtils.js";
import {
pemToJWK,
generateNonce,
base64UrlEncodeSha256,
} from "./utils/cryptoUtils.js";
import {
buildAccessToken,
generateRefreshToken,
Expand Down Expand Up @@ -40,6 +44,8 @@ const jwks = pemToJWK(publicKeyPem, "public");
//TODO move this into a service that caches these (e.g via redis or something)
let sessions = [];
let issuanceResults = [];
let codeFlowSessions = [];
let codeFlowSessionsResults = [];

router.get("/.well-known/openid-credential-issuer", async (req, res) => {
// console.log("1 ROUTE /.well-known/openid-credential-issuer CALLED!!!!!!");
Expand Down Expand Up @@ -121,52 +127,108 @@ router.get("/authorize", async (req, res) => {
);
const redirectUri = decodeURIComponent(req.query.redirect_uri);
const nonce = req.query.nonce;
const codeChallenge = decodeURIComponent(req.query.code_challenge);
const codeChallengeMethod = req.query.code_challenge_method;
const codeChallenge = decodeURIComponent(req.query.code_challenge); //secret TODO cache this with challenge method under client state.
const codeChallengeMethod = req.query.code_challenge_method; //challenge method

const clientMetadata = JSON.parse(
decodeURIComponent(req.query.client_metadata)
);
//validations
let errors = [];
if (authorizationDetails.credential_definition) {
console.log(
`credential ${authorizationDetails.credential_definition.type} was requested`
);
} else {
if (authorizationDetails.types) {
//EBSI style
console.log(`credential ${authorizationDetails.types} was requested`);
} else {
errors.push("no credentials requested");
}
}

if (responseType !== "code") {
errors.push("Invalid response_type");
}
if (!scope.includes("openid")) {
errors.push("Invalid scope");
}

// If validations pass, redirect with a 302 Found response
const authorizationCode = "SplxlOBeZQQYbYS6WxSbIA"; //TODO make this dynamic
codeFlowSessions.push({
challenge: codeChallenge,
method: codeChallengeMethod,
sessionId: authorizationCode,
});
codeFlowSessionsResults.push({
sessionId: authorizationCode,
status: "pending",
});

const redirectUrl = `${redirectUri}?code=${authorizationCode}`;
// If there are errors, log errors
if (errors.length > 0) {
console.error("Validation errors:", errors);
let error_description = "";
error.array.forEach((element) => {
error_description += element + " ";
});
const errorRedirectUrl = `${redirectUri}?error=invalid_request
&error_description=${error_description}`;
return res.redirect(302, errorRedirectUrl);
} else {
// This sets the HTTP status to 302 and the Location header to the redirectUrl
return res.redirect(302, redirectUrl);
}

// If validations pass, redirect with a 302 Found response
const authorizationCode = "SplxlOBeZQQYbYS6WxSbIA"; //TODO make this dynamic
const redirectUrl = `https://Wallet.example.org/cb?code=${authorizationCode}`;

// This sets the HTTP status to 302 and the Location header to the redirectUrl
return res.redirect(302, redirectUrl);
});

router.post("/token_endpoint", async (req, res) => {
// console.log("6 ROUTE /token_endpoint CALLED!!!!!!");
//pre-auth code flow
const grantType = req.body.grant_type;
const preAuthorizedCode = req.body["pre-authorized_code"]; // req.body["pre-authorized_code"]
const userPin = req.body["user_pin"];
//code flow
const code = req.body["code"]; //TODO check the code ...
const code_verifier = req.body["code_verifier"];
const redirect_uri = req.body["redirect_uri"];

console.log("token_endpoint parameters received");
console.log(grantType);
console.log(preAuthorizedCode);
console.log(userPin);
console.log("---------");

let index = sessions.indexOf(preAuthorizedCode);
if (index >= 0) {
console.log(`credential for session ${preAuthorizedCode} has been issued`);
issuanceResults[index].status = "success";
console.log(issuanceResults[index].status);
if (grantType == "urn:ietf:params:oauth:grant-type:pre-authorized_code") {
console.log("pre-auth code flow");
let index = sessions.indexOf(preAuthorizedCode);
if (index >= 0) {
console.log(
`credential for session ${preAuthorizedCode} has been issued`
);
issuanceResults[index].status = "success";
console.log("pre-auth code flow" + issuanceResults[index].status);
}
} else {
if (grantType == "authorization_code") {
//check PKCE
for (let i = 0; i < codeFlowSessions.array.length; i++) {
let element = codeFlowSessions.array[i];
if (code === element.sessionId) {
let challenge = element.challenge;
// let method = element.method;
if (base64UrlEncodeSha256(code_verifier) === challenge) {
index = i;
codeFlowSessionsResults[i].status = "success";
console.log("code flow" + issuanceResults[index].status);
}
}
}
}
}

//TODO return error if code flow validation fails and is not a pre-auth flow
res.json({
access_token: buildAccessToken(serverURL, privateKey),
refresh_token: generateRefreshToken(),
Expand Down
21 changes: 21 additions & 0 deletions utils/cryptoUtils.js
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,27 @@ export async function decryptJWE(jweToken, privateKeyPEM) {
}
}


export async function base64UrlEncodeSha256(codeVerifier) {
// Convert the code verifier string to an ArrayBuffer with ASCII encoding
const encoder = new TextEncoder();
const data = encoder.encode(codeVerifier);

// Calculate the SHA-256 hash of the ArrayBuffer
const hashBuffer = await crypto.subtle.digest('SHA-256', data);

// Convert the ArrayBuffer to a Uint8Array
const hashArray = new Uint8Array(hashBuffer);

// Convert the bytes to a Base64 string
const base64String = btoa(String.fromCharCode.apply(null, hashArray));

// Convert Base64 to Base64URL by replacing '+' with '-', '/' with '_', and stripping '='
const base64UrlString = base64String.replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');

return base64UrlString;
}

function parseVP(vp_token) {
let vpPartsArray = vp_token.split(".");
let disclosuresPart = vpPartsArray[2]; //this is the actual sd-jdt from the vpToken
Expand Down

0 comments on commit 1ec21da

Please sign in to comment.