summaryrefslogtreecommitdiffstats
path: root/security/manager/ssl/tests/unit/test_cert_dbKey.js
blob: 3ff36f905cf70c4c27edb8779e2d5fa6dbf4cb65 (plain)
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
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
// -*- Mode: javascript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 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";

// This test tests that the nsIX509Cert.dbKey and nsIX509CertDB.findCertByDBKey
// APIs work as expected. That is, getting a certificate's dbKey and using it
// in findCertByDBKey should return the same certificate. Also, for backwards
// compatibility, findCertByDBKey should ignore any whitespace in its input
// (even though now nsIX509Cert.dbKey will never have whitespace in it).

function hexStringToBytes(hex) {
  let bytes = [];
  for (let hexByteStr of hex.split(":")) {
    bytes.push(parseInt(hexByteStr, 16));
  }
  return bytes;
}

function encodeCommonNameAsBytes(commonName) {
  // The encoding will look something like this (in hex):
  // 30 (SEQUENCE) <length of contents>
  //    31 (SET) <length of contents>
  //       30 (SEQUENCE) <length of contents>
  //          06 (OID) 03 (length)
  //             55 04 03 (id-at-commonName)
  //          0C (UTF8String) <length of common name>
  //             <common name bytes>
  // To make things simple, it would be nice to have the length of each
  // component be less than 128 bytes (so we can have single-byte lengths).
  // For this to hold, the maximum length of the contents of the outermost
  // SEQUENCE must be 127. Everything not in the contents of the common name
  // will take up 11 bytes, so the value of the common name itself can be at
  // most 116 bytes.
  ok(
    commonName.length <= 116,
    "test assumption: common name can't be longer than 116 bytes (makes " +
      "DER encoding easier)"
  );
  let commonNameOIDBytes = [0x06, 0x03, 0x55, 0x04, 0x03];
  let commonNameBytes = [0x0c, commonName.length];
  for (let i = 0; i < commonName.length; i++) {
    commonNameBytes.push(commonName.charCodeAt(i));
  }
  let bytes = commonNameOIDBytes.concat(commonNameBytes);
  bytes.unshift(bytes.length);
  bytes.unshift(0x30); // SEQUENCE
  bytes.unshift(bytes.length);
  bytes.unshift(0x31); // SET
  bytes.unshift(bytes.length);
  bytes.unshift(0x30); // SEQUENCE
  return bytes;
}

function testInvalidDBKey(certDB, dbKey) {
  throws(
    () => certDB.findCertByDBKey(dbKey),
    /NS_ERROR_ILLEGAL_INPUT/,
    `findCertByDBKey(${dbKey}) should raise NS_ERROR_ILLEGAL_INPUT`
  );
}

function testDBKeyForNonexistentCert(certDB, dbKey) {
  let cert = certDB.findCertByDBKey(dbKey);
  ok(!cert, "shouldn't find cert for given dbKey");
}

function byteArrayToByteString(bytes) {
  let byteString = "";
  for (let b of bytes) {
    byteString += String.fromCharCode(b);
  }
  return byteString;
}

function run_test() {
  do_get_profile();
  let certDB = Cc["@mozilla.org/security/x509certdb;1"].getService(
    Ci.nsIX509CertDB
  );
  let cert = constructCertFromFile("bad_certs/test-ca.pem");
  equal(
    cert.issuerName,
    "CN=" + cert.issuerCommonName,
    "test assumption: this certificate's issuer distinguished name " +
      "consists only of a common name"
  );
  let issuerBytes = encodeCommonNameAsBytes(cert.issuerCommonName);
  ok(
    issuerBytes.length < 256,
    "test assumption: length of encoded issuer is less than 256 bytes"
  );
  let serialNumberBytes = hexStringToBytes(cert.serialNumber);
  ok(
    serialNumberBytes.length < 256,
    "test assumption: length of encoded serial number is less than 256 bytes"
  );
  let dbKeyHeader = [
    0,
    0,
    0,
    0,
    0,
    0,
    0,
    0,
    0,
    0,
    0,
    serialNumberBytes.length,
    0,
    0,
    0,
    issuerBytes.length,
  ];
  let expectedDbKeyBytes = dbKeyHeader.concat(serialNumberBytes, issuerBytes);
  let expectedDbKey = btoa(byteArrayToByteString(expectedDbKeyBytes));
  equal(
    cert.dbKey,
    expectedDbKey,
    "actual and expected dbKey values should match"
  );

  let certFromDbKey = certDB.findCertByDBKey(expectedDbKey);
  ok(
    areCertsEqual(certFromDbKey, cert),
    "nsIX509CertDB.findCertByDBKey should find the right certificate"
  );

  ok(
    expectedDbKey.length > 64,
    "test assumption: dbKey should be longer than 64 characters"
  );
  let expectedDbKeyWithCRLF = expectedDbKey.replace(/(.{64})/, "$1\r\n");
  ok(
    expectedDbKeyWithCRLF.indexOf("\r\n") == 64,
    "test self-check: adding CRLF to dbKey should succeed"
  );
  certFromDbKey = certDB.findCertByDBKey(expectedDbKeyWithCRLF);
  ok(
    areCertsEqual(certFromDbKey, cert),
    "nsIX509CertDB.findCertByDBKey should work with dbKey with CRLF"
  );

  let expectedDbKeyWithSpaces = expectedDbKey.replace(/(.{64})/, "$1  ");
  ok(
    expectedDbKeyWithSpaces.indexOf("  ") == 64,
    "test self-check: adding spaces to dbKey should succeed"
  );
  certFromDbKey = certDB.findCertByDBKey(expectedDbKeyWithSpaces);
  ok(
    areCertsEqual(certFromDbKey, cert),
    "nsIX509CertDB.findCertByDBKey should work with dbKey with spaces"
  );

  // Test some invalid dbKey values.
  testInvalidDBKey(certDB, "AAAA"); // Not long enough.
  // No header.
  testInvalidDBKey(
    certDB,
    btoa(
      byteArrayToByteString(
        [0, 0, 0, serialNumberBytes.length, 0, 0, 0, issuerBytes.length].concat(
          serialNumberBytes,
          issuerBytes
        )
      )
    )
  );
  testInvalidDBKey(
    certDB,
    btoa(
      byteArrayToByteString([
        0,
        0,
        0,
        0,
        0,
        0,
        0,
        0,
        255,
        255,
        255,
        255, // serial number length is way too long
        255,
        255,
        255,
        255, // issuer length is way too long
        0,
        0,
        0,
        0,
      ])
    )
  );
  // Truncated issuer.
  testInvalidDBKey(
    certDB,
    btoa(
      byteArrayToByteString([
        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 10, 1, 1, 2, 3,
      ])
    )
  );
  // Issuer doesn't decode to valid common name.
  testDBKeyForNonexistentCert(
    certDB,
    btoa(
      byteArrayToByteString([
        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 3, 1, 1, 2, 3,
      ])
    )
  );

  // zero-length serial number and issuer -> no such certificate
  testDBKeyForNonexistentCert(
    certDB,
    btoa(
      byteArrayToByteString([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0])
    )
  );
}