<!DOCTYPE html>
<html lang="en">
<head>
<script>
function base64urlToBase64(base64url) {
let base64 = base64url.replace(/-/g, "+").replace(/_/g, "/");
// Add padding to make the length of the base64 string divisible by 4.
if (base64.length % 4 != 0)
base64 += "=".repeat(4 - base64.length % 4);
return base64;
}
function base64ToArrayBuffer(base64) {
return Uint8Array.from(atob(base64), c => c.charCodeAt(0)).buffer;
}
function arrayBufferToBase64(buffer) {
let binary = "";
let bytes = new Uint8Array(buffer);
for (let i = 0; i < bytes.byteLength; ++i)
binary += String.fromCharCode(bytes[i]);
return window.btoa(binary);
};
function parseAuthenticatorFlags(flags) {
return {
up: !!(flags & 1 << 0),
uv: !!(flags & 1 << 2),
be: !!(flags & 1 << 3),
bs: !!(flags & 1 << 4),
at: !!(flags & 1 << 6),
ed: !!(flags & 1 << 7),
};
}
async function generateBase64Key() {
let ecKey = await window.crypto.subtle.generateKey(
{ name: "ECDSA", namedCurve: "P-256" }, true /* extractable */, ["sign", "verify"]);
let privateKeyPkcs8 = await window.crypto.subtle.exportKey("pkcs8", ecKey.privateKey);
return arrayBufferToBase64(privateKeyPkcs8);
};
async function generateRpIdHash(rpId = "devtools.test") {
let hash = await window.crypto.subtle.digest(
{ name: "SHA-256" }, new TextEncoder("utf-8").encode(rpId));
return arrayBufferToBase64(hash);
}
async function registerCredential(options = {}) {
options = Object.assign({
authenticatorSelection: {
requireResidentKey: false,
userVerification: "discouraged",
},
rp: {
id: "devtools.test",
name: "DevTools Test",
},
challenge: Uint8Array.from("challenge"),
pubKeyCredParams: [
{type: "public-key", alg: -7},
],
user: {
name: "name",
displayName: "displayName",
id: Uint8Array.from([1]),
},
}, options);
try {
const credential = await navigator.credentials.create({publicKey: options});
let result = {
status: "OK",
credential: {
id: credential.id,
rawId: Array.from(new Uint8Array(credential.rawId)),
transports: credential.response.getTransports(),
},
};
const extensions = credential.getClientExtensionResults();
if (extensions.largeBlob) {
result.largeBlobSupported = extensions.largeBlob.supported;
}
if ('credBlob' in extensions) {
result.credBlob = extensions.credBlob;
}
result.flags = parseAuthenticatorFlags(
new Uint8Array(credential.response.getAuthenticatorData())[32]);
return result;
} catch (error) {
return {status: error.toString()};
}
}
async function getCredential(credential, options = {}) {
options = Object.assign({
challenge: Uint8Array.from("Winter is Coming"),
rpId: "devtools.test",
allowCredentials: [credential],
userVerification: "preferred",
}, options);
try {
const attestation = await navigator.credentials.get({publicKey: options});
let result = {
status: "OK",
attestation,
};
if (attestation.getClientExtensionResults().largeBlob) {
result.blob = new TextDecoder().decode(
attestation.getClientExtensionResults().largeBlob.blob);
}
result.flags = parseAuthenticatorFlags(
new Uint8Array(attestation.response.authenticatorData)[32]);
return result;
} catch (error) {
return {status: error.toString()};
}
}
async function getCredentialSignature() {
try {
const r = await getCredential({
type: "public-key",
id: new TextEncoder().encode("cred-1"),
transports: ["usb", "ble", "nfc"],
});
const signature = new Uint8Array(r.attestation.response.signature);
return {
status: "OK",
signature
}
} catch(error) {
return {status: error.toString()};
}
}
async function getFlags(bitNumber) {
try {
const r = await getCredential({
type: "public-key",
id: new TextEncoder().encode("cred-1"),
transports: ["usb", "ble", "nfc"],
});
const flags = new Uint8Array(r.attestation.response.authenticatorData)[32];
// bitNumber is 0 indexed.
const bit = (flags & (1 << bitNumber)) != 0 ? 1 : 0;
return {
status: "OK",
bit
}
} catch(error) {
return {status: error.toString()};
}
}
</script>
</head>
<body>
</body>
</html>