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
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
|
// -*- 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"
);
});
|