// -*- 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, certificateUsageSSLServer ); } function tamperWithSignatureAndEnsureVerificationFailure(certificatePath) { let base64 = readAndTamperWithNthByte(certificatePath, BYTE_IN_SIGNATURE); let cert = certdb.constructX509FromBase64(base64); return checkCertErrorGeneric( certdb, cert, SEC_ERROR_BAD_SIGNATURE, certificateUsageSSLServer ); } // 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, certificateUsageSSLServer ); } 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" ); });