<!DOCTYPE html>
<html>

<head>
<title>WebCrypto Test Suite</title>
<meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
<link rel="stylesheet" href="./test_WebCrypto.css"/>
<script src="/tests/SimpleTest/SimpleTest.js"></script>

<!-- Utilities for manipulating ABVs -->
<script src="util.js"></script>

<!-- A simple wrapper around IndexedDB -->
<script src="simpledb.js"></script>

<!-- Test vectors drawn from the literature -->
<script src="./test-vectors.js"></script>

<!-- General testing framework -->
<script src="./test-array.js"></script>

<script>/*<![CDATA[*/
"use strict";

// -----------------------------------------------------------------------------
TestArray.addTest(
  "JWK import and use of an AES-GCM key",
  function () {
    var that = this;

    function doEncrypt(x) {
      return crypto.subtle.encrypt(
        {
          name: "AES-GCM",
          iv: tv.aes_gcm_enc.iv,
          additionalData: tv.aes_gcm_enc.adata,
          tagLength: 128
        },
        x, tv.aes_gcm_enc.data);
    }

    crypto.subtle.importKey("jwk", tv.aes_gcm_enc.key_jwk, "AES-GCM", false, ['encrypt'])
      .then(doEncrypt)
      .then(
        memcmp_complete(that, tv.aes_gcm_enc.result),
        error(that)
      );
  }
);

// -----------------------------------------------------------------------------
TestArray.addTest(
  "JWK import and use of an RSASSA-PKCS1-v1_5 private key",
  function () {
    var that = this;
    var alg = { name: "RSASSA-PKCS1-v1_5", hash: "SHA-256" };

    function doSign(x) {
      return crypto.subtle.sign(alg.name, x, tv.rsassa.data);
    }
    function fail(x) { console.log(x); error(that); }

    crypto.subtle.importKey("jwk", tv.rsassa.jwk_priv, alg, false, ['sign'])
      .then( doSign, fail )
      .then( memcmp_complete(that, tv.rsassa.sig256), fail );
  }
);

// -----------------------------------------------------------------------------
TestArray.addTest(
  "JWK import and use of an RSASSA-PKCS1-v1_5 public key",
  function () {
    var that = this;
    var alg = { name: "RSASSA-PKCS1-v1_5", hash: "SHA-256" };

    function doVerify(x) {
      return crypto.subtle.verify(alg.name, x, tv.rsassa.sig256, tv.rsassa.data);
    }
    function fail(x) { error(that); }

    crypto.subtle.importKey("jwk", tv.rsassa.jwk_pub, alg, false, ['verify'])
      .then( doVerify, fail )
      .then(
        complete(that, function(x) { return x; }),
        fail
      );
  });

// -----------------------------------------------------------------------------
TestArray.addTest(
  "JWK import failure on incomplete RSA private key (missing 'qi')",
  function () {
    var that = this;
    var alg = { name: "RSA-OAEP", hash: "SHA-256" };
    var jwk = {
      kty: "RSA",
      n: tv.rsassa.jwk_priv.n,
      e: tv.rsassa.jwk_priv.e,
      d: tv.rsassa.jwk_priv.d,
      p: tv.rsassa.jwk_priv.p,
      q: tv.rsassa.jwk_priv.q,
      dp: tv.rsassa.jwk_priv.dp,
      dq: tv.rsassa.jwk_priv.dq,
    };

    crypto.subtle.importKey("jwk", jwk, alg, true, ['encrypt', 'decrypt'])
      .then( error(that), complete(that) );
  }
);

// -----------------------------------------------------------------------------
TestArray.addTest(
  "JWK import failure on algorithm mismatch",
  function () {
    var that = this;
    var alg = "AES-GCM";
    var jwk = { k: "c2l4dGVlbiBieXRlIGtleQ", alg: "A256GCM" };

    crypto.subtle.importKey("jwk", jwk, alg, true, ['encrypt', 'decrypt'])
      .then( error(that), complete(that) );
  }
);

