1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
|
// -*- 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, ",,", null);
}
function ensureSignatureVerificationFailure(certificatePath) {
let cert = constructCertFromFile(certificatePath);
checkCertErrorGeneric(certdb, cert, SEC_ERROR_BAD_SIGNATURE,
certificateUsageSSLServer);
}
function tamperWithSignatureAndEnsureVerificationFailure(certificatePath) {
let base64 = readAndTamperWithNthByte(certificatePath, BYTE_IN_SIGNATURE);
let cert = certdb.constructX509FromBase64(base64);
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, ",,", null);
}
function tamperWithSerialNumberAndEnsureVerificationFailure(certificatePath) {
let base64 = readAndTamperWithNthByte(certificatePath,
BYTE_IN_SERIAL_NUMBER);
let cert = certdb.constructX509FromBase64(base64);
checkCertErrorGeneric(certdb, cert, SEC_ERROR_BAD_SIGNATURE,
certificateUsageSSLServer);
}
function run_test() {
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");
ensureSignatureVerificationFailure("test_cert_signatures/ee-rsa.pem");
ensureSignatureVerificationFailure("test_cert_signatures/ee-secp384r1.pem");
// Tamper with the signatures on end-entity certificates and ensure that they
// do not validate successfully.
tamperWithSignatureAndEnsureVerificationFailure(
"test_cert_signatures/ee-rsa-direct.pem");
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");
ensureSignatureVerificationFailure("test_cert_signatures/ee-rsa.pem");
ensureSignatureVerificationFailure("test_cert_signatures/ee-secp384r1.pem");
// Tamper with the serial numbers of end-entity certificates and ensure that
// they do not validate successfully.
tamperWithSerialNumberAndEnsureVerificationFailure(
"test_cert_signatures/ee-rsa-direct.pem");
tamperWithSerialNumberAndEnsureVerificationFailure(
"test_cert_signatures/ee-secp384r1-direct.pem");
}
|