// -*- indent-tabs-mode: nil; js-indent-level: 2 -*-
// 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/.

"use strict";

// Tests that certificates cannot be tampered with without being detected.
// Tests a combination of cases: RSA signatures, ECDSA signatures, certificate
// chains where the intermediate has been tampered with, chains where the
// end-entity has been tampered, tampering of the signature, and tampering in
// the rest of the certificate.

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

// Reads a PEM-encoded certificate, modifies the nth byte (0-indexed), and
// returns the base64-encoded bytes of the certificate. Negative indices may be
// specified to modify a byte from the end of the certificate.
function readAndTamperWithNthByte(certificatePath, n) {
  let pem = readFile(do_get_file(certificatePath, false));
  let der = atob(pemToBase64(pem));
  if (n < 0) {
    // remember, n is negative at this point
    n = der.length + n;
  }
  let replacement = "\x22";
  if (der.charCodeAt(n) == replacement) {
    replacement = "\x23";
  }
  der = der.substring(0, n) + replacement + der.substring(n + 1);
  return btoa(der);
}

// The signature on certificates appears last. This should modify the contents
// of the signature such that it no longer validates correctly while still
// resulting in a structurally valid certificate.
const BYTE_IN_SIGNATURE = -8;
function addSignatureTamperedCertificate(certificatePath) {
  let base64 = readAndTamperWithNthByte(certificatePath, BYTE_IN_SIGNATURE);
  certdb.addCertFromBase64(base64, ",,");
}

function ensureSignatureVerificationFailure(certificatePath) {
  let cert = constructCertFromFile(certificatePath);
  return checkCertErrorGeneric(
    certdb,
    cert,
    SEC_ERROR_BAD_SIGNATURE,
    Ci.nsIX509CertDB.verifyUsageTLSServer
  );
}

function tamperWithSignatureAndEnsureVerificationFailure(certificatePath) {
  let base64 = readAndTamperWithNthByte(certificatePath, BYTE_IN_SIGNATURE);
  let cert = certdb.constructX509FromBase64(base64);
  return checkCertErrorGeneric(
    certdb,
    cert,
    SEC_ERROR_BAD_SIGNATURE,
    Ci.nsIX509CertDB.verifyUsageTLSServer
  );
}

// The beginning of a certificate looks like this (in hex, using DER):
// 30 XX XX XX [the XX encode length - there are probably 3 bytes here]
//    30 XX XX XX [length again]
//       A0 03
//          02 01
//             02
//       02 XX [length again - 1 byte as long as we're using pycert]
//          XX XX ... [serial number - 20 bytes as long as we're using pycert]
// Since we want to modify the serial number, we need to change something from
// byte 15 to byte 34 (0-indexed). If it turns out that the two length sections
// we assumed were 3 bytes are shorter (they can't be longer), modifying
// something from byte 15 to byte 30 will still get us what we want. Since the
// serial number is a DER INTEGER and because it must be positive, it's best to
// skip the first two bytes of the serial number so as to not run into any
// issues there. Thus byte 17 is a good byte to modify.
const BYTE_IN_SERIAL_NUMBER = 17;
function addSerialNumberTamperedCertificate(certificatePath) {
  let base64 = readAndTamperWithNthByte(certificatePath, BYTE_IN_SERIAL_NUMBER);
  certdb.addCertFromBase64(base64, ",,");
}

function tamperWithSerialNumberAndEnsureVerificationFailure(certificatePath) {
  let base64 = readAndTamperWithNthByte(certificatePath, BYTE_IN_SERIAL_NUMBER);
  let cert = certdb.constructX509FromBase64(base64);
  return checkCertErrorGeneric(
    certdb,
    cert,
    SEC_ERROR_BAD_SIGNATURE,
    Ci.nsIX509CertDB.verifyUsageTLSServer
  );
}

add_task(async function () {
  addCertFromFile(certdb, "test_cert_signatures/ca-rsa.pem", "CTu,,");
  addCertFromFile(certdb, "test_cert_signatures/ca-secp384r1.pem", "CTu,,");

  // Tamper with the signatures on intermediate certificates and ensure that
  // end-entity certificates issued by those intermediates do not validate
  // successfully.
  addSignatureTamperedCertificate("test_cert_signatures/int-rsa.pem");
  addSignatureTamperedCertificate("test_cert_signatures/int-secp384r1.pem");
  await ensureSignatureVerificationFailure("test_cert_signatures/ee-rsa.pem");
  await ensureSignatureVerificationFailure(
    "test_cert_signatures/ee-secp384r1.pem"
  );

  // Tamper with the signatures on end-entity certificates and ensure that they
  // do not validate successfully.
  await tamperWithSignatureAndEnsureVerificationFailure(
    "test_cert_signatures/ee-rsa-direct.pem"
  );
  await tamperWithSignatureAndEnsureVerificationFailure(
    "test_cert_signatures/ee-secp384r1-direct.pem"
  );

  // Tamper with the serial numbers of intermediate certificates and ensure
  // that end-entity certificates issued by those intermediates do not validate
  // successfully.
  addSerialNumberTamperedCertificate("test_cert_signatures/int-rsa.pem");
  addSerialNumberTamperedCertificate("test_cert_signatures/int-secp384r1.pem");
  await ensureSignatureVerificationFailure("test_cert_signatures/ee-rsa.pem");
  await ensureSignatureVerificationFailure(
    "test_cert_signatures/ee-secp384r1.pem"
  );

  // Tamper with the serial numbers of end-entity certificates and ensure that
  // they do not validate successfully.
  await tamperWithSerialNumberAndEnsureVerificationFailure(
    "test_cert_signatures/ee-rsa-direct.pem"
  );
  await tamperWithSerialNumberAndEnsureVerificationFailure(
    "test_cert_signatures/ee-secp384r1-direct.pem"
  );
});