// -----------------------------------------------------------------------------
TestArray.addTest(
  "JWK import failure on usages mismatch",
  function () {
    var that = this;
    var alg = "AES-GCM";
    var jwk = { k: "c2l4dGVlbiBieXRlIGtleQ", key_ops: ['encrypt'] };

    crypto.subtle.importKey("jwk", jwk, alg, true, ['encrypt', 'decrypt'])
      .then( error(that), complete(that) );
  }
);

// -----------------------------------------------------------------------------
TestArray.addTest(
  "JWK import failure on extractable mismatch",
  function () {
    var that = this;
    var alg = "AES-GCM";
    var jwk = { k: "c2l4dGVlbiBieXRlIGtleQ", ext: false };

    crypto.subtle.importKey("jwk", jwk, alg, true, ['encrypt'])
      .then( error(that), complete(that) );
  }
);

// -----------------------------------------------------------------------------
TestArray.addTest(
  "JWK export of a symmetric key",
  function () {
    var that = this;
    var alg = "AES-GCM";
    var jwk = { k: "c2l4dGVlbiBieXRlIGtleQ", kty: "oct" };

    function doExport(k) {
      return crypto.subtle.exportKey("jwk", k);
    }

    crypto.subtle.importKey("jwk", jwk, alg, true, ['encrypt', 'decrypt'])
      .then(doExport)
      .then(
        complete(that, function(x) {
          return hasBaseJwkFields(x) &&
                 hasFields(x, ['k']) &&
                 x.kty == 'oct' &&
                 x.alg == 'A128GCM' &&
                 x.ext &&
                 shallowArrayEquals(x.key_ops, ['encrypt','decrypt']) &&
                 x.k == jwk.k
        }),
        error(that)
      );
  }
);

// -----------------------------------------------------------------------------
TestArray.addTest(
  "JWK import/export of an RSA private key",
  function () {
    var jwk = tv.rsassa.jwk_priv;

    var that = this;
    var alg = { name: "RSASSA-PKCS1-v1_5", hash: "SHA-256" };

    function doExport(k) {
      return crypto.subtle.exportKey("jwk", k);
    }

    crypto.subtle.importKey("jwk", jwk, alg, true, ['sign'])
      .then(doExport)
      .then(
        complete(that, function(x) {
          return hasBaseJwkFields(x) &&
                 hasFields(x, ['n', 'e', 'd', 'p', 'q', 'dp', 'dq', 'qi']) &&
                 x.kty == 'RSA' &&
                 x.alg == 'RS256' &&
                 x.ext &&
                 shallowArrayEquals(x.key_ops, ['sign']) &&
                 x.n  == jwk.n  &&
                 x.e  == jwk.e  &&
                 x.d  == jwk.d  &&
                 x.p  == jwk.p  &&
                 x.q  == jwk.q  &&
                 x.dp == jwk.dp &&
                 x.dq == jwk.dq &&
                 x.qi == jwk.qi;
          }),
        error(that)
      );
  }
);

// -----------------------------------------------------------------------------
TestArray.addTest(
  "JWK import/export of an RSA private key where p < q",
  function () {
    var jwk = tv.rsassa.jwk_priv_pLTq;

    var that = this;
    var alg = { name: "RSASSA-PKCS1-v1_5", hash: "SHA-256" };

    function doExport(k) {
      return crypto.subtle.exportKey("jwk", k);
    }

    crypto.subtle.importKey("jwk", jwk, alg, true, ['sign'])
      .then(doExport)
      .then(
        complete(that, function(x) {
          return hasBaseJwkFields(x) &&
                 hasFields(x, ['n', 'e', 'd', 'p', 'q', 'dp', 'dq', 'qi']) &&
                 x.kty == 'RSA' &&
                 x.alg == 'RS256' &&
                 x.ext &&
                 shallowArrayEquals(x.key_ops, ['sign']) &&
                 x.n  == jwk.n  &&
                 x.e  == jwk.e  &&
                 x.d  == jwk.d  &&
                 x.p  == jwk.p  &&
                 x.q  == jwk.q  &&
                 x.dp == jwk.dp &&
                 x.dq == jwk.dq &&
                 x.qi == jwk.qi;
          }),
        error(that)
      );
  }
);

