test_qwacs.js (16432B)
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 do_get_profile(); // must be called before getting nsIX509CertDB 9 const certdb = Cc["@mozilla.org/security/x509certdb;1"].getService( 10 Ci.nsIX509CertDB 11 ); 12 13 const { QWACs } = ChromeUtils.importESModule( 14 "resource://gre/modules/psm/QWACs.sys.mjs" 15 ); 16 17 async function verify_1_qwacs(filename, expectSuccess, extraCertNames = []) { 18 let cert = constructCertFromFile(filename); 19 let result = await certdb.asyncVerifyQWAC( 20 Ci.nsIX509CertDB.OneQWAC, 21 cert, 22 "example.com", 23 extraCertNames.map(filename => constructCertFromFile(filename)) 24 ); 25 equal( 26 result, 27 expectSuccess, 28 `${filename} ${expectSuccess ? "should" : "should not"} verify as 1-QWAC` 29 ); 30 } 31 32 add_task(async function test_verify_1_qwacs() { 33 Services.prefs.clearUserPref("security.qwacs.enable_test_trust_anchors"); 34 // By default, the QWACs test trust anchors are not used. 35 await verify_1_qwacs("test_qwacs/1-qwac.pem", false); 36 await verify_1_qwacs("test_qwacs/1-qwac-qevcpw.pem", false); 37 38 Services.prefs.setBoolPref("security.qwacs.enable_test_trust_anchors", true); 39 40 await verify_1_qwacs("test_qwacs/1-qwac.pem", true); 41 await verify_1_qwacs("test_qwacs/1-qwac-qevcpw.pem", true); 42 43 await verify_1_qwacs("test_qwacs/1-qwac-other-optional-qcs.pem", true); 44 45 // One or more intermediates may be necessary for path building. 46 await verify_1_qwacs("test_qwacs/1-qwac-via-intermediate.pem", false); 47 await verify_1_qwacs("test_qwacs/1-qwac-via-intermediate.pem", true, [ 48 "test_qwacs/test-int.pem", 49 ]); 50 51 await verify_1_qwacs("test_qwacs/empty-qc-type-statement.pem", false); 52 await verify_1_qwacs("test_qwacs/missing-qc-type-statement.pem", false); 53 await verify_1_qwacs("test_qwacs/missing-qcs-compliance.pem", false); 54 await verify_1_qwacs("test_qwacs/wrong-qc-type.pem", false); 55 await verify_1_qwacs("test_qwacs/no-1-qwac-policies.pem", false); 56 await verify_1_qwacs("test_qwacs/no-policies.pem", false); 57 await verify_1_qwacs("test_qwacs/2-qwac.pem", false); 58 }); 59 60 async function verify_2_qwacs( 61 filename, 62 expectSuccess, 63 hostname = "example.com" 64 ) { 65 let cert = constructCertFromFile(filename); 66 let result = await certdb.asyncVerifyQWAC( 67 Ci.nsIX509CertDB.TwoQWAC, 68 cert, 69 hostname, 70 [] 71 ); 72 equal( 73 result, 74 expectSuccess, 75 `${filename} ${expectSuccess ? "should" : "should not"} verify as 2-QWAC` 76 ); 77 } 78 79 add_task(async function test_verify_2_qwacs() { 80 Services.prefs.clearUserPref("security.qwacs.enable_test_trust_anchors"); 81 // By default, the QWACs test trust anchors are not used. 82 await verify_2_qwacs("test_qwacs/2-qwac.pem", false); 83 84 Services.prefs.setBoolPref("security.qwacs.enable_test_trust_anchors", true); 85 86 await verify_2_qwacs("test_qwacs/2-qwac.pem", true); 87 88 await verify_2_qwacs("test_qwacs/1-qwac.pem", false); 89 await verify_2_qwacs("test_qwacs/2-qwac-no-eku.pem", false); 90 await verify_2_qwacs("test_qwacs/2-qwac-tls-server-eku.pem", false); 91 await verify_2_qwacs("test_qwacs/2-qwac-multiple-key-purpose-eku.pem", false); 92 await verify_2_qwacs("test_qwacs/2-qwac.pem", false, "example.org"); 93 }); 94 95 // Produces base64url(hash(base64url(certificate DER))) 96 async function certificateHash(certificate, hashAlg) { 97 let hash = await crypto.subtle.digest( 98 hashAlg.replace("S", "SHA-"), 99 new Uint8Array( 100 stringToArray( 101 QWACs.toBase64URLEncoding(arrayToString(certificate.getRawDER())) 102 ) 103 ) 104 ); 105 return QWACs.toBase64URLEncoding(arrayToString(new Uint8Array(hash))); 106 } 107 108 const kTLSCertificateBindingEE = constructCertFromFile("test_qwacs/2-qwac.pem"); 109 110 const kTLSCertificateBindingHeader = { 111 alg: "", 112 cty: "TLS-Certificate-Binding-v1", 113 // RFC 7515 Section 4.1.6: "Each string in the array is a base64-encoded 114 // (Section 4 of [RFC4648] -- not base64url-encoded) DER [ITU.X690.2008] PKIX 115 // certificate value." 116 x5c: [], 117 sigD: { 118 mId: "http://uri.etsi.org/19182/ObjectIdByURIHash", 119 pars: [], 120 hashM: "", 121 hashV: [], 122 }, 123 }; 124 125 async function makeBindingHeader( 126 certificateChain, 127 certificatesToBind, 128 signingAlg, 129 hashAlg 130 ) { 131 let header = structuredClone(kTLSCertificateBindingHeader); 132 header.alg = signingAlg; 133 header.x5c = certificateChain.map(c => btoa(arrayToString(c.getRawDER()))); 134 header.sigD.hashM = hashAlg; 135 for (let toBind of certificatesToBind) { 136 header.sigD.pars.push(""); 137 header.sigD.hashV.push(await certificateHash(toBind, hashAlg)); 138 } 139 return header; 140 } 141 142 add_task(async function test_validate_tls_certificate_binding_header() { 143 let serverCertificate = constructCertFromFile("bad_certs/default-ee.pem"); 144 let testHeader = await makeBindingHeader( 145 [kTLSCertificateBindingEE], 146 [serverCertificate], 147 "RS256", 148 "S256" 149 ); 150 let validatedHeader = QWACs.validateTLSCertificateBindingHeader(testHeader); 151 ok(validatedHeader, "header should validate successfully"); 152 deepEqual(validatedHeader.algorithm, { 153 name: "RSASSA-PKCS1-v1_5", 154 hash: "SHA-256", 155 }); 156 equal(validatedHeader.certificates.length, 1); 157 equal(validatedHeader.hashAlg, "SHA-256"); 158 equal(validatedHeader.hashes.length, 1); 159 160 let headerWithExtraKey = structuredClone(testHeader); 161 headerWithExtraKey.extra = "foo"; 162 ok( 163 !QWACs.validateTLSCertificateBindingHeader(headerWithExtraKey), 164 "header with extra key should not validate" 165 ); 166 167 let headerWithExtraSigDKey = structuredClone(testHeader); 168 headerWithExtraSigDKey.sigD.additional = "bar"; 169 ok( 170 !QWACs.validateTLSCertificateBindingHeader(headerWithExtraSigDKey), 171 "header with extra key in sigD should not parse" 172 ); 173 174 let headerWithWrongCty = structuredClone(testHeader); 175 headerWithWrongCty.cty = "TLS-Certificate-Binding-v2"; 176 ok( 177 !QWACs.validateTLSCertificateBindingHeader(headerWithWrongCty), 178 "header with wrong cty should not parse" 179 ); 180 181 let headerWithWrongMId = structuredClone(testHeader); 182 headerWithWrongMId.sigD.mId = "http://example.org"; 183 ok( 184 !QWACs.validateTLSCertificateBindingHeader(headerWithWrongMId), 185 "header with wrong sigD.mId should not parse" 186 ); 187 188 let headerWithTooManyPars = structuredClone(testHeader); 189 headerWithTooManyPars.sigD.pars.push(""); 190 ok( 191 !QWACs.validateTLSCertificateBindingHeader(headerWithTooManyPars), 192 "header with too many sigD.pars elements should not parse" 193 ); 194 195 let headerWithInvalidHashV = structuredClone(testHeader); 196 headerWithInvalidHashV.sigD.hashV[0] = 1234; 197 ok( 198 !QWACs.validateTLSCertificateBindingHeader(headerWithInvalidHashV), 199 "header with invalid sigD.hashV should not parse" 200 ); 201 202 let headerWithTooManyHashV = structuredClone(testHeader); 203 headerWithTooManyHashV.sigD.hashV.push(headerWithTooManyHashV.sigD.hashV[0]); 204 ok( 205 !QWACs.validateTLSCertificateBindingHeader(headerWithTooManyHashV), 206 "header with too many sigD.hashV elements should not parse" 207 ); 208 209 let headerWithEmptyX5c = structuredClone(testHeader); 210 headerWithEmptyX5c.x5c = []; 211 ok( 212 !QWACs.validateTLSCertificateBindingHeader(headerWithEmptyX5c), 213 "header with empty x5c should not parse" 214 ); 215 216 let headerWithUnsupportedAlg = structuredClone(testHeader); 217 headerWithUnsupportedAlg.alg = "RS384"; 218 ok( 219 !QWACs.validateTLSCertificateBindingHeader(headerWithUnsupportedAlg), 220 "header with unsupported alg should not parse" 221 ); 222 223 let headerWithUnsupportedHashM = structuredClone(testHeader); 224 headerWithUnsupportedHashM.sigD.hashM = "S224"; 225 ok( 226 !QWACs.validateTLSCertificateBindingHeader(headerWithUnsupportedHashM), 227 "header with unsupported sigD.hashM should not parse" 228 ); 229 230 let headerWithOptionalHeaders = structuredClone(testHeader); 231 headerWithOptionalHeaders.kid = "optional kid"; 232 headerWithOptionalHeaders.iat = "optional iat"; 233 ok( 234 QWACs.validateTLSCertificateBindingHeader(headerWithOptionalHeaders), 235 "header with optional headers should parse" 236 ); 237 238 let headerWithX5tS256Match = structuredClone(testHeader); 239 let signingCertificateHash = await crypto.subtle.digest( 240 "SHA-256", 241 new Uint8Array(kTLSCertificateBindingEE.getRawDER()) 242 ); 243 headerWithX5tS256Match["x5t#S256"] = QWACs.toBase64URLEncoding( 244 arrayToString(new Uint8Array(signingCertificateHash)) 245 ); 246 ok( 247 QWACs.validateTLSCertificateBindingHeader(headerWithX5tS256Match), 248 "header with matching x5t#S256 should parse" 249 ); 250 251 let headerWithX5tS256Mismatch = structuredClone(testHeader); 252 headerWithX5tS256Mismatch["x5t#S256"] = 253 "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"; 254 ok( 255 !QWACs.validateTLSCertificateBindingHeader(headerWithX5tS256Mismatch), 256 "header with x5t#S256 mismatch should not parse" 257 ); 258 259 let headerWithBadExp = structuredClone(testHeader); 260 headerWithBadExp.exp = "not a number"; 261 ok( 262 !QWACs.validateTLSCertificateBindingHeader(headerWithBadExp), 263 "header with bad expiration time should not parse" 264 ); 265 266 let expiredHeader = structuredClone(testHeader); 267 expiredHeader.exp = "946684800"; 268 ok( 269 !QWACs.validateTLSCertificateBindingHeader(expiredHeader), 270 "expired header should not parse" 271 ); 272 273 let unexpiredHeader = structuredClone(testHeader); 274 unexpiredHeader.exp = "4102444800"; 275 ok( 276 QWACs.validateTLSCertificateBindingHeader(unexpiredHeader), 277 "unexpired header should parse" 278 ); 279 }); 280 281 async function validate_tls_certificate_binding_header_with_algorithms( 282 signatureAlg, 283 hashAlg, 284 expectedSignatureAlg, 285 expectedHashAlg 286 ) { 287 let serverCertificate = constructCertFromFile("bad_certs/default-ee.pem"); 288 let testHeader = await makeBindingHeader( 289 [kTLSCertificateBindingEE], 290 [serverCertificate], 291 signatureAlg, 292 hashAlg 293 ); 294 let validatedHeader = QWACs.validateTLSCertificateBindingHeader(testHeader); 295 ok(validatedHeader, "header should validate successfully"); 296 deepEqual(validatedHeader.algorithm, expectedSignatureAlg); 297 equal(validatedHeader.certificates.length, 1); 298 equal(validatedHeader.hashAlg, expectedHashAlg); 299 equal(validatedHeader.hashes.length, 1); 300 } 301 302 add_task( 303 async function test_validate_tls_certificate_binding_header_with_algorithms() { 304 let options = [ 305 { 306 signatureAlg: "RS256", 307 hashAlg: "S256", 308 expectedSignatureAlg: { name: "RSASSA-PKCS1-v1_5", hash: "SHA-256" }, 309 expectedHashAlg: "SHA-256", 310 }, 311 { 312 signatureAlg: "PS256", 313 hashAlg: "S384", 314 expectedSignatureAlg: { 315 name: "RSA-PSS", 316 saltLength: 32, 317 hash: "SHA-256", 318 }, 319 expectedHashAlg: "SHA-384", 320 }, 321 { 322 signatureAlg: "ES256", 323 hashAlg: "S512", 324 expectedSignatureAlg: { 325 name: "ECDSA", 326 namedCurve: "P-256", 327 hash: "SHA-256", 328 }, 329 expectedHashAlg: "SHA-512", 330 }, 331 ]; 332 for (let option of options) { 333 await validate_tls_certificate_binding_header_with_algorithms( 334 option.signatureAlg, 335 option.hashAlg, 336 option.expectedSignatureAlg, 337 option.expectedHashAlg 338 ); 339 } 340 } 341 ); 342 343 async function sign(privateKeyInfo, algorithm, header) { 344 let toSign = new Uint8Array(stringToArray(header + ".")); 345 let key = await crypto.subtle.importKey( 346 "pkcs8", 347 privateKeyInfo, 348 algorithm, 349 true, 350 ["sign"] 351 ); 352 let signature = await crypto.subtle.sign(algorithm, key, toSign); 353 return ( 354 header + 355 ".." + 356 QWACs.toBase64URLEncoding(arrayToString(new Uint8Array(signature))) 357 ); 358 } 359 360 async function signTLSCertificateBinding(header, key, algorithm) { 361 return sign( 362 key, 363 algorithm, 364 QWACs.toBase64URLEncoding(JSON.stringify(header)) 365 ); 366 } 367 368 async function verify_tls_certificate_binding_signature( 369 headerSignatureAlgorithm, 370 headerHashAlgorithm, 371 signatureAlgorithm, 372 signingKeyFilename, 373 bindingCertificateFilename, 374 serverCertificateFilename, 375 presentedServerCertificateFilename, 376 expectSuccess, 377 expectedCertificateSubject 378 ) { 379 let signingKey = new Uint8Array( 380 stringToArray( 381 QWACs.fromBase64URLEncoding( 382 pemToBase64(readFile(do_get_file(signingKeyFilename, false))) 383 ) 384 ) 385 ); 386 let bindingCertificate = constructCertFromFile(bindingCertificateFilename); 387 let serverCertificate = constructCertFromFile(serverCertificateFilename); 388 let testHeader = await makeBindingHeader( 389 [bindingCertificate], 390 [serverCertificate], 391 headerSignatureAlgorithm, 392 headerHashAlgorithm 393 ); 394 let binding = await signTLSCertificateBinding( 395 testHeader, 396 signingKey, 397 signatureAlgorithm 398 ); 399 let presentedServerCertificate = constructCertFromFile( 400 presentedServerCertificateFilename 401 ); 402 let qwac = await QWACs.verifyTLSCertificateBinding( 403 binding, 404 presentedServerCertificate, 405 "example.com" 406 ); 407 equal( 408 !!qwac, 409 expectSuccess, 410 `TLS certificate binding ${expectSuccess ? "should" : "should not"} verify correctly` 411 ); 412 if (expectSuccess) { 413 equal( 414 qwac.commonName, 415 expectedCertificateSubject, 416 "Verification should return the expected certificate" 417 ); 418 } 419 } 420 421 add_task(async function test_verify_tls_certificate_binding_signatures() { 422 let options = [ 423 { 424 headerSignatureAlgorithm: "RS256", 425 headerHashAlgorithm: "S512", 426 signatureAlgorithm: { name: "RSASSA-PKCS1-v1_5", hash: "SHA-256" }, 427 signingKeyFilename: "bad_certs/default-ee.key", 428 bindingCertificateFilename: "test_qwacs/2-qwac.pem", 429 serverCertificateFilename: "bad_certs/default-ee.pem", 430 presentedServerCertificateFilename: "bad_certs/default-ee.pem", 431 expectSuccess: true, 432 expectedCertificateSubject: "2-QWAC", 433 }, 434 // presented server certificate / bound server certificate mismatch 435 { 436 headerSignatureAlgorithm: "RS256", 437 headerHashAlgorithm: "S512", 438 signatureAlgorithm: { name: "RSASSA-PKCS1-v1_5", hash: "SHA-256" }, 439 signingKeyFilename: "bad_certs/default-ee.key", 440 bindingCertificateFilename: "test_qwacs/2-qwac.pem", 441 serverCertificateFilename: "bad_certs/default-ee.pem", 442 presentedServerCertificateFilename: 443 "bad_certs/ee-from-missing-intermediate.pem", 444 expectSuccess: false, 445 expectedCertificateSubject: null, 446 }, 447 { 448 headerSignatureAlgorithm: "PS256", 449 headerHashAlgorithm: "S256", 450 signatureAlgorithm: { 451 name: "RSA-PSS", 452 hash: "SHA-256", 453 saltLength: 32, 454 }, 455 signingKeyFilename: "bad_certs/default-ee.key", 456 bindingCertificateFilename: "test_qwacs/2-qwac.pem", 457 serverCertificateFilename: "bad_certs/default-ee.pem", 458 presentedServerCertificateFilename: "bad_certs/default-ee.pem", 459 expectSuccess: true, 460 expectedCertificateSubject: "2-QWAC", 461 }, 462 { 463 headerSignatureAlgorithm: "ES256", 464 headerHashAlgorithm: "S384", 465 signatureAlgorithm: { 466 name: "ECDSA", 467 namedCurve: "P-256", 468 hash: "SHA-256", 469 }, 470 signingKeyFilename: "test_qwacs/secp256r1.key", 471 bindingCertificateFilename: "test_qwacs/2-qwac-ec.pem", 472 serverCertificateFilename: "bad_certs/default-ee.pem", 473 presentedServerCertificateFilename: "bad_certs/default-ee.pem", 474 expectSuccess: true, 475 expectedCertificateSubject: "2-QWAC with EC key", 476 }, 477 // header signature algorithm / actual signature algorithm mismatch 478 { 479 headerSignatureAlgorithm: "RS256", 480 headerHashAlgorithm: "S384", 481 signatureAlgorithm: { 482 name: "ECDSA", 483 namedCurve: "P-256", 484 hash: "SHA-256", 485 }, 486 signingKeyFilename: "test_qwacs/secp256r1.key", 487 bindingCertificateFilename: "test_qwacs/2-qwac-ec.pem", 488 serverCertificateFilename: "bad_certs/default-ee.pem", 489 presentedServerCertificateFilename: "bad_certs/default-ee.pem", 490 expectSuccess: false, 491 expectedCertificateSubject: null, 492 }, 493 ]; 494 495 for (let option of options) { 496 await verify_tls_certificate_binding_signature( 497 option.headerSignatureAlgorithm, 498 option.headerHashAlgorithm, 499 option.signatureAlgorithm, 500 option.signingKeyFilename, 501 option.bindingCertificateFilename, 502 option.serverCertificateFilename, 503 option.presentedServerCertificateFilename, 504 option.expectSuccess, 505 option.expectedCertificateSubject 506 ); 507 } 508 });