From fbf6aae72e0888a1d525e4a9f72b28e7e641489d Mon Sep 17 00:00:00 2001 From: Alex Godbehere Date: Tue, 21 Jul 2026 13:29:03 +0100 Subject: [PATCH] visualiser: PKCE challenge without crypto.subtle crypto.subtle only exists in secure contexts, and several ACS deployments serve the visualiser over plain http (kiosk displays on closed networks especially), where login died before redirecting. Fall back to a pure-JS FIPS 180-4 SHA-256 for the PKCE challenge; verified byte-for-byte against node:crypto across block boundaries. --- acs-visualiser/public/oidc.js | 76 ++++++++++++++++++++++++++++++++++- 1 file changed, 75 insertions(+), 1 deletion(-) diff --git a/acs-visualiser/public/oidc.js b/acs-visualiser/public/oidc.js index 10ec32360..ebc463d10 100644 --- a/acs-visualiser/public/oidc.js +++ b/acs-visualiser/public/oidc.js @@ -15,6 +15,80 @@ function b64url (buf) { .replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, ""); } +/* Pure-JS SHA-256 (FIPS 180-4). crypto.subtle only exists in secure + * contexts and several ACS deployments (kiosks especially) are served + * over plain http, so we need a fallback for the PKCE challenge. */ +function sha256_js (bytes) { + const K = [ + 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, + 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5, + 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, + 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, + 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, + 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da, + 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, + 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967, + 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, + 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, + 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, + 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070, + 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, + 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3, + 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, + 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2, + ]; + const H = [ + 0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, + 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19, + ]; + const rr = (x, n) => (x >>> n) | (x << (32 - n)); + + const len = bytes.length; + const padded = new Uint8Array((len + 9 + 63) & ~63); + padded.set(bytes); + padded[len] = 0x80; + new DataView(padded.buffer).setUint32(padded.length - 4, len * 8); + + const w = new Int32Array(64); + const view = new DataView(padded.buffer); + for (let off = 0; off < padded.length; off += 64) { + for (let i = 0; i < 16; i++) + w[i] = view.getUint32(off + i * 4); + for (let i = 16; i < 64; i++) { + const s0 = rr(w[i-15], 7) ^ rr(w[i-15], 18) ^ (w[i-15] >>> 3); + const s1 = rr(w[i-2], 17) ^ rr(w[i-2], 19) ^ (w[i-2] >>> 10); + w[i] = (w[i-16] + s0 + w[i-7] + s1) | 0; + } + + let [a, b, c, d, e, f, g, h] = H; + for (let i = 0; i < 64; i++) { + const S1 = rr(e, 6) ^ rr(e, 11) ^ rr(e, 25); + const ch = (e & f) ^ (~e & g); + const t1 = (h + S1 + ch + K[i] + w[i]) | 0; + const S0 = rr(a, 2) ^ rr(a, 13) ^ rr(a, 22); + const maj = (a & b) ^ (a & c) ^ (b & c); + const t2 = (S0 + maj) | 0; + h = g; g = f; f = e; e = (d + t1) | 0; + d = c; c = b; b = a; a = (t1 + t2) | 0; + } + H[0] = (H[0] + a) | 0; H[1] = (H[1] + b) | 0; + H[2] = (H[2] + c) | 0; H[3] = (H[3] + d) | 0; + H[4] = (H[4] + e) | 0; H[5] = (H[5] + f) | 0; + H[6] = (H[6] + g) | 0; H[7] = (H[7] + h) | 0; + } + + const out = new Uint8Array(32); + const ov = new DataView(out.buffer); + H.forEach((v, i) => ov.setUint32(i * 4, v >>> 0)); + return out; +} + +async function sha256 (bytes) { + if (crypto.subtle) + return new Uint8Array(await crypto.subtle.digest("SHA-256", bytes)); + return sha256_js(bytes); +} + function jwt_claims (token) { try { const b64 = token.split(".")[1] @@ -102,7 +176,7 @@ export class OidcClient { async start_code_flow () { const verifier = b64url(crypto.getRandomValues(new Uint8Array(32))); - const challenge = b64url(await crypto.subtle.digest("SHA-256", + const challenge = b64url(await sha256( new TextEncoder().encode(verifier))); const state = b64url(crypto.getRandomValues(new Uint8Array(16)));