"use strict"; const https = require("node:https"); const crypto = require("node:crypto"); const fs = require("node:fs"); const os = require("node:os"); const path = require("node:path"); const { spawnSync } = require("node:child_process"); const options = parseArgs(process.argv.slice(2)); const PORT = Number(options.port || process.env.PORT || 8443); const BIND_HOST = options.host || process.env.HOST || "0.0.0.0"; const COOKIE_MAX_AGE_SECONDS = Number(options.cookieMaxAge || process.env.COOKIE_MAX_AGE_SECONDS || 60); const ALLOW_NONE_ALGORITHM = Boolean(options.allowNone || process.env.DBSC_ALLOW_NONE === "1"); const COOKIE_NAME = "__Host-dbsc_poc"; const state = { pending: new Map(), sessions: new Map(), cookieIndex: new Map(), events: [], }; function parseArgs(argv) { const parsed = {}; for (let i = 0; i < argv.length; i += 1) { const arg = argv[i]; if (arg === "--allow-none") { parsed.allowNone = true; } else if (arg === "--port") { parsed.port = argv[++i]; } else if (arg === "--host") { parsed.host = argv[++i]; } else if (arg === "--cookie-max-age") { parsed.cookieMaxAge = argv[++i]; } } return parsed; } function addEvent(message) { const line = `${new Date().toISOString()} ${message}`; state.events.push(line); while (state.events.length > 80) state.events.shift(); console.log(line); } function findOpenSsl() { const candidates = [ process.env.OPENSSL, "openssl", "C:\\Program Files\\Git\\usr\\bin\\openssl.exe", "C:\\Program Files\\Git\\mingw64\\bin\\openssl.exe", ].filter(Boolean); for (const candidate of candidates) { const result = spawnSync(candidate, ["version"], { stdio: "ignore" }); if (result.status === 0) return candidate; } return null; } function ensureCertificate() { const certDir = path.join(__dirname, "certs"); const keyPath = path.join(certDir, "dbsc-poc-key.pem"); const certPath = path.join(certDir, "dbsc-poc-cert.pem"); if (!fs.existsSync(keyPath) || !fs.existsSync(certPath)) { fs.mkdirSync(certDir, { recursive: true }); const openssl = findOpenSsl(); if (!openssl) { throw new Error("OpenSSL was not found. Install Git for Windows or set OPENSSL to an openssl.exe path."); } const altNames = [ "DNS:localhost", `DNS:${os.hostname()}`, "IP:127.0.0.1", ...getLocalIpv4Addresses().map((address) => `IP:${address}`), ]; const result = spawnSync(openssl, [ "req", "-x509", "-newkey", "rsa:2048", "-sha256", "-days", "365", "-nodes", "-keyout", keyPath, "-out", certPath, "-subj", "/CN=localhost", "-addext", `subjectAltName=${[...new Set(altNames)].join(",")}`, ], { stdio: "ignore" }); if (result.status !== 0) { throw new Error("OpenSSL failed to generate the localhost certificate."); } } return { key: fs.readFileSync(keyPath), cert: fs.readFileSync(certPath), }; } function getLocalIpv4Addresses() { const addresses = []; for (const interfaces of Object.values(os.networkInterfaces())) { for (const entry of interfaces || []) { if (entry.family === "IPv4" && !entry.internal) { addresses.push(entry.address); } } } return addresses; } function requestOrigin(req) { const host = req.headers.host || `localhost:${PORT}`; return `https://${host}`; } function token(bytes = 24) { return crypto.randomBytes(bytes).toString("base64url"); } function nowSeconds() { return Math.floor(Date.now() / 1000); } function htmlEscape(value) { return String(value) .replaceAll("&", "&") .replaceAll("<", "<") .replaceAll(">", ">") .replaceAll('"', """); } function quoteSfString(value) { return `"${String(value).replaceAll("\\", "\\\\").replaceAll('"', '\\"')}"`; } function parseStructuredString(value) { if (!value) return null; const input = String(value).trim(); if (!input.startsWith('"')) return input.split(";", 1)[0].trim(); let output = ""; let escaped = false; for (let i = 1; i < input.length; i += 1) { const ch = input[i]; if (escaped) { output += ch; escaped = false; continue; } if (ch === "\\") { escaped = true; continue; } if (ch === '"') break; output += ch; } return output; } function parseCookies(req) { const cookies = new Map(); const raw = req.headers.cookie; if (!raw) return cookies; for (const part of raw.split(";")) { const [name, ...rest] = part.trim().split("="); if (!name) continue; cookies.set(name, decodeURIComponent(rest.join("="))); } return cookies; } function pruneSessionCookies(session) { const now = nowSeconds(); for (const [value, expires] of session.cookies) { if (expires <= now) { session.cookies.delete(value); state.cookieIndex.delete(value); } } } function getSessionFromCookie(req) { const cookies = parseCookies(req); const cookieValue = cookies.get(COOKIE_NAME); if (!cookieValue) return null; const sessionId = state.cookieIndex.get(cookieValue); if (!sessionId) return null; const session = state.sessions.get(sessionId); if (!session) return null; pruneSessionCookies(session); if (!session.cookies.has(cookieValue)) return null; return session; } function boundCookie(value, maxAge = COOKIE_MAX_AGE_SECONDS) { return `${COOKIE_NAME}=${encodeURIComponent(value)}; Max-Age=${maxAge}; Path=/; Secure; HttpOnly; SameSite=Lax`; } function sessionInstructions(session) { return { session_identifier: session.id, refresh_url: "/dbsc/refresh", scope: { include_site: false, scope_specification: [ { type: "exclude", path: "/dbsc" }, { type: "exclude", path: "/login" }, { type: "exclude", path: "/logout" }, ], }, credentials: [ { type: "cookie", name: COOKIE_NAME, attributes: "Path=/; Secure; HttpOnly; SameSite=Lax", }, ], allowed_refresh_initiators: [], }; } function decodeJwt(jwt) { const parts = String(jwt).split("."); if (parts.length !== 3) throw new Error("DBSC proof is not a compact JWS."); return { parts, header: JSON.parse(Buffer.from(parts[0], "base64url").toString("utf8")), payload: JSON.parse(Buffer.from(parts[1], "base64url").toString("utf8")), signature: parts[2] ? Buffer.from(parts[2], "base64url") : Buffer.alloc(0), signingInput: Buffer.from(`${parts[0]}.${parts[1]}`, "ascii"), }; } function verifyDbscJwt({ jwt, expectedChallenge, expectedAuthorization = null, storedJwk = null, registration = false }) { const proof = decodeJwt(jwt); const { header, payload } = proof; if (header.typ !== "dbsc+jwt") throw new Error("JWT typ must be dbsc+jwt."); if (payload.jti !== expectedChallenge) throw new Error("JWT challenge mismatch."); if (expectedAuthorization && payload.authorization !== expectedAuthorization) { throw new Error("JWT authorization mismatch."); } const alg = String(header.alg || ""); let jwk = storedJwk; if (registration) { if (alg !== "none" && !header.jwk) { throw new Error("Registration proof must include the public JWK."); } jwk = header.jwk || null; } if (alg === "none") { if (!ALLOW_NONE_ALGORITHM) throw new Error("alg=none is disabled."); if (proof.signature.length !== 0) throw new Error("alg=none JWT must have an empty signature."); return { header, payload, jwk, algorithm: alg }; } if (alg !== "ES256") throw new Error(`Unsupported JWT alg '${alg}'.`); if (!jwk) throw new Error("Missing JWK for ES256 verification."); if (jwk.kty !== "EC" || jwk.crv !== "P-256") throw new Error("Only EC P-256 JWKs are supported."); if (proof.signature.length !== 64) throw new Error("ES256 signature must be 64 bytes."); const publicKey = crypto.createPublicKey({ key: jwk, format: "jwk" }); const ok = crypto.verify( "sha256", proof.signingInput, { key: publicKey, dsaEncoding: "ieee-p1363" }, proof.signature, ); if (!ok) throw new Error("ES256 signature verification failed."); return { header, payload, jwk, algorithm: alg }; } function send(res, status, body = "", contentType = "text/html; charset=utf-8", headers = {}) { const bytes = Buffer.from(body); res.statusCode = status; res.setHeader("Cache-Control", "no-store"); res.setHeader("Content-Type", contentType); res.setHeader("Content-Length", bytes.length); for (const [name, value] of Object.entries(headers)) { res.setHeader(name, value); } res.end(bytes); } function sendJson(res, status, value, headers = {}) { send(res, status, JSON.stringify(value, null, 2), "application/json", headers); } function page(title, body) { return ` ${htmlEscape(title)}
${body}
`; } function eventLog() { return htmlEscape(state.events.join("\n")); } function loginPage(origin, notice = "") { return page("DBSC POC", `

DBSC POC

${htmlEscape(origin)}
Protected Bound-only
${htmlEscape(notice)}
${eventLog()}
`); } function pendingLoginPage(username, origin) { return page("DBSC POC", `

DBSC POC

Signed in as ${htmlEscape(username)}
Protected
Registration
waiting
Cookie lifetime
${COOKIE_MAX_AGE_SECONDS} seconds
Origin
${htmlEscape(origin)}
${eventLog()}
`); } function protectedPage(session) { const ttl = Math.max(0, session.cookieExpires - nowSeconds()); return page("Protected", `

Protected

Bound cookie accepted
Logout
DBSC session
${htmlEscape(session.id)}
JWT alg
${htmlEscape(session.algorithm)}
Cookie TTL
${ttl} seconds
Refresh endpoint
/dbsc/refresh
${eventLog()}
`); } function deviceBoundPage(session) { const ttl = Math.max(0, session.cookieExpires - nowSeconds()); return page("Device Bound", `

Device Bound

DBSC cookie required
Protected Logout
User
${htmlEscape(session.username)}
DBSC session
${htmlEscape(session.id)}
Cookie name
${COOKIE_NAME}
Cookie TTL
${ttl} seconds
Reload Whoami JSON
${eventLog()}
`); } async function readBody(req) { const chunks = []; for await (const chunk of req) chunks.push(chunk); return Buffer.concat(chunks).toString("utf8"); } async function handleLogin(req, res) { const body = await readBody(req); const form = new URLSearchParams(body); const username = form.get("username") || "demo"; const challenge = token(); state.pending.set(challenge, { challenge, username, created: nowSeconds(), }); addEvent(`login accepted for '${username}'; sent Secure-Session-Registration`); send(res, 200, pendingLoginPage(username, requestOrigin(req)), "text/html; charset=utf-8", { "Secure-Session-Registration": `(ES256);path=${quoteSfString("/dbsc/register")};challenge=${quoteSfString(challenge)}`, }); } async function handleRegister(req, res) { try { const jwt = parseStructuredString(req.headers["secure-session-response"]); if (!jwt) throw new Error("Missing Secure-Session-Response."); const unverified = decodeJwt(jwt); const pending = state.pending.get(unverified.payload.jti); if (!pending) throw new Error("Missing or unknown registration challenge."); const proof = verifyDbscJwt({ jwt, expectedChallenge: pending.challenge, registration: true, }); const sessionId = token(18); const cookieValue = token(32); const nextChallenge = token(); const session = { id: sessionId, username: pending.username, jwk: proof.jwk, algorithm: proof.algorithm, cookieValue, cookieExpires: nowSeconds() + COOKIE_MAX_AGE_SECONDS, cookies: new Map([[cookieValue, nowSeconds() + COOKIE_MAX_AGE_SECONDS]]), challenge: nextChallenge, challengeExpires: nowSeconds() + 90, }; state.sessions.set(sessionId, session); state.cookieIndex.set(cookieValue, sessionId); state.pending.delete(pending.challenge); addEvent(`registered DBSC session ${sessionId} with ${proof.algorithm}`); sendJson(res, 200, sessionInstructions(session), { "Set-Cookie": boundCookie(cookieValue), "Secure-Session-Challenge": `${quoteSfString(nextChallenge)};id=${quoteSfString(sessionId)}`, }); } catch (error) { addEvent(`registration failed: ${error.message}`); sendJson(res, 400, { error: error.message }); } } async function handleRefresh(req, res) { try { const sessionId = parseStructuredString(req.headers["sec-secure-session-id"]); const session = sessionId ? state.sessions.get(sessionId) : null; if (!session) throw new Error("Unknown Sec-Secure-Session-Id."); const jwt = parseStructuredString(req.headers["secure-session-response"]); if (!jwt) { session.challenge = token(); session.challengeExpires = nowSeconds() + 90; addEvent(`refresh challenge issued for ${session.id}`); send(res, 403, "", "text/plain; charset=utf-8", { "Secure-Session-Challenge": `${quoteSfString(session.challenge)};id=${quoteSfString(session.id)}`, }); return; } if (session.challengeExpires <= nowSeconds()) { throw new Error("Refresh challenge expired."); } verifyDbscJwt({ jwt, expectedChallenge: session.challenge, storedJwk: session.jwk, }); pruneSessionCookies(session); session.cookieValue = token(32); session.cookieExpires = nowSeconds() + COOKIE_MAX_AGE_SECONDS; session.cookies.set(session.cookieValue, session.cookieExpires); session.challenge = token(); session.challengeExpires = nowSeconds() + 90; state.cookieIndex.set(session.cookieValue, session.id); addEvent(`refreshed bound cookie for ${session.id}`); sendJson(res, 200, sessionInstructions(session), { "Set-Cookie": boundCookie(session.cookieValue), "Secure-Session-Challenge": `${quoteSfString(session.challenge)};id=${quoteSfString(session.id)}`, }); } catch (error) { addEvent(`refresh failed: ${error.message}`); sendJson(res, 403, { continue: false, error: error.message }); } } function handleStatus(req, res) { const session = getSessionFromCookie(req); if (!session) { sendJson(res, 401, { authenticated: false }); return; } sendJson(res, 200, { authenticated: true, session_identifier: session.id, cookie_ttl_seconds: Math.max(0, session.cookieExpires - nowSeconds()), }, { "Secure-Session-Challenge": `${quoteSfString(session.challenge)};id=${quoteSfString(session.id)}`, }); } function handleWhoami(req, res) { const session = getSessionFromCookie(req); if (!session) { if (req.headers["secure-session-skipped"]) { addEvent(`whoami request arrived with Secure-Session-Skipped: ${req.headers["secure-session-skipped"]}`); } sendJson(res, 401, { authenticated: false, dbsc: { active: false, required_cookie: COOKIE_NAME, }, }); return; } sendJson(res, 200, { authenticated: true, username: session.username, dbsc: { active: true, session_identifier: session.id, cookie_name: COOKIE_NAME, cookie_ttl_seconds: Math.max(0, session.cookieExpires - nowSeconds()), proof_algorithm: session.algorithm, refresh_url: "/dbsc/refresh", accepted_cookie_count: session.cookies.size, }, }, { "Secure-Session-Challenge": `${quoteSfString(session.challenge)};id=${quoteSfString(session.id)}`, }); } function handleProtected(req, res) { const session = getSessionFromCookie(req); if (!session) { if (req.headers["secure-session-skipped"]) { addEvent(`protected request arrived with Secure-Session-Skipped: ${req.headers["secure-session-skipped"]}`); } send(res, 401, loginPage(requestOrigin(req), "No valid bound cookie on this request.")); return; } send(res, 200, protectedPage(session), "text/html; charset=utf-8", { "Secure-Session-Challenge": `${quoteSfString(session.challenge)};id=${quoteSfString(session.id)}`, }); } function handleDeviceBound(req, res) { const session = getSessionFromCookie(req); if (!session) { if (req.headers["secure-session-skipped"]) { addEvent(`device-bound request arrived with Secure-Session-Skipped: ${req.headers["secure-session-skipped"]}`); } send(res, 401, loginPage(requestOrigin(req), "A current device-bound session cookie is required.")); return; } send(res, 200, deviceBoundPage(session), "text/html; charset=utf-8", { "Secure-Session-Challenge": `${quoteSfString(session.challenge)};id=${quoteSfString(session.id)}`, }); } function handleExpire(req, res) { const session = getSessionFromCookie(req); if (session) { const presentedCookie = parseCookies(req).get(COOKIE_NAME); if (presentedCookie) { session.cookies.delete(presentedCookie); state.cookieIndex.delete(presentedCookie); } if (presentedCookie === session.cookieValue) { session.cookieExpires = nowSeconds() - 1; } addEvent(`expired bound cookie for ${session.id}`); } res.writeHead(302, { Location: "/protected", "Cache-Control": "no-store", "Set-Cookie": boundCookie("expired", 0), }); res.end(); } function handleLogout(req, res) { state.sessions.clear(); state.cookieIndex.clear(); addEvent("cleared local sessions"); res.writeHead(302, { Location: "/", "Cache-Control": "no-store", "Set-Cookie": boundCookie("logged-out", 0), "Clear-Site-Data": '"cookies"', }); res.end(); } const tlsOptions = ensureCertificate(); const server = https.createServer(tlsOptions, async (req, res) => { try { const url = new URL(req.url, requestOrigin(req)); const route = `${req.method} ${url.pathname}`; if (route === "GET /") return send(res, 200, loginPage(requestOrigin(req))); if (route === "POST /login") return handleLogin(req, res); if (route === "POST /dbsc/register") return handleRegister(req, res); if (route === "POST /dbsc/refresh") return handleRefresh(req, res); if (route === "GET /api/status") return handleStatus(req, res); if (route === "GET /api/whoami") return handleWhoami(req, res); if (route === "GET /protected") return handleProtected(req, res); if (route === "GET /device-bound") return handleDeviceBound(req, res); if (route === "POST /expire") return handleExpire(req, res); if (route === "GET /logout") return handleLogout(req, res); send(res, 404, "Not found", "text/plain; charset=utf-8"); } catch (error) { addEvent(`request failed: ${error.stack || error.message}`); send(res, 500, "Internal Server Error", "text/plain; charset=utf-8"); } }); server.listen(PORT, BIND_HOST, () => { addEvent(`DBSC POC listening on ${BIND_HOST}:${PORT}`); addEvent(`open https://localhost:${PORT}/ or https://:${PORT}/`); addEvent(`bound cookies last ${COOKIE_MAX_AGE_SECONDS} seconds`); });