<html>
<head><title>OK</title></head>
<body>
A basic SD-JWT verifier.
<script>
function main(token, key, aud, nonce) {
return check(token, key, aud, nonce);
}
async function check(token, key, aud, nonce) {
console.log("checking sdjwtkb");
const [issued, disclosures, kb] = parse(token);
const signed = await verify(issued, key);
if (!signed) {
console.log("signature doesn't match");
return false;
}
console.log("signatures match");
const payload = JSON.parse(base64decode(issued.payload));
console.log("verifying. bound?");
const bound = await verify(kb, payload.cnf.jwk);
console.log(bound);
if (!bound) {
return false;
}
const binding = JSON.parse(base64decode(kb.payload));
console.log("aud checks out?" + binding.aud);
if (binding.aud != aud) {
return false;
}
console.log("nonce checks out?");
if (binding.nonce != nonce) {
return false;
}
console.log("issued recently?");
if (!binding.iat) {
return false;
}
console.log("lets look at disclosures!");
for (const disclosure of disclosures) {
console.log("Parsing a disclosure");
console.log(disclosure);
const serialization = base64UrlEncode(JSON.stringify(disclosure));
const digest = await sha256(serialization);
if (!payload._sd.includes(digest)) {
return false;
}
}
console.log("done!");
return disclosures.map(([salt, name, value]) => value);
}
function jwt(str) {
const header = str.substring(0, str.indexOf("."));
str = str.substring(header.length + 1);
const payload = str.substring(0, str.indexOf("."));
str = str.substring(payload.length + 1);
return {
header: header,
payload: payload,
signature: str
}
}
function parse(token) {
let str = token;
const first = str.substring(0, str.indexOf("~"));
str = str.substring(first.length + 1);
const issued = jwt(first);
const disclosures = [];
while (str.indexOf("~") > 0) {
const disclosure = str.substring(0, str.indexOf("~"));
str = str.substring(disclosure.length + 1);
console.log("Parsing disclosure " + disclosure);
disclosures.push(JSON.parse(base64decode(disclosure)));
}
const kb = jwt(str);
return [issued, disclosures, kb];
}
async function verify(jwt, jwk) {
const {header, payload, signature} = jwt;
const bufSignature = base64ToArrayBuffer(stripurlencoding(signature));
const data = header + "." + payload;
const bufData = textToArrayBuffer(data);
const algo = {
name: "ECDSA",
namedCurve: "P-256",
};
const hash = {name: "SHA-256"};
const signAlgo = {...algo, hash};
const key = await crypto.subtle.importKey("jwk", jwk, {
name: "ECDSA",
namedCurve: "P-256",
}, true, ["verify"]);
return await crypto.subtle.verify(
signAlgo, key, bufSignature, bufData);
}
function stripurlencoding(b64) {
return b64.replace(/_/g, '/').replace(/-/g, '+');
}
function base64ToArrayBuffer(b64) {
var byteString = atob(b64);
var byteArray = new Uint8Array(byteString.length);
for (var i = 0; i < byteString.length; i++) {
byteArray[i] = byteString.charCodeAt(i);
}
return byteArray.buffer;
}
function textToArrayBuffer(str) {
var buf = unescape(encodeURIComponent(str))
var bufView = new Uint8Array(buf.length)
for (var i=0; i < buf.length; i++) {
bufView[i] = buf.charCodeAt(i)
}
return bufView
}
function base64decode(base64) {
return atob(base64.replace(/_/g, '/').replace(/-/g, '+'));
}
function urlEncode(str) {
return str.replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
}
function base64UrlEncode(str) {
const base64Encoded = btoa(str);
return urlEncode(base64Encoded);
}
async function sha256(message) {
const encoder = new TextEncoder();
const data = encoder.encode(message);
const hash = await window.crypto.subtle.digest("SHA-256", data);
const hashArray = Array.from(new Uint8Array(hash));
return base64UrlEncode(String.fromCharCode(...hashArray));
}
</script>
</body>
</html>