browser_aboutCertError.js (43740B)
1 /* Any copyright is dedicated to the Public Domain. 2 * http://creativecommons.org/publicdomain/zero/1.0/ */ 3 4 "use strict"; 5 6 // This is testing the aboutCertError page (Bug 1207107). 7 8 const GOOD_PAGE = "https://example.com/"; 9 const GOOD_PAGE_2 = "https://example.org/"; 10 const BAD_CERT = "https://expired.example.com/"; 11 const UNKNOWN_ISSUER = "https://self-signed.example.com"; 12 const BAD_STS_CERT = 13 "https://badchain.include-subdomains.pinning.example.com:443"; 14 const { TabStateFlusher } = ChromeUtils.importESModule( 15 "resource:///modules/sessionstore/TabStateFlusher.sys.mjs" 16 ); 17 18 // Security CertError Felt Privacy set to false & true 19 // checked in one checkReturnToAboutHome to avoid duplicating code 20 add_task(async function checkReturnToAboutHome() { 21 info( 22 "Loading a bad cert page directly and making sure 'return to previous page' goes to about:home" 23 ); 24 for (let toggleFeltPrivacy of [ 25 setSecurityCertErrorsFeltPrivacyToFalse, 26 setSecurityCertErrorsFeltPrivacyToTrue, 27 ]) { 28 await toggleFeltPrivacy(); 29 30 for (let useFrame of [false, true]) { 31 let tab = await openErrorPage(BAD_CERT, useFrame); 32 let browser = tab.linkedBrowser; 33 await SpecialPowers.spawn(browser, [], () => { 34 content.document.notifyUserGestureActivation(); 35 }); 36 37 is(browser.webNavigation.canGoBack, false, "!webNavigation.canGoBack"); 38 is( 39 browser.webNavigation.canGoForward, 40 false, 41 "!webNavigation.canGoForward" 42 ); 43 44 // Populate the shistory entries manually, since it happens asynchronously 45 // and the following tests will be too soon otherwise. 46 await TabStateFlusher.flush(browser); 47 let { entries } = JSON.parse(SessionStore.getTabState(tab)); 48 is(entries.length, 1, "there is one shistory entry"); 49 50 info("Clicking the go back button on about:certerror"); 51 let bc = browser.browsingContext; 52 if (useFrame) { 53 bc = bc.children[0]; 54 } 55 let locationChangePromise = BrowserTestUtils.waitForLocationChange( 56 gBrowser, 57 "about:home" 58 ); 59 60 if (Services.prefs.getBoolPref("security.certerrors.felt-privacy-v1")) { 61 info("Felt Privacy enabled - using net-error-card"); 62 63 await SpecialPowers.spawn(bc, [useFrame], async function (subFrame) { 64 const netErrorCard = 65 content.document.querySelector("net-error-card").wrappedJSObject; 66 await netErrorCard.getUpdateComplete(); 67 const returnButton = netErrorCard.returnButton; 68 69 if (!subFrame) { 70 if (!Services.focus.focusedElement == returnButton) { 71 await ContentTaskUtils.waitForEvent(returnButton, "focus"); 72 } 73 Assert.ok(true, "returnButton has focus"); 74 } 75 // Note that going back to about:newtab might cause a process flip, if 76 // the browser is configured to run about:newtab in its own special 77 // content process. 78 returnButton.scrollIntoView(true); 79 EventUtils.synthesizeMouseAtCenter(returnButton, {}, content); 80 }); 81 } else { 82 info("Felt Privacy disabled - using aboutNetError"); 83 await SpecialPowers.spawn(bc, [useFrame], async function (subFrame) { 84 let returnButton = content.document.getElementById("returnButton"); 85 if (!subFrame) { 86 if (!Services.focus.focusedElement == returnButton) { 87 await ContentTaskUtils.waitForEvent(returnButton, "focus"); 88 } 89 Assert.ok(true, "returnButton has focus"); 90 } 91 // Note that going back to about:newtab might cause a process flip, if 92 // the browser is configured to run about:newtab in its own special 93 // content process. 94 returnButton.click(); 95 }); 96 } 97 98 await locationChangePromise; 99 100 is(browser.webNavigation.canGoBack, true, "webNavigation.canGoBack"); 101 is( 102 browser.webNavigation.canGoForward, 103 false, 104 "!webNavigation.canGoForward" 105 ); 106 is(gBrowser.currentURI.spec, "about:home", "Went back"); 107 108 BrowserTestUtils.removeTab(gBrowser.selectedTab); 109 } 110 await SpecialPowers.popPrefEnv(); 111 } 112 }); 113 114 // Security CertError Felt Privacy set to false only 115 add_task(async function checkReturnToPreviousPage_feltPrivacyToFalse() { 116 await setSecurityCertErrorsFeltPrivacyToFalse(); 117 info( 118 "Loading a bad cert page and making sure 'return to previous page' goes back" 119 ); 120 for (let useFrame of [false, true]) { 121 let tab; 122 let browser; 123 if (useFrame) { 124 tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, GOOD_PAGE); 125 browser = tab.linkedBrowser; 126 await SpecialPowers.spawn(browser, [], () => { 127 content.document.notifyUserGestureActivation(); 128 }); 129 130 BrowserTestUtils.startLoadingURIString(browser, GOOD_PAGE_2); 131 await BrowserTestUtils.browserLoaded(browser, false, GOOD_PAGE_2); 132 await injectErrorPageFrame(tab, BAD_CERT); 133 } else { 134 tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, GOOD_PAGE); 135 browser = gBrowser.selectedBrowser; 136 await SpecialPowers.spawn(browser, [], () => { 137 content.document.notifyUserGestureActivation(); 138 }); 139 140 info("Loading and waiting for the cert error"); 141 let certErrorLoaded = BrowserTestUtils.waitForErrorPage(browser); 142 BrowserTestUtils.startLoadingURIString(browser, BAD_CERT); 143 await certErrorLoaded; 144 } 145 146 is(browser.webNavigation.canGoBack, true, "webNavigation.canGoBack"); 147 is( 148 browser.webNavigation.canGoForward, 149 false, 150 "!webNavigation.canGoForward" 151 ); 152 153 // Populate the shistory entries manually, since it happens asynchronously 154 // and the following tests will be too soon otherwise. 155 await TabStateFlusher.flush(browser); 156 let { entries } = JSON.parse(SessionStore.getTabState(tab)); 157 is(entries.length, 2, "there are two shistory entries"); 158 159 info("Clicking the go back button on about:certerror"); 160 let bc = browser.browsingContext; 161 if (useFrame) { 162 bc = bc.children[0]; 163 } 164 165 let pageShownPromise = BrowserTestUtils.waitForContentEvent( 166 browser, 167 "pageshow", 168 true 169 ); 170 await SpecialPowers.spawn(bc, [useFrame], async function () { 171 let returnButton = content.document.getElementById("returnButton"); 172 returnButton.click(); 173 }); 174 await pageShownPromise; 175 176 is(browser.webNavigation.canGoBack, false, "!webNavigation.canGoBack"); 177 is(browser.webNavigation.canGoForward, true, "webNavigation.canGoForward"); 178 is(gBrowser.currentURI.spec, GOOD_PAGE, "Went back"); 179 180 BrowserTestUtils.removeTab(gBrowser.selectedTab); 181 } 182 }); 183 184 // Works for both Security CertError Felt Privacy set to true and false 185 // This checks that the appinfo.appBuildID starts with a date string, 186 // which is required for the misconfigured system time check. 187 add_task(async function checkAppBuildIDIsDate() { 188 let appBuildID = Services.appinfo.appBuildID; 189 let year = parseInt(appBuildID.substr(0, 4), 10); 190 let month = parseInt(appBuildID.substr(4, 2), 10); 191 let day = parseInt(appBuildID.substr(6, 2), 10); 192 193 Assert.ok(year >= 2016 && year <= 2100, "appBuildID contains a valid year"); 194 Assert.ok(month >= 1 && month <= 12, "appBuildID contains a valid month"); 195 Assert.ok(day >= 1 && day <= 31, "appBuildID contains a valid day"); 196 }); 197 198 add_task(async function checkAdvancedDetails_feltPrivacyToFalse() { 199 await setSecurityCertErrorsFeltPrivacyToFalse(); 200 info( 201 "Loading a bad cert page and verifying the main error and advanced details section" 202 ); 203 for (let useFrame of [false, true]) { 204 let tab = await openErrorPage(BAD_CERT, useFrame); 205 let browser = tab.linkedBrowser; 206 207 let bc = browser.browsingContext; 208 if (useFrame) { 209 bc = bc.children[0]; 210 } 211 212 let message = await SpecialPowers.spawn(bc, [], async function () { 213 let doc = content.document; 214 215 const shortDesc = doc.getElementById("errorShortDesc"); 216 const sdArgs = JSON.parse(shortDesc.dataset.l10nArgs); 217 is( 218 sdArgs.hostname, 219 "expired.example.com", 220 "Should list hostname in error message." 221 ); 222 223 Assert.ok( 224 doc.getElementById("certificateErrorDebugInformation").hidden, 225 "Debug info is initially hidden" 226 ); 227 228 let exceptionButton = doc.getElementById("exceptionDialogButton"); 229 Assert.ok( 230 !exceptionButton.disabled, 231 "Exception button is not disabled by default." 232 ); 233 234 let advancedButton = doc.getElementById("advancedButton"); 235 advancedButton.click(); 236 237 // Wait until fluent sets the errorCode inner text. 238 let errorCode; 239 await ContentTaskUtils.waitForCondition(() => { 240 errorCode = doc.getElementById("errorCode"); 241 return errorCode && errorCode.textContent != ""; 242 }, "error code has been set inside the advanced button panel"); 243 244 return { 245 textContent: errorCode.textContent, 246 tagName: errorCode.tagName.toLowerCase(), 247 }; 248 }); 249 is( 250 message.textContent, 251 "SEC_ERROR_EXPIRED_CERTIFICATE", 252 "Correct error message found" 253 ); 254 is(message.tagName, "a", "Error message is a link"); 255 256 message = await SpecialPowers.spawn(bc, [], async function () { 257 let doc = content.document; 258 let errorCode = doc.getElementById("errorCode"); 259 errorCode.click(); 260 let div = doc.getElementById("certificateErrorDebugInformation"); 261 let text = doc.getElementById("certificateErrorText"); 262 Assert.notStrictEqual( 263 content.getComputedStyle(div).display, 264 "none", 265 "Debug information is visible" 266 ); 267 let handshakeCertificates = 268 content.docShell.failedChannel.securityInfo.handshakeCertificates.map( 269 cert => cert.getBase64DERString() 270 ); 271 return { 272 divDisplay: content.getComputedStyle(div).display, 273 text: text.textContent, 274 handshakeCertificates, 275 }; 276 }); 277 isnot(message.divDisplay, "none", "Debug information is visible"); 278 ok(message.text.includes(BAD_CERT), "Correct URL found"); 279 ok( 280 message.text.includes("Certificate has expired"), 281 "Correct error message found" 282 ); 283 ok( 284 message.text.includes("HTTP Strict Transport Security: false"), 285 "Correct HSTS value found" 286 ); 287 ok( 288 message.text.includes("HTTP Public Key Pinning: false"), 289 "Correct HPKP value found" 290 ); 291 let certChain = getCertChainAsString(message.handshakeCertificates); 292 ok(message.text.includes(certChain), "Found certificate chain"); 293 294 BrowserTestUtils.removeTab(gBrowser.selectedTab); 295 } 296 }); 297 298 add_task(async function checkAdvancedDetailsForHSTS_feltPrivacyToFalse() { 299 await setSecurityCertErrorsFeltPrivacyToFalse(); 300 info( 301 "Loading a bad STS cert page and verifying the advanced details section" 302 ); 303 for (let useFrame of [false, true]) { 304 let tab = await openErrorPage(BAD_STS_CERT, useFrame); 305 let browser = tab.linkedBrowser; 306 307 let bc = browser.browsingContext; 308 if (useFrame) { 309 bc = bc.children[0]; 310 } 311 312 let message = await SpecialPowers.spawn(bc, [], async function () { 313 let doc = content.document; 314 let advancedButton = doc.getElementById("advancedButton"); 315 advancedButton.click(); 316 317 // Wait until fluent sets the errorCode inner text. 318 let ec; 319 await ContentTaskUtils.waitForCondition(() => { 320 ec = doc.getElementById("errorCode"); 321 return ec.textContent != ""; 322 }, "error code has been set inside the advanced button panel"); 323 324 let cdl = doc.getElementById("cert_domain_link"); 325 return { 326 ecTextContent: ec.textContent, 327 ecTagName: ec.tagName.toLowerCase(), 328 cdlTextContent: cdl.textContent, 329 cdlTagName: cdl.tagName.toLowerCase(), 330 }; 331 }); 332 333 const badStsUri = Services.io.newURI(BAD_STS_CERT); 334 is( 335 message.ecTextContent, 336 "SSL_ERROR_BAD_CERT_DOMAIN", 337 "Correct error message found" 338 ); 339 is(message.ecTagName, "a", "Error message is a link"); 340 const url = badStsUri.prePath.slice(badStsUri.prePath.indexOf(".") + 1); 341 is(message.cdlTextContent, url, "Correct cert_domain_link contents found"); 342 is(message.cdlTagName, "a", "cert_domain_link is a link"); 343 344 message = await SpecialPowers.spawn(bc, [], async function () { 345 let doc = content.document; 346 let errorCode = doc.getElementById("errorCode"); 347 errorCode.click(); 348 let div = doc.getElementById("certificateErrorDebugInformation"); 349 let text = doc.getElementById("certificateErrorText"); 350 let handshakeCertificates = 351 content.docShell.failedChannel.securityInfo.handshakeCertificates.map( 352 cert => cert.getBase64DERString() 353 ); 354 return { 355 divDisplay: content.getComputedStyle(div).display, 356 text: text.textContent, 357 handshakeCertificates, 358 }; 359 }); 360 isnot(message.divDisplay, "none", "Debug information is visible"); 361 ok(message.text.includes(badStsUri.spec), "Correct URL found"); 362 ok( 363 message.text.includes( 364 "requested domain name does not match the server\u2019s certificate" 365 ), 366 "Correct error message found" 367 ); 368 ok( 369 message.text.includes("HTTP Strict Transport Security: false"), 370 "Correct HSTS value found" 371 ); 372 ok( 373 message.text.includes("HTTP Public Key Pinning: true"), 374 "Correct HPKP value found" 375 ); 376 let certChain = getCertChainAsString(message.handshakeCertificates); 377 ok(message.text.includes(certChain), "Found certificate chain"); 378 379 BrowserTestUtils.removeTab(gBrowser.selectedTab); 380 } 381 }); 382 383 add_task(async function checkUnknownIssuerLearnMoreLink_feltPrivacyToFalse() { 384 await setSecurityCertErrorsFeltPrivacyToFalse(); 385 info( 386 "Loading a cert error for self-signed pages and checking the correct link is shown" 387 ); 388 for (let useFrame of [false, true]) { 389 let tab = await openErrorPage(UNKNOWN_ISSUER, useFrame); 390 let browser = tab.linkedBrowser; 391 392 let bc = browser.browsingContext; 393 if (useFrame) { 394 bc = bc.children[0]; 395 } 396 397 let href = await SpecialPowers.spawn(bc, [], async function () { 398 let learnMoreLink = content.document.getElementById("learnMoreLink"); 399 return learnMoreLink.href; 400 }); 401 ok(href.endsWith("security-error"), "security-error in the Learn More URL"); 402 403 BrowserTestUtils.removeTab(gBrowser.selectedTab); 404 } 405 }); 406 407 add_task(async function checkViewCertificate_feltPrivacyToFalse() { 408 await setSecurityCertErrorsFeltPrivacyToFalse(); 409 info("Loading a cert error and checking that the certificate can be shown."); 410 for (let useFrame of [true, false]) { 411 if (useFrame) { 412 // Bug #1573502 413 continue; 414 } 415 let tab = await openErrorPage(UNKNOWN_ISSUER, useFrame); 416 let browser = tab.linkedBrowser; 417 418 let bc = browser.browsingContext; 419 if (useFrame) { 420 bc = bc.children[0]; 421 } 422 423 let loaded = BrowserTestUtils.waitForNewTab(gBrowser, null, true); 424 await SpecialPowers.spawn(bc, [], async function () { 425 let viewCertificate = content.document.getElementById("viewCertificate"); 426 viewCertificate.click(); 427 }); 428 await loaded; 429 430 let spec = gBrowser.currentURI.spec; 431 Assert.ok( 432 spec.startsWith("about:certificate"), 433 "about:certificate is the new opened tab" 434 ); 435 436 await SpecialPowers.spawn( 437 gBrowser.selectedTab.linkedBrowser, 438 [], 439 async function () { 440 let doc = content.document; 441 let certificateSection = await ContentTaskUtils.waitForCondition(() => { 442 return doc.querySelector("certificate-section"); 443 }, "Certificate section found"); 444 445 let infoGroup = 446 certificateSection.shadowRoot.querySelector("info-group"); 447 Assert.ok(infoGroup, "infoGroup found"); 448 449 let items = infoGroup.shadowRoot.querySelectorAll("info-item"); 450 let commonnameID = items[items.length - 1].shadowRoot 451 .querySelector("label") 452 .getAttribute("data-l10n-id"); 453 Assert.equal( 454 commonnameID, 455 "certificate-viewer-common-name", 456 "The correct item was selected" 457 ); 458 459 let commonnameValue = 460 items[items.length - 1].shadowRoot.querySelector(".info").textContent; 461 Assert.equal( 462 commonnameValue, 463 "self-signed.example.com", 464 "Shows the correct certificate in the page" 465 ); 466 } 467 ); 468 BrowserTestUtils.removeTab(gBrowser.selectedTab); // closes about:certificate 469 BrowserTestUtils.removeTab(gBrowser.selectedTab); 470 } 471 }); 472 473 add_task(async function checkBadStsCertHeadline_feltPrivacyToFalse() { 474 await setSecurityCertErrorsFeltPrivacyToFalse(); 475 info( 476 "Loading a bad sts cert error page and checking that the correct headline is shown" 477 ); 478 for (let useFrame of [false, true]) { 479 let tab = await openErrorPage(BAD_CERT, useFrame); 480 let browser = tab.linkedBrowser; 481 482 let bc = browser.browsingContext; 483 if (useFrame) { 484 bc = bc.children[0]; 485 } 486 487 await SpecialPowers.spawn(bc, [useFrame], async _useFrame => { 488 const titleText = content.document.querySelector(".title-text"); 489 is( 490 titleText.dataset.l10nId, 491 _useFrame ? "nssBadCert-sts-title" : "nssBadCert-title", 492 "Error page title is set" 493 ); 494 }); 495 BrowserTestUtils.removeTab(gBrowser.selectedTab); 496 } 497 }); 498 499 add_task(async function checkSandboxedIframe_feltPrivacyToFalse() { 500 await setSecurityCertErrorsFeltPrivacyToFalse(); 501 info( 502 "Loading a bad sts cert error in a sandboxed iframe and check that the correct headline is shown" 503 ); 504 let useFrame = true; 505 let sandboxed = true; 506 let tab = await openErrorPage(BAD_CERT, useFrame, sandboxed); 507 let browser = tab.linkedBrowser; 508 509 let bc = browser.browsingContext.children[0]; 510 await SpecialPowers.spawn(bc, [], async function () { 511 let doc = content.document; 512 513 const titleText = doc.querySelector(".title-text"); 514 is( 515 titleText.dataset.l10nId, 516 "nssBadCert-sts-title", 517 "Title shows Did Not Connect: Potential Security Issue" 518 ); 519 520 const errorLabel = doc.querySelector( 521 '[data-l10n-id="cert-error-code-prefix-link"]' 522 ); 523 const elArgs = JSON.parse(errorLabel.dataset.l10nArgs); 524 is( 525 elArgs.error, 526 "SEC_ERROR_EXPIRED_CERTIFICATE", 527 "Correct error message found" 528 ); 529 is( 530 doc.getElementById("errorCode").tagName.toLowerCase(), 531 "a", 532 "Error message contains a link" 533 ); 534 }); 535 BrowserTestUtils.removeTab(gBrowser.selectedTab); 536 }); 537 538 add_task(async function checkViewSource_feltPrivacyToFalse() { 539 await setSecurityCertErrorsFeltPrivacyToFalse(); 540 info( 541 "Loading a bad sts cert error in a sandboxed iframe and check that the correct headline is shown" 542 ); 543 let uri = "view-source:" + BAD_CERT; 544 let tab = await openErrorPage(uri); 545 let browser = tab.linkedBrowser; 546 547 await SpecialPowers.spawn(browser, [], async function () { 548 let doc = content.document; 549 550 const errorLabel = doc.querySelector( 551 '[data-l10n-id="cert-error-code-prefix-link"]' 552 ); 553 const elArgs = JSON.parse(errorLabel.dataset.l10nArgs); 554 is( 555 elArgs.error, 556 "SEC_ERROR_EXPIRED_CERTIFICATE", 557 "Correct error message found" 558 ); 559 is( 560 doc.getElementById("errorCode").tagName.toLowerCase(), 561 "a", 562 "Error message contains a link" 563 ); 564 565 const titleText = doc.querySelector(".title-text"); 566 is(titleText.dataset.l10nId, "nssBadCert-title", "Error page title is set"); 567 568 const shortDesc = doc.getElementById("errorShortDesc"); 569 const sdArgs = JSON.parse(shortDesc.dataset.l10nArgs); 570 is( 571 sdArgs.hostname, 572 "expired.example.com", 573 "Should list hostname in error message." 574 ); 575 }); 576 577 let loaded = BrowserTestUtils.browserLoaded(browser, false, uri); 578 info("Clicking the exceptionDialogButton in advanced panel"); 579 await SpecialPowers.spawn(browser, [], async function () { 580 let doc = content.document; 581 let exceptionButton = doc.getElementById("exceptionDialogButton"); 582 exceptionButton.click(); 583 }); 584 585 info("Loading the url after adding exception"); 586 await loaded; 587 588 await SpecialPowers.spawn(browser, [], async function () { 589 let doc = content.document; 590 ok( 591 !doc.documentURI.startsWith("about:certerror"), 592 "Exception has been added" 593 ); 594 }); 595 596 let certOverrideService = Cc[ 597 "@mozilla.org/security/certoverride;1" 598 ].getService(Ci.nsICertOverrideService); 599 certOverrideService.clearValidityOverride("expired.example.com", -1, {}); 600 601 loaded = BrowserTestUtils.waitForErrorPage(browser); 602 BrowserCommands.reloadSkipCache(); 603 await loaded; 604 605 BrowserTestUtils.removeTab(gBrowser.selectedTab); 606 }); 607 608 add_task(async function testCertificateTransparency() { 609 info( 610 "Test that when certificate transparency is enforced, the right error page is shown." 611 ); 612 // Enforce certificate transparency for certificates issued by our test root. 613 // This is only possible in debug builds, hence skipping this test in 614 // non-debug builds (see below). 615 await SpecialPowers.pushPrefEnv({ 616 set: [ 617 ["security.pki.certificate_transparency.mode", 2], 618 [ 619 "security.test.built_in_root_hash", 620 "8D:9D:57:09:E5:7D:D5:C6:4B:BE:24:70:E9:E5:BF:FF:16:F6:F2:C2:49:4E:0F:B9:37:1C:DD:3A:0E:10:45:F4", 621 ], 622 ], 623 }); 624 625 for (let useFrame of [false, true]) { 626 let tab; 627 let browser; 628 if (useFrame) { 629 tab = await BrowserTestUtils.openNewForegroundTab( 630 gBrowser, 631 "about:blank" 632 ); 633 browser = tab.linkedBrowser; 634 635 // injectErrorPageFrame is a helper from head.js for this purpose 636 await injectErrorPageFrame(tab, GOOD_PAGE, useFrame); 637 } else { 638 tab = await openErrorPage(GOOD_PAGE, useFrame); 639 browser = tab.linkedBrowser; 640 } 641 642 let bc = browser.browsingContext; 643 if (useFrame) { 644 bc = bc.children[0]; 645 } 646 647 let message = await SpecialPowers.spawn(bc, [], async function () { 648 const doc = content.document; 649 const shortDesc = doc.getElementById("errorShortDesc"); 650 const sdArgs = JSON.parse(shortDesc.dataset.l10nArgs); 651 Assert.equal( 652 sdArgs.hostname, 653 "example.com", 654 "Should list hostname in error message." 655 ); 656 const advancedButton = doc.getElementById("advancedButton"); 657 advancedButton.scrollIntoView(true); 658 EventUtils.synthesizeMouseAtCenter(advancedButton, {}, content); 659 660 // Wait until fluent sets the errorCode inner text. 661 let errorCode; 662 await ContentTaskUtils.waitForCondition(() => { 663 errorCode = doc.getElementById("errorCode"); 664 return errorCode && errorCode.textContent != ""; 665 }, "error code has been set inside the advanced button panel"); 666 667 return { 668 textContent: errorCode.textContent, 669 tagName: errorCode.tagName.toLowerCase(), 670 }; 671 }); 672 673 Assert.equal( 674 message.textContent, 675 "MOZILLA_PKIX_ERROR_INSUFFICIENT_CERTIFICATE_TRANSPARENCY", 676 "Correct error message found" 677 ); 678 Assert.equal(message.tagName, "a", "Error message is a link"); 679 680 message = await SpecialPowers.spawn(bc, [], async function () { 681 const doc = content.document; 682 const errorCode = doc.getElementById("errorCode"); 683 errorCode.scrollIntoView(true); 684 EventUtils.synthesizeMouseAtCenter(errorCode, {}, content); 685 const text = doc.getElementById("certificateErrorText"); 686 return { 687 text: text.textContent, 688 }; 689 }); 690 Assert.ok(message.text.includes(GOOD_PAGE), "Correct URL found"); 691 692 BrowserTestUtils.removeTab(gBrowser.selectedTab); 693 BrowserTestUtils.removeTab(tab); 694 } 695 696 await SpecialPowers.popPrefEnv(); 697 698 // Certificate transparency can only be enforced for our test certificates in 699 // debug builds. 700 }).skip(!AppConstants.DEBUG); 701 702 add_task(async function testCertificateTransparency_feltPrivacyTrue() { 703 info( 704 "Test that when certificate transparency is enforced, the right error page is shown." 705 ); 706 // Enforce certificate transparency for certificates issued by our test root. 707 // This is only possible in debug builds, hence skipping this test in 708 // non-debug builds (see below). 709 await SpecialPowers.pushPrefEnv({ 710 set: [ 711 ["security.pki.certificate_transparency.mode", 2], 712 [ 713 "security.test.built_in_root_hash", 714 "8D:9D:57:09:E5:7D:D5:C6:4B:BE:24:70:E9:E5:BF:FF:16:F6:F2:C2:49:4E:0F:B9:37:1C:DD:3A:0E:10:45:F4", 715 ], 716 ["security.certerrors.felt-privacy-v1", true], 717 ], 718 }); 719 720 for (let useFrame of [false, true]) { 721 const tab = await openErrorPage(GOOD_PAGE, useFrame); 722 const browser = tab.linkedBrowser; 723 724 let bc = browser.browsingContext; 725 if (useFrame) { 726 bc = bc.children[0]; 727 } 728 729 const message = await SpecialPowers.spawn(bc, [], async function () { 730 const doc = content.document; 731 732 const netErrorCard = doc.querySelector("net-error-card").wrappedJSObject; 733 await netErrorCard.getUpdateComplete(); 734 735 netErrorCard.advancedButton.scrollIntoView(); 736 EventUtils.synthesizeMouseAtCenter( 737 netErrorCard.advancedButton, 738 {}, 739 content 740 ); 741 742 await ContentTaskUtils.waitForCondition(() => { 743 return ( 744 netErrorCard.whyDangerous && 745 netErrorCard.whyDangerous.textContent != "" 746 ); 747 }, "Waiting for why dangerous text"); 748 const args = JSON.parse(netErrorCard.whyDangerous.dataset.l10nArgs); 749 Assert.strictEqual( 750 args.hostname, 751 "example.com", 752 "Should list hostname in error message." 753 ); 754 755 // Wait until fluent sets the errorCode inner text. 756 await ContentTaskUtils.waitForCondition(() => { 757 return ( 758 netErrorCard.errorCode && netErrorCard.errorCode.textContent != "" 759 ); 760 }, "error code has been set inside the advanced button panel"); 761 762 netErrorCard.errorCode.scrollIntoView(); 763 EventUtils.synthesizeMouseAtCenter(netErrorCard.errorCode, {}, content); 764 await ContentTaskUtils.waitForCondition(() => { 765 return ( 766 netErrorCard.certErrorText && 767 netErrorCard.certErrorText.textContent != "" 768 ); 769 }, "Certificate Error is showing"); 770 771 return { 772 certText: netErrorCard.certErrorText.textContent, 773 errorCode: netErrorCard.errorCode.textContent, 774 tagName: netErrorCard.errorCode.tagName.toLowerCase(), 775 }; 776 }); 777 778 Assert.ok( 779 message.errorCode.includes( 780 "MOZILLA_PKIX_ERROR_INSUFFICIENT_CERTIFICATE_TRANSPARENCY" 781 ), 782 "Correct error message found" 783 ); 784 Assert.strictEqual(message.tagName, "a", "Error message is a link"); 785 Assert.ok(message.certText.includes(GOOD_PAGE), "Correct URL found"); 786 787 BrowserTestUtils.removeTab(gBrowser.selectedTab); 788 } 789 790 await SpecialPowers.popPrefEnv(); 791 792 // Certificate transparency can only be enforced for our test certificates in 793 // debug builds. 794 }).skip(!AppConstants.DEBUG); 795 796 /** 797 * A reusable helper that runs assertions on a network error page. 798 * It encapsulates the SpecialPowers.spawn call to be CSP-compliant. 799 * 800 * @param {object} params - Parameters for the assertion. 801 * @param {string} params.expectedUrl - The URL to load and check. 802 * @param {string} params.expectedHostname - The expected hostname to assert in the error page. 803 * @param {string} params.expectedErrorCode - The expected error code to assert. 804 * @param {string} params.expectedInfo - Info string for logging. 805 * @param {string} params.expectedErrorMessage - Error message to assert in the error text. 806 * @param {boolean} params.expectedHpkp - HPKP value to assert in the error text. 807 */ 808 async function assertNetErrorPage({ 809 expectedUrl, 810 expectedHostname, 811 expectedErrorCode, 812 expectedInfo, 813 expectedErrorMessage, 814 expectedHpkp, 815 }) { 816 await setSecurityCertErrorsFeltPrivacyToTrue(); 817 info(`${expectedInfo}`); 818 819 for (let useFrame of [false, true]) { 820 let tab = await openErrorPage(expectedUrl, useFrame); 821 let browser = tab.linkedBrowser; 822 let bc = browser.browsingContext; 823 if (useFrame) { 824 bc = bc.children[0]; 825 } 826 827 const newTabPromise = BrowserTestUtils.waitForNewTab(gBrowser, null, true); 828 const contentData = await SpecialPowers.spawn( 829 bc, 830 [expectedHostname, expectedErrorCode], 831 async function (hostname, errorCode) { 832 const netErrorCard = 833 content.document.querySelector("net-error-card").wrappedJSObject; 834 await netErrorCard.getUpdateComplete(); 835 836 // Assert Error Card Basics 837 Assert.ok( 838 netErrorCard.certErrorBodyTitle, 839 "The error page title should exist." 840 ); 841 842 const shortDesc = netErrorCard.certErrorIntro; 843 const shortDescArgs = JSON.parse(shortDesc.dataset.l10nArgs); 844 Assert.equal( 845 shortDescArgs.hostname, 846 hostname, 847 "Should list hostname in error message." 848 ); 849 850 // Assert Advanced button 851 Assert.ok( 852 !netErrorCard.advancedContainer, 853 "The Advanced container should NOT be found in shadow DOM before click." 854 ); 855 const advancedButton = netErrorCard.advancedButton; 856 Assert.ok(advancedButton, "The advanced button should exist."); 857 Assert.equal( 858 advancedButton.dataset.l10nId, 859 "fp-certerror-advanced-button", 860 "Button should have the 'advanced' l10n ID." 861 ); 862 863 advancedButton.scrollIntoView(true); 864 EventUtils.synthesizeMouseAtCenter(advancedButton, {}, content); 865 await ContentTaskUtils.waitForCondition( 866 () => netErrorCard.advancedContainer, 867 "Wait for the advanced container." 868 ); 869 870 const hideExceptionButton = netErrorCard.shouldHideExceptionButton(); 871 if (!hideExceptionButton) { 872 await ContentTaskUtils.waitForCondition( 873 () => 874 netErrorCard.exceptionButton && 875 !netErrorCard.exceptionButton.disabled, 876 "Wait for the exception button to be created." 877 ); 878 879 Assert.ok( 880 !netErrorCard.exceptionButton.disabled, 881 "The exception button is now enabled." 882 ); 883 } 884 885 Assert.equal( 886 advancedButton.dataset.l10nId, 887 "fp-certerror-hide-advanced-button", 888 "Button should have the 'hide-advanced' l10n ID." 889 ); 890 Assert.ok( 891 netErrorCard.advancedShowing, 892 "Advanced showing attribute should be true" 893 ); 894 895 // Assert Error Code 896 const certErrorCodeLink = netErrorCard.errorCode; 897 Assert.equal( 898 certErrorCodeLink.textContent, 899 `Error Code: ${errorCode}`, 900 "Error code text is as expected" 901 ); 902 Assert.equal( 903 certErrorCodeLink.tagName.toLowerCase(), 904 "a", 905 "Error code is a link" 906 ); 907 908 certErrorCodeLink.scrollIntoView(true); 909 EventUtils.synthesizeMouseAtCenter(certErrorCodeLink, {}, content); 910 await ContentTaskUtils.waitForMutationCondition( 911 netErrorCard, 912 { attributeFilter: ["certErrorDebugInfoShowing"] }, 913 () => netErrorCard.certErrorDebugInfoShowing 914 ); 915 Assert.ok( 916 netErrorCard.certErrorDebugInfoShowing, 917 "The 'certErrorDebugInfoShowing' boolean should be toggled (to true) after Advance button click on assertAdvancedButton." 918 ); 919 Assert.ok(netErrorCard.certErrorText, "Error Code Detail should exist"); 920 921 // Assert Site Certificate 922 info("Clicking the View Certificate button in advanced panel"); 923 netErrorCard.viewCertificate.scrollIntoView(true); 924 EventUtils.synthesizeMouseAtCenter( 925 netErrorCard.viewCertificate, 926 {}, 927 content 928 ); 929 930 // Extract data needed by the parent process 931 const failedCertChain = 932 content.docShell.failedChannel.securityInfo.handshakeCertificates.map( 933 cert => cert.getBase64DERString() 934 ); 935 936 return { 937 errorText: netErrorCard.certErrorText.textContent, 938 rawCertChain: failedCertChain, 939 }; 940 } 941 ); 942 943 Assert.ok( 944 contentData.errorText.includes(expectedHostname), 945 "Correct URL found" 946 ); 947 Assert.ok( 948 contentData.errorText.includes(expectedErrorMessage), 949 "Correct error message exists" 950 ); 951 Assert.ok( 952 contentData.errorText.includes("HTTP Strict Transport Security: false"), 953 "Correct HSTS value exists" 954 ); 955 Assert.ok( 956 contentData.errorText.includes( 957 `HTTP Public Key Pinning: ${expectedHpkp}` 958 ), 959 "Correct HPKP value exists" 960 ); 961 962 info("Loading the about:certificate page"); 963 let newTab = await newTabPromise; 964 965 // Parent process checking if certificate viewer opened 966 const spec = gBrowser.currentURI.spec; 967 Assert.ok( 968 spec.startsWith("about:certificate"), 969 "about:certificate is the new opened tab" 970 ); 971 972 await SpecialPowers.spawn( 973 gBrowser.selectedTab.linkedBrowser, 974 [expectedHostname], 975 async function (hostname) { 976 const doc = content.document; 977 const certificateSection = await ContentTaskUtils.waitForCondition( 978 () => { 979 // Content process checking if we're no longer on the error page 980 Assert.ok( 981 !doc.documentURI.startsWith("about:certerror"), 982 "We are now in a new tab with content including: about:certificate" 983 ); 984 return doc.querySelector("certificate-section"); 985 }, 986 "Certificate section found" 987 ); 988 989 const infoGroup = 990 certificateSection.shadowRoot.querySelector("info-group"); 991 Assert.ok(infoGroup, "infoGroup found"); 992 993 const items = infoGroup.shadowRoot.querySelectorAll("info-item"); 994 const commonNameID = items[items.length - 1].shadowRoot 995 .querySelector("label") 996 .getAttribute("data-l10n-id"); 997 Assert.equal( 998 commonNameID, 999 "certificate-viewer-common-name", 1000 "The correct item was selected" 1001 ); 1002 1003 const commonNameValue = 1004 items[items.length - 1].shadowRoot.querySelector(".info").textContent; 1005 1006 // Bug 1992278 1007 // Structuring the logic this way avoids issue to be addressed 1008 // in most cases the values match, "self-signed.example.com", "self-signed.example.com" 1009 // in the case of pinning, the commonNameValue is shorter, "pinning-test.example.com", 1010 // than the hostname "badchain.include-subdomains.pinning.example.com" 1011 // as a temporary measure I have reversed the logic such that hostname includes commonNameValue 1012 Assert.ok( 1013 hostname.includes(commonNameValue), 1014 "Error text should include the expected hostname" 1015 ); 1016 } 1017 ); 1018 1019 BrowserTestUtils.removeTab(newTab); 1020 BrowserTestUtils.removeTab(tab); 1021 } 1022 } 1023 1024 /** 1025 * A reusable helper that runs assertions on a view-source network error page. 1026 * It encapsulates the SpecialPowers.spawn call to be CSP-compliant. 1027 * 1028 * @param {object} params - Parameters for the assertion. 1029 * @param {string} params.expectedHostname - The expected hostname to assert in the error page. 1030 * @param {string} params.expectedUrl - The URL to load and check. 1031 */ 1032 async function assertViewSourceNetErrorPage({ 1033 expectedHostname, 1034 expectedUrl, 1035 expectedInfo, 1036 }) { 1037 await setSecurityCertErrorsFeltPrivacyToTrue(); 1038 info(`${expectedInfo}`); 1039 1040 let tab = await openErrorPage(expectedUrl); 1041 const browser = tab.linkedBrowser; 1042 const loaded = BrowserTestUtils.browserLoaded(browser, false, expectedUrl); 1043 1044 await SpecialPowers.spawn(browser, [], async function () { 1045 const netErrorCard = 1046 content.document.querySelector("net-error-card").wrappedJSObject; 1047 await netErrorCard.getUpdateComplete(); 1048 // Advanced button 1049 const advancedButton = netErrorCard.advancedButton; 1050 advancedButton.scrollIntoView(true); 1051 EventUtils.synthesizeMouseAtCenter(advancedButton, {}, content); 1052 await ContentTaskUtils.waitForCondition( 1053 () => 1054 netErrorCard.exceptionButton && !netErrorCard.exceptionButton.disabled, 1055 "Wait for the exception button to be created." 1056 ); 1057 1058 info("Clicking the Proceed Risky button in advanced panel"); 1059 netErrorCard.exceptionButton.scrollIntoView(true); 1060 EventUtils.synthesizeMouseAtCenter( 1061 netErrorCard.exceptionButton, 1062 {}, 1063 content 1064 ); 1065 }); 1066 1067 info("Loading the url after proceeding with risk"); 1068 await loaded; 1069 1070 // Clean up the cert override 1071 const certOverrideService = Cc[ 1072 "@mozilla.org/security/certoverride;1" 1073 ].getService(Ci.nsICertOverrideService); 1074 certOverrideService.clearValidityOverride(expectedHostname, -1, {}); 1075 1076 // To ensure the state is reset, reload and wait for the error page to return. 1077 info("Reloading to ensure the certificate override was cleared."); 1078 const errorPageLoaded = BrowserTestUtils.waitForErrorPage(browser); 1079 1080 BrowserCommands.reloadSkipCache(); 1081 await errorPageLoaded; 1082 1083 info("Override cleared and error page is shown again."); 1084 BrowserTestUtils.removeTab(tab); 1085 } 1086 1087 // Security CertError Felt Privacy set to true only 1088 add_task(async function checkReturnToPreviousPage_feltPrivacyToTrue() { 1089 await setSecurityCertErrorsFeltPrivacyToTrue(); 1090 info( 1091 "Loading a bad cert page and making sure 'return to previous page' goes back" 1092 ); 1093 for (let useFrame of [false, true]) { 1094 let tab; 1095 let browser; 1096 if (useFrame) { 1097 tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, GOOD_PAGE); 1098 browser = tab.linkedBrowser; 1099 await SpecialPowers.spawn(browser, [], () => { 1100 content.document.notifyUserGestureActivation(); 1101 }); 1102 1103 BrowserTestUtils.startLoadingURIString(browser, GOOD_PAGE_2); 1104 await BrowserTestUtils.browserLoaded(browser, false, GOOD_PAGE_2); 1105 await injectErrorPageFrame(tab, BAD_CERT); 1106 } else { 1107 tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, GOOD_PAGE); 1108 browser = gBrowser.selectedBrowser; 1109 await SpecialPowers.spawn(browser, [], () => { 1110 content.document.notifyUserGestureActivation(); 1111 }); 1112 1113 info("Loading and waiting for the cert error"); 1114 const certErrorLoaded = BrowserTestUtils.waitForErrorPage(browser); 1115 BrowserTestUtils.startLoadingURIString(browser, BAD_CERT); 1116 await certErrorLoaded; 1117 } 1118 1119 Assert.ok(browser.webNavigation.canGoBack, "webNavigation.canGoBack"); 1120 Assert.ok( 1121 !browser.webNavigation.canGoForward, 1122 "!webNavigation.canGoForward" 1123 ); 1124 1125 // Populate the shistory entries manually, since it happens asynchronously 1126 // and the following tests will be too soon otherwise. 1127 await TabStateFlusher.flush(browser); 1128 const { entries } = JSON.parse(SessionStore.getTabState(tab)); 1129 Assert.equal(entries.length, 2, "there are two history entries"); 1130 1131 info("Clicking the go back button on about:certerror"); 1132 let bc = browser.browsingContext; 1133 if (useFrame) { 1134 bc = bc.children[0]; 1135 } 1136 1137 const pageShownPromise = BrowserTestUtils.waitForContentEvent( 1138 browser, 1139 "pageshow", 1140 true 1141 ); 1142 await SpecialPowers.spawn(bc, [useFrame], async function () { 1143 const netErrorCard = 1144 content.document.querySelector("net-error-card").wrappedJSObject; 1145 await netErrorCard.getUpdateComplete(); 1146 const returnButton = netErrorCard.returnButton; 1147 returnButton.scrollIntoView(true); 1148 EventUtils.synthesizeMouseAtCenter(returnButton, {}, content); 1149 }); 1150 await pageShownPromise; 1151 1152 Assert.ok(!browser.webNavigation.canGoBack, "!webNavigation.canGoBack"); 1153 Assert.ok(browser.webNavigation.canGoForward, "webNavigation.canGoForward"); 1154 Assert.equal(gBrowser.currentURI.spec, GOOD_PAGE, "Went back"); 1155 1156 BrowserTestUtils.removeTab(gBrowser.selectedTab); 1157 } 1158 }); 1159 1160 add_task(async function checkAdvancedDetails_feltPrivacyToTrue() { 1161 // Helper function does all the content-side checks. 1162 await assertNetErrorPage({ 1163 expectedUrl: BAD_CERT, 1164 expectedHostname: new URL(BAD_CERT).hostname, 1165 expectedErrorCode: "SEC_ERROR_EXPIRED_CERTIFICATE", 1166 expectedInfo: 1167 "Loading a bad cert page and verifying the main error and advanced details section", 1168 expectedErrorMessage: "Certificate has expired", 1169 expectedHpkp: false, 1170 }); 1171 }); 1172 1173 add_task(async function checkAdvancedDetailsForHSTS_feltPrivacyToTrue() { 1174 // Helper function does all the content-side checks. 1175 await assertNetErrorPage({ 1176 expectedUrl: BAD_STS_CERT, 1177 expectedHostname: new URL(BAD_STS_CERT).hostname, 1178 expectedErrorCode: "SSL_ERROR_BAD_CERT_DOMAIN", 1179 expectedInfo: 1180 "Loading a bad STS cert page and verifying the advanced details section", 1181 expectedErrorMessage: 1182 "requested domain name does not match the server\u2019s certificate", // Expected error message 1183 expectedHpkp: true, 1184 }); 1185 }); 1186 1187 add_task(async function checkUnknownIssuerDetails_feltPrivacyToTrue() { 1188 // Helper function does all the content-side checks. 1189 await assertNetErrorPage({ 1190 expectedUrl: UNKNOWN_ISSUER, 1191 expectedHostname: new URL(UNKNOWN_ISSUER).hostname, 1192 expectedErrorCode: "MOZILLA_PKIX_ERROR_SELF_SIGNED_CERT", 1193 expectedInfo: 1194 "Loading a cert error for self-signed pages and checking the correct link is shown", 1195 expectedErrorMessage: 1196 "The certificate is not trusted because it is self-signed.", 1197 expectedHpkp: false, 1198 }); 1199 }); 1200 1201 add_task(async function checkViewSource_feltPrivacyToTrue() { 1202 await assertViewSourceNetErrorPage({ 1203 expectedHostname: new URL(BAD_CERT).hostname, 1204 expectedUrl: "view-source:" + BAD_CERT, 1205 expectedInfo: 1206 "Loading a bad sts cert error in a sandboxed iframe and check that the correct headline is shown", 1207 }); 1208 }); 1209 1210 add_task(async function checkSandboxedIframe_feltPrivacyToTrue() { 1211 await setSecurityCertErrorsFeltPrivacyToTrue(); 1212 info( 1213 "Loading a bad sts cert error in a sandboxed iframe and check that the correct headline is shown" 1214 ); 1215 let useFrame = true; 1216 let sandboxed = true; 1217 let tab = await openErrorPage(BAD_CERT, useFrame, sandboxed); 1218 let browser = tab.linkedBrowser; 1219 1220 let bc = browser.browsingContext.children[0]; 1221 await SpecialPowers.spawn(bc, [], async function () { 1222 const netErrorCard = 1223 content.document.querySelector("net-error-card").wrappedJSObject; 1224 await netErrorCard.getUpdateComplete(); 1225 1226 // Assert Error Card Basics 1227 Assert.ok( 1228 netErrorCard.certErrorBodyTitle, 1229 "The error page title should exist." 1230 ); 1231 const advancedButton = netErrorCard.advancedButton; 1232 advancedButton.scrollIntoView(true); 1233 EventUtils.synthesizeMouseAtCenter(advancedButton, {}, content); 1234 await ContentTaskUtils.waitForCondition( 1235 () => netErrorCard.advancedContainer, 1236 "Wait for the advanced container." 1237 ); 1238 1239 const hideExceptionButton = netErrorCard.shouldHideExceptionButton(); 1240 if (!hideExceptionButton) { 1241 await ContentTaskUtils.waitForCondition( 1242 () => 1243 netErrorCard.exceptionButton && 1244 !netErrorCard.exceptionButton.disabled, 1245 "Wait for the exception button to be created." 1246 ); 1247 } 1248 1249 // Assert Error Code 1250 const certErrorCodeLink = netErrorCard.errorCode; 1251 Assert.equal( 1252 certErrorCodeLink.textContent, 1253 `Error Code: SEC_ERROR_EXPIRED_CERTIFICATE`, 1254 "Error Code is as expected" 1255 ); 1256 Assert.equal( 1257 certErrorCodeLink.tagName.toLowerCase(), 1258 "a", 1259 "Error Code is a link" 1260 ); 1261 }); 1262 BrowserTestUtils.removeTab(gBrowser.selectedTab); 1263 });