910e62b5创建于 1月15日历史提交
<html>
  <head><title>OK</title></head>
  <body>
    A basic SD-JWT verifier.
    <script>
      // This is a basic SD-JWT verifier based on Web Crypto
      // that is intended to be used in tests.
      //
      // You can call main() to verify a SD-JWT+KB. It checks
      // if:
      //
      // (a) The issued JWT's signature is derived from the
      // public key passed as a parameter
      // (b) The key binding JWT's signature is derived from
      // the public key in the issued JWT's payload
      // (c) The key binding JWT was addressed to the audience
      // (d) The key binding JWT contains the challenge nonce
      // (e) The disclosures digests are included in the
      // issued JWT's payload _sd array.
      //
      // It returns false if verification fails and the list
      // of all disclosured fields is it succeeds.

      function main(token, key, aud, nonce) {
        return check(token, key, aud, nonce);
      }

      async function check(token, key, aud, nonce) {
        console.log("checking sdjwtkb");

        // Can we parse the SD-JWT+KB?
        const [issued, disclosures, kb] = parse(token);

        const signed = await verify(issued, key);

        // Does the signature of the issued JWT match?
        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);

        // Does the signature of the key binding JWT match?
        if (!bound) {
          return false;
        }

        const binding = JSON.parse(base64decode(kb.payload));

        console.log("aud checks out?" + binding.aud);

        // Was the presentation intended for me?
        if (binding.aud != aud) {
          return false;
        }

        console.log("nonce checks out?");

        // Was my challenge signed?
        if (binding.nonce != nonce) {
          return false;
        }

        console.log("issued recently?");

        // Was this 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);

          // Was the disclosure included in the digest?
          if (!payload._sd.includes(digest)) {
            return false;
          }
        }

        console.log("done!");

        // Ok, everything checks out.
        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", // secp256r1
        };
        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)) // 2 bytes for each char
        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>