// -----------------------------------------------------------------------------
TestArray.addTest(
  "JWK export of an RSA public key",
  function () {
    var that = this;
    var alg = { name: "RSASSA-PKCS1-v1_5", hash: "SHA-256" };
    var jwk = tv.rsassa.jwk_pub;

    function doExport(k) {
      return crypto.subtle.exportKey("jwk", k);
    }

    crypto.subtle.importKey("jwk", jwk, alg, true, ['verify'])
      .then(doExport)
      .then(
        complete(that, function(x) {
          window.jwk_pub = x;
          return hasBaseJwkFields(x) &&
                 hasFields(x, ['n', 'e']) &&
                 x.kty == 'RSA' &&
                 x.alg == 'RS256' &&
                 x.ext &&
                 shallowArrayEquals(x.key_ops, ['verify']) &&
                 x.n  == jwk.n  &&
                 x.e  == jwk.e;
          }),
        error(that)
      );
  }
);

// --------
TestArray.addTest(
  "Check JWK parameters on generated ECDSA key pair",
  function() {
    crypto.subtle.generateKey({name: 'ECDSA', namedCurve: 'P-256'}, true, ['sign', 'verify'])
      .then(pair => Promise.all([
         crypto.subtle.exportKey('jwk', pair.privateKey),
         crypto.subtle.exportKey('jwk', pair.publicKey)
      ]))
      .then(
        complete(this, function(x) {
          var priv = x[0];
          var pub = x[1];
          var pubIsSubsetOfPriv = Object.keys(pub)
            .filter(k => k !== 'key_ops') // key_ops is the only complex attr
            .reduce((all, k) => all && pub[k] === priv[k], true);
          // Can't use hasBaseJwkFields() because EC keys don't get "alg":
          // "alg" matches curve to hash, but WebCrypto keys are more flexible.
          return hasFields(pub, ['kty', 'crv', 'key_ops', 'ext']) &&
            pub.kty === 'EC' &&
            pub.crv === 'P-256' &&
            pub.ext &&
            typeof(pub.x) === 'string' &&
            typeof(pub.y) === 'string' &&
            shallowArrayEquals(pub.key_ops, ['verify']) &&
            pubIsSubsetOfPriv &&
            shallowArrayEquals(priv.key_ops, ['sign']) &&
            typeof(priv.d) === 'string';
        }),
        error(this));
  }
);

// --------
TestArray.addTest(
  "Check key_ops parameter on an unusable RSA public key",
  function() {
    var parameters = {
      name: 'RSASSA-PKCS1-v1_5',
      modulusLength: 1024,
      publicExponent: new Uint8Array([1, 0, 1]),
      hash: 'SHA-256'
    };
    // The public key generated here will have no usages and will therefore
    // have an empty key_ops list.
    crypto.subtle.generateKey(parameters, true, ['sign'])
      .then(pair => crypto.subtle.exportKey('jwk', pair.publicKey))
      .then(complete(this, x => x.key_ops.length === 0),
            error(this));
  }
);
/*]]>*/</script>
</head>

<body>

<div id="content">
	<div id="head">
		<b>Web</b>Crypto<br>
	</div>

    <div id="start" onclick="start();">RUN ALL</div>

    <div id="resultDiv" class="content">
    Summary:
    <span class="pass"><span id="passN">0</span> passed, </span>
    <span class="fail"><span id="failN">0</span> failed, </span>
    <span class="pending"><span id="pendingN">0</span> pending.</span>
    <br/>
    <br/>

    <table id="results">
        <tr>
            <th>Test</th>
            <th>Result</th>
            <th>Time</th>
        </tr>
    </table>

    </div>

    <div id="foot"></div>
</div>

</body>
</html>