// Any copyright is dedicated to the Public Domain.
// http://creativecommons.org/publicdomain/zero/1.0/
"use strict";

// Checks that the security.OCSP.enabled pref correctly controls OCSP fetching
// behavior.

do_get_profile(); // Must be called before getting nsIX509CertDB
const gCertDB = Cc["@mozilla.org/security/x509certdb;1"].getService(
  Ci.nsIX509CertDB
);

const SERVER_PORT = 8888;

function certFromFile(filename) {
  return constructCertFromFile(`test_ev_certs/${filename}.pem`);
}

function loadCert(certName, trustString) {
  addCertFromFile(gCertDB, `test_ev_certs/${certName}.pem`, trustString);
}

function getFailingOCSPResponder() {
  return getFailingHttpServer(SERVER_PORT, ["www.example.com"]);
}

function getOCSPResponder(expectedCertNames) {
  return startOCSPResponder(
    SERVER_PORT,
    "www.example.com",
    "test_ev_certs",
    expectedCertNames,
    []
  );
}

// Tests that in ocspOff mode, OCSP fetches are never done.
async function testOff() {
  Services.prefs.setIntPref("security.OCSP.enabled", 0);
  info("Setting security.OCSP.enabled to 0");

  // EV chains should verify successfully and get EV status.
  clearOCSPCache();
  let ocspResponder = getFailingOCSPResponder();
  await checkEVStatus(
    gCertDB,
    certFromFile("test-oid-path-ee"),
    Ci.nsIX509CertDB.verifyUsageTLSServer,
    gEVExpected
  );
  await stopOCSPResponder(ocspResponder);

  // A DV chain should verify successfully.
  clearOCSPCache();
  ocspResponder = getFailingOCSPResponder();
  await checkCertErrorGeneric(
    gCertDB,
    certFromFile("non-ev-root-path-ee"),
    PRErrorCodeSuccess,
    Ci.nsIX509CertDB.verifyUsageTLSServer
  );
  await stopOCSPResponder(ocspResponder);
}

// Tests that in ocspOn mode, OCSP fetches are done for both EV and DV certs.
async function testOn() {
  Services.prefs.setIntPref("security.OCSP.enabled", 1);
  info("Setting security.OCSP.enabled to 1");

  // EV chains should verify successfully and get EV status.
  clearOCSPCache();
  let ocspResponder = getOCSPResponder(["test-oid-path-ee"]);
  await checkEVStatus(
    gCertDB,
    certFromFile("test-oid-path-ee"),
    Ci.nsIX509CertDB.verifyUsageTLSServer,
    gEVExpected
  );
  await stopOCSPResponder(ocspResponder);

  // If a successful OCSP response is fetched, then a DV chain should verify
  // successfully.
  clearOCSPCache();
  ocspResponder = getOCSPResponder(["non-ev-root-path-ee"]);
  await checkCertErrorGeneric(
    gCertDB,
    certFromFile("non-ev-root-path-ee"),
    PRErrorCodeSuccess,
    Ci.nsIX509CertDB.verifyUsageTLSServer
  );
  await stopOCSPResponder(ocspResponder);
}

async function testCRLiteEnforced() {
  Services.prefs.setBoolPref("security.OCSP.require", false);
  info("Setting security.OCSP.require to false");

  Services.prefs.setIntPref("security.OCSP.enabled", 1);
  info("Setting security.OCSP.enabled to 1");

  Services.prefs.setIntPref("security.pki.crlite_mode", 2);
  info("Setting security.pki.crlite_mode to 2");

  // When CRLite is enforced, OCSP requests should be made for DV certs that do
  // not chain to a builtin root.
  clearOCSPCache();
  let ocspResponder = getOCSPResponder(["non-ev-root-path-ee"]);
  await checkCertErrorGeneric(
    gCertDB,
    certFromFile("non-ev-root-path-ee"),
    PRErrorCodeSuccess,
    Ci.nsIX509CertDB.verifyUsageTLSServer
  );
  await stopOCSPResponder(ocspResponder);

  // The rest of the tests here use "security.test.built_in_root_hash", which
  // only works in debug builds.
  if (!AppConstants.DEBUG) {
    return;
  }

  // When CRLite is enforced and OCSP is not required, OCSP requests should not
  // be made for DV certs that chain to a builtin root.
  Services.prefs.setBoolPref("security.OCSP.require", false);
  info("Setting security.OCSP.require to false");
  let nonEVRootCert = certFromFile("non-evroot-ca");
  Services.prefs.setCharPref(
    "security.test.built_in_root_hash",
    nonEVRootCert.sha256Fingerprint
  );
  info(
    "Setting security.test.built_in_root_hash to " +
      nonEVRootCert.sha256Fingerprint
  );

  clearOCSPCache();
  ocspResponder = getOCSPResponder([]);
  await checkCertErrorGeneric(
    gCertDB,
    certFromFile("non-ev-root-path-ee"),
    PRErrorCodeSuccess,
    Ci.nsIX509CertDB.verifyUsageTLSServer
  );
  await stopOCSPResponder(ocspResponder);

  // When CRLite is enforced and OCSP is not required, OCSP requests should not
  // be made for EV certs that chain to a builtin root.
  Services.prefs.setBoolPref("security.OCSP.require", false);
  info("Setting security.OCSP.require to false");
  let evroot = certFromFile("evroot");
  Services.prefs.setCharPref(
    "security.test.built_in_root_hash",
    evroot.sha256Fingerprint
  );
  info(
    "Setting security.test.built_in_root_hash to " + evroot.sha256Fingerprint
  );

  clearOCSPCache();
  ocspResponder = getOCSPResponder([]);
  await checkEVStatus(
    gCertDB,
    certFromFile("test-oid-path-ee"),
    Ci.nsIX509CertDB.verifyUsageTLSServer,
    gEVExpected
  );
  await stopOCSPResponder(ocspResponder);

  // When CRLite is enforced and OCSP is required, OCSP requests should be made
  // for DV certs.
  Services.prefs.setBoolPref("security.OCSP.require", true);
  info("Setting security.OCSP.require to true");
  clearOCSPCache();
  ocspResponder = getOCSPResponder(["non-ev-root-path-ee"]);
  await checkCertErrorGeneric(
    gCertDB,
    certFromFile("non-ev-root-path-ee"),
    PRErrorCodeSuccess,
    Ci.nsIX509CertDB.verifyUsageTLSServer
  );
  await stopOCSPResponder(ocspResponder);

  // When CRLite is enforced and OCSP is required, OCSP requests should be made
  // for EV certs.
  Services.prefs.setBoolPref("security.OCSP.require", true);
  info("Setting security.OCSP.require to true");
  Services.prefs.setCharPref(
    "security.test.built_in_root_hash",
    evroot.sha256Fingerprint
  );
  info(
    "Setting security.test.built_in_root_hash to " + evroot.sha256Fingerprint
  );

  clearOCSPCache();
  ocspResponder = getOCSPResponder(["test-oid-path-ee"]);
  await checkEVStatus(
    gCertDB,
    certFromFile("test-oid-path-ee"),
    Ci.nsIX509CertDB.verifyUsageTLSServer,
    gEVExpected
  );
  await stopOCSPResponder(ocspResponder);
}

// Tests that in ocspEVOnly mode, OCSP fetches are done for EV certs only.
async function testEVOnly() {
  Services.prefs.setIntPref("security.OCSP.enabled", 2);
  info("Setting security.OCSP.enabled to 2");

  // If a successful OCSP response is fetched, then an EV chain should verify
  // successfully and get EV status as well.
  clearOCSPCache();
  let ocspResponder = gEVExpected
    ? getOCSPResponder(["test-oid-path-ee"])
    : getFailingOCSPResponder();
  await checkEVStatus(
    gCertDB,
    certFromFile("test-oid-path-ee"),
    Ci.nsIX509CertDB.verifyUsageTLSServer,
    gEVExpected
  );
  await stopOCSPResponder(ocspResponder);

  // A DV chain should verify successfully even without doing OCSP fetches.
  clearOCSPCache();
  ocspResponder = getFailingOCSPResponder();
  await checkCertErrorGeneric(
    gCertDB,
    certFromFile("non-ev-root-path-ee"),
    PRErrorCodeSuccess,
    Ci.nsIX509CertDB.verifyUsageTLSServer
  );
  await stopOCSPResponder(ocspResponder);
}

add_task(async function () {
  registerCleanupFunction(() => {
    Services.prefs.clearUserPref("network.dns.localDomains");
    Services.prefs.clearUserPref("security.OCSP.enabled");
    Services.prefs.clearUserPref("security.OCSP.require");
    Services.prefs.clearUserPref("security.pki.crlite_mode");
    Services.prefs.clearUserPref("security.test.built_in_root_hash");
  });
  Services.prefs.setCharPref("network.dns.localDomains", "www.example.com");
  // Enable hard fail to ensure chains that should only succeed because they get
  // a good OCSP response do not succeed due to soft fail leniency.
  Services.prefs.setBoolPref("security.OCSP.require", true);

  loadCert("evroot", "CTu,,");
  loadCert("test-oid-path-int", ",,");
  loadCert("non-evroot-ca", "CTu,,");
  loadCert("non-ev-root-path-int", ",,");

  await testOff();
  await testOn();
  await testEVOnly();
  await testCRLiteEnforced();
});
