"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)}
`);
}
function pendingLoginPage(username, origin) {
return page("DBSC POC", `
- Registration
- waiting
- Cookie lifetime
- ${COOKIE_MAX_AGE_SECONDS} seconds
- Origin
- ${htmlEscape(origin)}
`);
}
function protectedPage(session) {
const ttl = Math.max(0, session.cookieExpires - nowSeconds());
return page("Protected", `
Protected
Bound cookie accepted
- DBSC session
- ${htmlEscape(session.id)}
- JWT alg
- ${htmlEscape(session.algorithm)}
- Cookie TTL
- ${ttl} seconds
- Refresh endpoint
- /dbsc/refresh
`);
}
function deviceBoundPage(session) {
const ttl = Math.max(0, session.cookieExpires - nowSeconds());
return page("Device Bound", `
Device Bound
DBSC cookie required
- User
- ${htmlEscape(session.username)}
- DBSC session
- ${htmlEscape(session.id)}
- Cookie name
- ${COOKIE_NAME}
- Cookie TTL
- ${ttl} seconds
`);
}
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`);
});