﻿<!DOCTYPE HTML>
<html>
<head>
  <script type="application/javascript" src="pc.js"></script>
</head>
<body>
<pre id="test">
<script type="application/javascript">
  createHTML({
    bug: "1172785",
    title: "Certificate management"
  });

  function badCertificate(config, expectedError, message) {
    return RTCPeerConnection.generateCertificate(config)
      .then(() => ok(false, message),
            e => is(e.name, expectedError, message));
  }

  // Checks a handful of obviously bad options to RTCCertificate.create().  Most
  // of the checking is done by the WebCrypto code underpinning this, hence the
  // baffling error codes, but a sanity check is still in order.
  function checkBadParameters() {
    return Promise.all([
      badCertificate({
        name: "RSASSA-PKCS1-v1_5",
        hash: "SHA-256",
        modulusLength: 1023,
        publicExponent: new Uint8Array([1, 0, 1])
      }, "NotSupportedError", "1023-bit is too small to succeed"),

      badCertificate({
        name: "RSASSA-PKCS1-v1_5",
          hash: "SHA-384",
          modulusLength: 2048,
          publicExponent: new Uint8Array([1, 0, 1])
      }, "NotSupportedError", "SHA-384 isn't supported yet"),

      // A SyntaxError happens in the "generate key operation" step, but
      // webrtc-pc does not say to reject the promise if this step fails.
      // It does say to throw NotSupportedError if we have passed "an
      // algorithm that the user agent cannot or will not use to generate a
      // certificate".
      badCertificate({
        name: "ECDH",
        namedCurve: "P-256"
      }, "NotSupportedError", "ECDH is rejected because the usage is neither \"deriveKey\" or \"deriveBits\""),

      badCertificate({
        name: "not a valid algorithm"
      }, "NotSupportedError", "not a valid algorithm"),

      badCertificate("ECDSA", "NotSupportedError", "a bare name is not enough"),

      badCertificate({
        name: "ECDSA",
        namedCurve: "not a curve"
      }, "NotSupportedError", "ECDSA with an unknown curve")
    ]);
  }

  function createDB() {
    var openDB = indexedDB.open("genericstore");
    openDB.onupgradeneeded = e => {
      var db = e.target.result;
      db.createObjectStore("data");
    };
    return new Promise(resolve => {
      openDB.onsuccess = e => resolve(e.target.result);
    });
  }

  function resultPromise(tx, op) {
    return new Promise((resolve, reject) => {
      op.onsuccess = e => resolve(e.target.result);
      op.onerror = () => reject(op.error);
      tx.onabort = () => reject(tx.error);
    });
  }

  function store(db, value) {
    var tx = db.transaction("data", "readwrite");
    var store = tx.objectStore("data");
    return resultPromise(tx, store.put(value, "value"));
  }

  function retrieve(db) {
    var tx = db.transaction("data", "readonly");
    var store = tx.objectStore("data");
    return resultPromise(tx, store.get("value"));
  }

  // Creates a database, stores a value, retrieves it.
  function storeAndRetrieve(value) {
    return createDB().then(db => {
      return store(db, value)
        .then(() => retrieve(db))
        .then(retrieved => {
          db.close();
          return retrieved;
        });
    });
  }

  var test;
  runNetworkTest(function (options) {
    var expiredCert;
    return Promise.resolve()
      .then(() => RTCPeerConnection.generateCertificate({
        name: "ECDSA",
        namedCurve: "P-256",
        expires: 1 // smallest possible expiration window
      }))
      .then(cert => {
        ok(!isNaN(cert.expires), 'cert has expiration time');
        info('Expires at ' + new Date(cert.expires));
        expiredCert = cert;
      })

      .then(() => checkBadParameters())

      .then(() => {
        var delay = expiredCert.expires - Date.now();
        // Hopefully this delay is never needed.
        if (delay > 0) {
          return new Promise(r => setTimeout(r, delay));
        }
      })
      .then(() => {
        ok(expiredCert.expires <= Date.now(), 'Cert should be at or past expiration');
        try {
          new RTCPeerConnection({ certificates: [expiredCert] });
          ok(false, 'Constructing peer connection with an expired cert is not allowed');
        } catch(e) {
          is(e.name, 'InvalidAccessError',
             'Constructing peer connection with an expired certs is not allowed');
        }
      })

      .then(() => Promise.all([
        RTCPeerConnection.generateCertificate({
          name: "ECDSA",
          namedCurve: "P-256"
        }),
        RTCPeerConnection.generateCertificate({
          name: "RSASSA-PKCS1-v1_5",
          hash: "SHA-256",
          modulusLength: 2048,
          publicExponent: new Uint8Array([1, 0, 1])
        })
      ]))

    // A round trip through indexedDB should not do anything.
      .then(storeAndRetrieve)
      .then(certs => {
        try {
          new RTCPeerConnection({ certificates: certs });
          ok(false, 'Constructing peer connection with multiple certs is not allowed');
        } catch(e) {
          is(e.name, 'NotSupportedError',
             'Constructing peer connection with multiple certs is not allowed');
        }
        return certs;
      })
      .then(certs => {
        test = new PeerConnectionTest({
          config_local: {
            certificates: [certs[0]]
          },
          config_remote: {
            certificates: [certs[1]]
          }
        });
        test.setMediaConstraints([{audio: true}], [{audio: true}]);
        return test.run();
      })
      .catch(e => {
        console.log('test failure', e);
        ok(false, 'test failed: ' + e);
      });
  });
</script>
</pre>
</body>
</html>
