Skip to content

Instantly share code, notes, and snippets.

@philips
Created February 11, 2026 23:19
Show Gist options
  • Select an option

  • Save philips/c00ac10f4ecafda2b8e4f267f639254c to your computer and use it in GitHub Desktop.

Select an option

Save philips/c00ac10f4ecafda2b8e4f267f639254c to your computer and use it in GitHub Desktop.
Passkey test
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Passkey Signer & Verifier</title>
<style>
body { font-family: system-ui, -apple-system, sans-serif; line-height: 1.5; max-width: 600px; margin: 40px auto; padding: 20px; color: #333; }
.card { border: 1px solid #ddd; padding: 20px; border-radius: 12px; box-shadow: 0 4px 6px rgba(0,0,0,0.05); margin-bottom: 20px; }
button { background: #007bff; color: white; border: none; padding: 10px 18px; border-radius: 6px; cursor: pointer; font-weight: 600; margin-top: 10px; }
button:disabled { background: #ccc; cursor: not-allowed; }
#payload-display { font-family: monospace; background: #eee; padding: 10px; border-radius: 4px; display: block; margin: 10px 0; }
.status { font-weight: bold; margin-top: 15px; }
.success { color: #28a745; }
.error { color: #dc3545; }
</style>
</head>
<body>
<h2>Passkey Tool</h2>
<div class="card">
<h3>1. Setup</h3>
<p>First, register this device to create a local public key.</p>
<button onclick="register()">Register Passkey</button>
<div id="reg-status" class="status"></div>
</div>
<div class="card">
<h3>2. Sign Payload</h3>
<p>Payload from URL:</p>
<span id="payload-display">None detected in #hash</span>
<button id="sign-btn" onclick="sign()" disabled>Sign with Passkey</button>
<div id="sign-status" class="status"></div>
</div>
<script>
// --- UTILS ---
const bufferEncode = (str) => new TextEncoder().encode(str);
const base64ToBuffer = (b64) => Uint8Array.from(atob(b64), c => c.charCodeAt(0));
const bufferToBase64 = (buf) => btoa(String.fromCharCode(...new Uint8Array(buf)));
// --- 1. REGISTRATION ---
async function register() {
const status = document.getElementById('reg-status');
try {
const options = {
publicKey: {
challenge: crypto.getRandomValues(new Uint8Array(32)),
rp: { name: "Passkey Demo", id: window.location.hostname },
user: { id: crypto.getRandomValues(new Uint8Array(16)), name: "user@demo", displayName: "Demo User" },
pubKeyCredParams: [{ alg: -7, type: "public-key" }], // ES256
authenticatorSelection: { residentKey: "required" },
timeout: 60000
}
};
const cred = await navigator.credentials.create(options);
// Save Credential ID and Public Key for later local use
localStorage.setItem("psk_id", bufferToBase64(cred.rawId));
localStorage.setItem("psk_pubkey", bufferToBase64(cred.response.getPublicKey()));
status.className = "status success";
status.innerText = "✅ Passkey Registered!";
document.getElementById('sign-btn').disabled = false;
} catch (err) {
status.className = "status error";
status.innerText = "❌ Error: " + err.message;
}
}
// --- 2. SIGNING ---
async function sign() {
const status = document.getElementById('sign-status');
const payload = window.location.hash.substring(1) || "default-payload";
const savedId = localStorage.getItem("psk_id");
try {
const options = {
publicKey: {
challenge: bufferEncode(payload),
allowCredentials: [{ id: base64ToBuffer(savedId), type: 'public-key' }],
userVerification: "required",
rpId: window.location.hostname
}
};
const assertion = await navigator.credentials.get(options);
// --- 3. IMMEDIATE VERIFICATION ---
const isValid = await verifyLocally(assertion, payload);
if (isValid) {
status.className = "status success";
status.innerText = "✅ Signature Verified Locally!";
} else {
throw new Error("Signature failed verification.");
}
} catch (err) {
status.className = "status error";
status.innerText = "❌ Error: " + err.message;
}
}
// --- 4. VERIFICATION (WEB CRYPTO API) ---
async function verifyLocally(assertion, originalPayload) {
const pubKeyB64 = localStorage.getItem("psk_pubkey");
if (!pubKeyB64) return false;
// Import public key
const publicKey = await crypto.subtle.importKey(
"spki",
base64ToBuffer(pubKeyB64),
{ name: "ECDSA", namedCurve: "P-256" },
false,
["verify"]
);
// Reconstruct the signed data
const clientDataHash = await crypto.subtle.digest("SHA-256", assertion.response.clientDataJSON);
const authData = new Uint8Array(assertion.response.authenticatorData);
const signedData = new Uint8Array(authData.length + clientDataHash.byteLength);
signedData.set(authData, 0);
signedData.set(new Uint8Array(clientDataHash), authData.length);
// Verify
return await crypto.subtle.verify(
{ name: "ECDSA", hash: { name: "SHA-256" } },
publicKey,
assertion.response.signature,
signedData
);
}
// Initialize UI
window.onload = () => {
const hash = window.location.hash.substring(1);
if (hash) document.getElementById('payload-display').innerText = decodeURIComponent(hash);
if (localStorage.getItem("psk_id")) document.getElementById('sign-btn').disabled = false;
};
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment