// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.

// Tests that starting a profile with a preexisting CRLite filter and stash
// works correctly.

"use strict";

const CHECK_AT_TIME = new Date("2020-01-01T00:00:00Z").getTime() / 1000;

async function test_crlite_preexisting(ctMode) {
  Services.prefs.setIntPref(
    "security.pki.certificate_transparency.mode",
    ctMode
  );

  let certdb = Cc["@mozilla.org/security/x509certdb;1"].getService(
    Ci.nsIX509CertDB
  );

  let validCert = constructCertFromFile(
    "test_crlite_filters/valid.example.com.pem"
  );
  await checkCertErrorGenericAtTime(
    certdb,
    validCert,
    PRErrorCodeSuccess,
    Ci.nsIX509CertDB.verifyUsageTLSServer,
    CHECK_AT_TIME,
    false,
    "valid.example.com",
    0
  );

  let revokedCert = constructCertFromFile(
    "test_crlite_filters/revoked.example.com.pem"
  );
  await checkCertErrorGenericAtTime(
    certdb,
    revokedCert,
    SEC_ERROR_REVOKED_CERTIFICATE,
    Ci.nsIX509CertDB.verifyUsageTLSServer,
    CHECK_AT_TIME,
    false,
    "revoked.example.com",
    0
  );

  let revokedInDeltaCert = constructCertFromFile(
    "test_crlite_filters/revoked-in-delta.example.com.pem"
  );
  await checkCertErrorGenericAtTime(
    certdb,
    revokedInDeltaCert,
    SEC_ERROR_REVOKED_CERTIFICATE,
    Ci.nsIX509CertDB.verifyUsageTLSServer,
    CHECK_AT_TIME,
    false,
    "revoked-in-delta.example.com",
    0
  );

  // This certificate has no embedded SCTs, but it should be considered revoked
  // if the appropriate SCT is side-loaded.
  let revokedNoSctCert = constructCertFromFile(
    "test_crlite_filters/revoked-no-sct.example.com.pem"
  );

  let sctFile = do_get_file(
    "test_crlite_filters/revoked-no-sct.example.com.sct",
    false
  );
  let sctBytes = readBinaryFile(sctFile);
  let sctList = new Uint8Array(2 + 2 + sctBytes.length);
  sctList[0] = (2 + sctBytes.length) / 256;
  sctList[1] = (2 + sctBytes.length) % 256;
  sctList[2] = sctBytes.length / 256;
  sctList[3] = sctBytes.length % 256;
  sctList.set(sctBytes, 4);

  await checkCertErrorGenericAtTime(
    certdb,
    revokedNoSctCert,
    PRErrorCodeSuccess,
    Ci.nsIX509CertDB.verifyUsageTLSServer,
    CHECK_AT_TIME,
    false,
    "revoked-no-sct.example.com",
    0,
    [] // no side-loaded SCTs
  );

  await checkCertErrorGenericAtTime(
    certdb,
    revokedNoSctCert,
    ctMode == CT_MODE_DISABLE
      ? PRErrorCodeSuccess
      : SEC_ERROR_REVOKED_CERTIFICATE,
    Ci.nsIX509CertDB.verifyUsageTLSServer,
    CHECK_AT_TIME,
    false,
    "revoked-no-sct.example.com",
    0,
    sctList
  );
}

add_task(async function () {
  Services.prefs.setIntPref(
    "security.pki.crlite_mode",
    CRLiteModeEnforcePrefValue
  );

  let securityStateDirectory = do_get_profile();
  securityStateDirectory.append("security_state");

  // For simplicity, re-use the filters from test_crlite_filters.js.
  do_get_file("test_crlite_filters/20200101-0-filter").copyTo(
    securityStateDirectory,
    "crlite.filter"
  );

  do_get_file("test_crlite_filters/20200101-1-filter.delta").copyTo(
    securityStateDirectory,
    "20201017-1-filter.delta"
  );

  let certStorage = Cc["@mozilla.org/security/certstorage;1"].getService(
    Ci.nsICertStorage
  );

  let certdb = Cc["@mozilla.org/security/x509certdb;1"].getService(
    Ci.nsIX509CertDB
  );

  // These need to be available for path building.
  let ca = addCertFromFile(certdb, "test_crlite_filters/ca.pem", "C,C,");
  ok(ca, "ca certificate should decode successfully");

  let issuerCert = constructCertFromFile("test_crlite_filters/int.pem");
  ok(issuerCert, "issuer certificate should decode successfully");

  // Mark CRLite filter as fresh
  await new Promise(resolve => {
    certStorage.testNoteCRLiteUpdateTime((rv, _) => {
      Assert.equal(rv, Cr.NS_OK, "marked filter as fresh");
      resolve();
    });
  });

  info(`testing with CT disabled`);
  await test_crlite_preexisting(CT_MODE_DISABLE);

  info(`testing with CT in telemetry only mode`);
  await test_crlite_preexisting(CT_MODE_COLLECT_TELEMETRY);

  info(`testing with CT enabled`);
  await test_crlite_preexisting(CT_MODE_ENFORCE);
});
