test_cert_signatures.js (5737B)
1 // -*- indent-tabs-mode: nil; js-indent-level: 2 -*- 2 // This Source Code Form is subject to the terms of the Mozilla Public 3 // License, v. 2.0. If a copy of the MPL was not distributed with this 4 // file, You can obtain one at http://mozilla.org/MPL/2.0/. 5 6 "use strict"; 7 8 // Tests that certificates cannot be tampered with without being detected. 9 // Tests a combination of cases: RSA signatures, ECDSA signatures, certificate 10 // chains where the intermediate has been tampered with, chains where the 11 // end-entity has been tampered, tampering of the signature, and tampering in 12 // the rest of the certificate. 13 14 do_get_profile(); // must be called before getting nsIX509CertDB 15 var certdb = Cc["@mozilla.org/security/x509certdb;1"].getService( 16 Ci.nsIX509CertDB 17 ); 18 19 // Reads a PEM-encoded certificate, modifies the nth byte (0-indexed), and 20 // returns the base64-encoded bytes of the certificate. Negative indices may be 21 // specified to modify a byte from the end of the certificate. 22 function readAndTamperWithNthByte(certificatePath, n) { 23 let pem = readFile(do_get_file(certificatePath, false)); 24 let der = atob(pemToBase64(pem)); 25 if (n < 0) { 26 // remember, n is negative at this point 27 n = der.length + n; 28 } 29 let replacement = "\x22"; 30 if (der.charCodeAt(n) == replacement) { 31 replacement = "\x23"; 32 } 33 der = der.substring(0, n) + replacement + der.substring(n + 1); 34 return btoa(der); 35 } 36 37 // The signature on certificates appears last. This should modify the contents 38 // of the signature such that it no longer validates correctly while still 39 // resulting in a structurally valid certificate. 40 const BYTE_IN_SIGNATURE = -8; 41 function addSignatureTamperedCertificate(certificatePath) { 42 let base64 = readAndTamperWithNthByte(certificatePath, BYTE_IN_SIGNATURE); 43 certdb.addCertFromBase64(base64, ",,"); 44 } 45 46 function ensureSignatureVerificationFailure(certificatePath) { 47 let cert = constructCertFromFile(certificatePath); 48 return checkCertErrorGeneric( 49 certdb, 50 cert, 51 SEC_ERROR_BAD_SIGNATURE, 52 Ci.nsIX509CertDB.verifyUsageTLSServer 53 ); 54 } 55 56 function tamperWithSignatureAndEnsureVerificationFailure(certificatePath) { 57 let base64 = readAndTamperWithNthByte(certificatePath, BYTE_IN_SIGNATURE); 58 let cert = certdb.constructX509FromBase64(base64); 59 return checkCertErrorGeneric( 60 certdb, 61 cert, 62 SEC_ERROR_BAD_SIGNATURE, 63 Ci.nsIX509CertDB.verifyUsageTLSServer 64 ); 65 } 66 67 // The beginning of a certificate looks like this (in hex, using DER): 68 // 30 XX XX XX [the XX encode length - there are probably 3 bytes here] 69 // 30 XX XX XX [length again] 70 // A0 03 71 // 02 01 72 // 02 73 // 02 XX [length again - 1 byte as long as we're using pycert] 74 // XX XX ... [serial number - 20 bytes as long as we're using pycert] 75 // Since we want to modify the serial number, we need to change something from 76 // byte 15 to byte 34 (0-indexed). If it turns out that the two length sections 77 // we assumed were 3 bytes are shorter (they can't be longer), modifying 78 // something from byte 15 to byte 30 will still get us what we want. Since the 79 // serial number is a DER INTEGER and because it must be positive, it's best to 80 // skip the first two bytes of the serial number so as to not run into any 81 // issues there. Thus byte 17 is a good byte to modify. 82 const BYTE_IN_SERIAL_NUMBER = 17; 83 function addSerialNumberTamperedCertificate(certificatePath) { 84 let base64 = readAndTamperWithNthByte(certificatePath, BYTE_IN_SERIAL_NUMBER); 85 certdb.addCertFromBase64(base64, ",,"); 86 } 87 88 function tamperWithSerialNumberAndEnsureVerificationFailure(certificatePath) { 89 let base64 = readAndTamperWithNthByte(certificatePath, BYTE_IN_SERIAL_NUMBER); 90 let cert = certdb.constructX509FromBase64(base64); 91 return checkCertErrorGeneric( 92 certdb, 93 cert, 94 SEC_ERROR_BAD_SIGNATURE, 95 Ci.nsIX509CertDB.verifyUsageTLSServer 96 ); 97 } 98 99 add_task(async function () { 100 addCertFromFile(certdb, "test_cert_signatures/ca-rsa.pem", "CTu,,"); 101 addCertFromFile(certdb, "test_cert_signatures/ca-secp384r1.pem", "CTu,,"); 102 103 // Tamper with the signatures on intermediate certificates and ensure that 104 // end-entity certificates issued by those intermediates do not validate 105 // successfully. 106 addSignatureTamperedCertificate("test_cert_signatures/int-rsa.pem"); 107 addSignatureTamperedCertificate("test_cert_signatures/int-secp384r1.pem"); 108 await ensureSignatureVerificationFailure("test_cert_signatures/ee-rsa.pem"); 109 await ensureSignatureVerificationFailure( 110 "test_cert_signatures/ee-secp384r1.pem" 111 ); 112 113 // Tamper with the signatures on end-entity certificates and ensure that they 114 // do not validate successfully. 115 await tamperWithSignatureAndEnsureVerificationFailure( 116 "test_cert_signatures/ee-rsa-direct.pem" 117 ); 118 await tamperWithSignatureAndEnsureVerificationFailure( 119 "test_cert_signatures/ee-secp384r1-direct.pem" 120 ); 121 122 // Tamper with the serial numbers of intermediate certificates and ensure 123 // that end-entity certificates issued by those intermediates do not validate 124 // successfully. 125 addSerialNumberTamperedCertificate("test_cert_signatures/int-rsa.pem"); 126 addSerialNumberTamperedCertificate("test_cert_signatures/int-secp384r1.pem"); 127 await ensureSignatureVerificationFailure("test_cert_signatures/ee-rsa.pem"); 128 await ensureSignatureVerificationFailure( 129 "test_cert_signatures/ee-secp384r1.pem" 130 ); 131 132 // Tamper with the serial numbers of end-entity certificates and ensure that 133 // they do not validate successfully. 134 await tamperWithSerialNumberAndEnsureVerificationFailure( 135 "test_cert_signatures/ee-rsa-direct.pem" 136 ); 137 await tamperWithSerialNumberAndEnsureVerificationFailure( 138 "test_cert_signatures/ee-secp384r1-direct.pem" 139 ); 140 });