/* Any copyright is dedicated to the Public Domain. * http://creativecommons.org/publicdomain/zero/1.0/ */ "use strict"; const SSL3_PAGE = "https://ssl3.example.com/"; const TLS10_PAGE = "https://tls1.example.com/"; const TLS12_PAGE = "https://tls12.example.com/"; const TRIPLEDES_PAGE = "https://3des.example.com/"; const lazy = {}; XPCOMUtils.defineLazyServiceGetter( lazy, "gDNSOverride", "@mozilla.org/network/native-dns-override;1", Ci.nsINativeDNSResolverOverride ); const DES_PREF = "security.ssl3.deprecated.rsa_des_ede3_sha"; // This includes all the cipher suite prefs we have. function resetPrefs() { Services.prefs.clearUserPref("security.tls.version.min"); Services.prefs.clearUserPref("security.tls.version.max"); Services.prefs.clearUserPref("security.tls.version.enable-deprecated"); Services.prefs.clearUserPref("browser.fixup.alternate.enabled"); Services.prefs.clearUserPref(DES_PREF); } add_task(async function resetToDefaultConfig() { info( "Change TLS config to cause page load to fail, check that reset button is shown and that it works" ); // Set ourselves up for a TLS error. Services.prefs.setIntPref("security.tls.version.min", 1); // TLS 1.0 Services.prefs.setIntPref("security.tls.version.max", 1); let browser; let pageLoaded; await BrowserTestUtils.openNewForegroundTab( gBrowser, () => { gBrowser.selectedTab = BrowserTestUtils.addTab(gBrowser, TLS12_PAGE); browser = gBrowser.selectedBrowser; pageLoaded = BrowserTestUtils.waitForErrorPage(browser); }, false ); info("Loading and waiting for the net error"); await pageLoaded; // Setup an observer for the target page. const finalLoadComplete = BrowserTestUtils.browserLoaded( browser, false, TLS12_PAGE ); await SpecialPowers.spawn(browser, [], async function () { const doc = content.document; ok( doc.documentURI.startsWith("about:neterror"), "Should be showing error page" ); const netErrorCard = await ContentTaskUtils.waitForCondition( () => content.document.querySelector("net-error-card")?.wrappedJSObject ); netErrorCard.advancedButton.scrollIntoView(true); EventUtils.synthesizeMouseAtCenter( netErrorCard.advancedButton, {}, content ); await ContentTaskUtils.waitForCondition( () => ContentTaskUtils.isVisible(netErrorCard.prefResetButton), "prefResetButton is visible" ); if (!Services.focus.focusedElement == netErrorCard.prefResetButton) { await ContentTaskUtils.waitForEvent( netErrorCard.prefResetButton, "focus" ); } Assert.ok(true, "prefResetButton has focus"); EventUtils.synthesizeMouseAtCenter( netErrorCard.prefResetButton, {}, content ); }); info("Waiting for the page to load after the click"); await finalLoadComplete; resetPrefs(); BrowserTestUtils.removeTab(gBrowser.selectedTab); }); add_task(async function checkLearnMoreLink() { info("Load an unsupported TLS page and check for a learn more link"); // Set ourselves up for TLS error Services.prefs.setIntPref("security.tls.version.min", 3); Services.prefs.setIntPref("security.tls.version.max", 4); let browser; let pageLoaded; await BrowserTestUtils.openNewForegroundTab( gBrowser, () => { gBrowser.selectedTab = BrowserTestUtils.addTab(gBrowser, TLS10_PAGE); browser = gBrowser.selectedBrowser; pageLoaded = BrowserTestUtils.waitForErrorPage(browser); }, false ); info("Loading and waiting for the net error"); await pageLoaded; const baseURL = Services.urlFormatter.formatURLPref("app.support.baseURL"); await SpecialPowers.spawn(browser, [baseURL], async function (_baseURL) { const doc = content.document; ok( doc.documentURI.startsWith("about:neterror"), "Should be showing error page" ); const netErrorCard = await ContentTaskUtils.waitForCondition( () => content.document.querySelector("net-error-card")?.wrappedJSObject ); netErrorCard.advancedButton.scrollIntoView(true); EventUtils.synthesizeMouseAtCenter( netErrorCard.advancedButton, {}, content ); const tlsVersionNotice = await ContentTaskUtils.waitForCondition( () => netErrorCard.tlsNotice ); ok( ContentTaskUtils.isVisible(tlsVersionNotice), "TLS version notice is visible" ); const learnMoreLink = netErrorCard.learnMoreLink; ok(ContentTaskUtils.isVisible(learnMoreLink), "Learn More link is visible"); is(learnMoreLink.getAttribute("href"), _baseURL + "connection-not-secure"); const titleEl = netErrorCard.errorTitle; const actualDataL10nID = titleEl.getAttribute("data-l10n-id"); is( actualDataL10nID, "nssFailure2-title", "Correct error page title is set" ); const errorCodeEl = netErrorCard.errorIntro.children[0]; is( errorCodeEl.getAttribute("data-l10n-id"), "cert-error-ssl-connection-error", "Correct error code is set" ); }); resetPrefs(); BrowserTestUtils.removeTab(gBrowser.selectedTab); }); // When a user tries going to a host without a suffix // and the term doesn't match a host and we are able to suggest a // valid correction, the page should show the correction. // e.g. http://example/example2 -> https://www.example.com/example2 add_task(async function checkDomainCorrectionReplacesLearnMoreLink() { await SpecialPowers.pushPrefEnv({ set: [["browser.fixup.alternate.enabled", false]], }); lazy.gDNSOverride.addIPOverride("www.example.com", "::1"); info("Try loading a URI that should result in an error page"); BrowserTestUtils.openNewForegroundTab( gBrowser, // eslint-disable-next-line sdl/no-insecure-url "http://example/example2/", false ); info("Loading and waiting for the net error"); let browser = gBrowser.selectedBrowser; let pageLoaded = BrowserTestUtils.waitForErrorPage(browser); await pageLoaded; const baseURL = Services.urlFormatter.formatURLPref("app.support.baseURL"); await SpecialPowers.spawn(browser, [baseURL], async function (_baseURL) { const doc = content.document; ok( doc.documentURI.startsWith("about:neterror"), "Should be showing error page" ); const netErrorCard = await ContentTaskUtils.waitForCondition( () => doc.querySelector("net-error-card")?.wrappedJSObject ); await netErrorCard.getUpdateComplete(); const errorIntro = netErrorCard.errorIntro; ok(ContentTaskUtils.isVisible(errorIntro), "Error intro text is visible"); let suggestionLink; await ContentTaskUtils.waitForCondition(() => { suggestionLink = netErrorCard.dnsSuggestion?.querySelector("a"); return ( suggestionLink?.textContent != "" && suggestionLink?.getAttribute("href") === "https://www.example.com/example2/" ); }, "www suggestion appears in intro text with correct link"); is( suggestionLink.getAttribute("href"), "https://www.example.com/example2/", "Suggestion link points to www variant" ); const learnMoreLink = netErrorCard.learnMoreLink; ok(learnMoreLink, "Learn more link is present"); is( learnMoreLink.getAttribute("href"), _baseURL + "server-not-found-connection-problem", "Learn more link still points to SUMO page" ); }); lazy.gDNSOverride.clearHostOverride("www.example.com"); resetPrefs(); // This page records an alternate_host_suggested impression in its content // process. Flush while the tab is still open: a closing tab's process drops // out of the list testFlushAllChildren() walks (bug 1843178), so anything // still buffered reaches the parent only when the process finally exits, // which on a slow machine is after the next task has cleared FOG. await Services.fog.testFlushAllChildren(); BrowserTestUtils.removeTab(gBrowser.selectedTab); }); // Impression and click telemetry for the alternate-host suggestion // (bug 2058380). Driven through the real injection path rather than a // hand-built span, so a suggestion that renders without its click listener // attached fails here. add_task(async function checkDomainCorrectionTelemetry() { // Drain before clearing, so data still buffered in a live content process // does not arrive with our own flush and double-count. The task above also // has to flush before it closes its tab, which this cannot do for it. await Services.fog.testFlushAllChildren(); Services.fog.testResetFOG(); await SpecialPowers.pushPrefEnv({ set: [["browser.fixup.alternate.enabled", false]], }); lazy.gDNSOverride.addIPOverride("www.example.com", "::1"); BrowserTestUtils.openNewForegroundTab( gBrowser, // eslint-disable-next-line sdl/no-insecure-url "http://example/example2/", false ); let browser = gBrowser.selectedBrowser; await BrowserTestUtils.waitForErrorPage(browser); await waitForSettledNetErrorCard(browser); const suggestionCount = await SpecialPowers.spawn(browser, [], async () => { const netErrorCard = content.document.querySelector("net-error-card").wrappedJSObject; // The suggestion is injected by NetErrorChild off the back of its own async // DNS lookup, which is independent of the card's render, so it still needs // a wait of its own after the card has settled. await ContentTaskUtils.waitForCondition( () => netErrorCard.dnsSuggestion?.querySelector("a"), "The suggestion link is injected" ); return netErrorCard.shadowRoot.querySelectorAll("#dns-suggestion").length; }); await Services.fog.testFlushAllChildren(); // Asserted separately from the counter so that a page which really did // render two suggestions is not mistaken for a telemetry bug. is(suggestionCount, 1, "The page holds a single suggestion"); is( Glean.securityUiNeterror.alternateHostSuggested.testGetValue(), 1, "The suggestion impression is recorded once" ); is( Glean.securityUiNeterror.clickDnsSuggestionLink.testGetValue(), null, "No click is recorded before the user clicks" ); // Cancel the navigation so the click is measured without leaving the page. await SpecialPowers.spawn(browser, [], async () => { const doc = content.document; doc.addEventListener("click", e => e.preventDefault(), { capture: true }); doc .querySelector("net-error-card") .wrappedJSObject.dnsSuggestion.querySelector("a") .click(); }); await Services.fog.testFlushAllChildren(); const events = Glean.securityUiNeterror.clickDnsSuggestionLink.testGetValue(); is(events?.length, 1, "The suggestion click is recorded once"); is(String(events[0].extra.is_frame), "false", "is_frame recorded"); ok( !JSON.stringify(events[0].extra).includes("example"), `No host reaches the event extras (got ${JSON.stringify(events[0].extra)})` ); is( Glean.securityUiNeterror.alternateHostSuggested.testGetValue(), 1, "Clicking does not add a second impression" ); lazy.gDNSOverride.clearHostOverride("www.example.com"); resetPrefs(); BrowserTestUtils.removeTab(gBrowser.selectedTab); }); // When a user tries to access a non-existent domain and no domain // suggestion is available, the learn more link should point to the // SUMO support page for DNS troubleshooting. add_task(async function checkDnsNotFoundLearnMoreLink() { info("Load a non-existent domain and check the learn more link"); BrowserTestUtils.openNewForegroundTab( gBrowser, // eslint-disable-next-line sdl/no-insecure-url "http://thisdomaindoesnotexist123456.test/", false ); let browser = gBrowser.selectedBrowser; let pageLoaded = BrowserTestUtils.waitForErrorPage(browser); await pageLoaded; const baseURL = Services.urlFormatter.formatURLPref("app.support.baseURL"); await SpecialPowers.spawn(browser, [baseURL], async function (_baseURL) { const doc = content.document; const netErrorCard = await ContentTaskUtils.waitForCondition( () => doc.querySelector("net-error-card")?.wrappedJSObject ); let learnMoreLink; await ContentTaskUtils.waitForCondition(() => { learnMoreLink = netErrorCard.learnMoreLink; return learnMoreLink && learnMoreLink.textContent != ""; }, "Learn more link has been set"); ok(ContentTaskUtils.isVisible(learnMoreLink), "Learn More link is visible"); is( learnMoreLink.getAttribute("href"), _baseURL + "server-not-found-connection-problem", "Link points to SUMO DNS troubleshooting page" ); }); BrowserTestUtils.removeTab(gBrowser.selectedTab); }); // Test that ciphersuites that use 3DES (namely, TLS_RSA_WITH_3DES_EDE_CBC_SHA) // can only be enabled when deprecated TLS is enabled. add_task(async function onlyAllow3DESWithDeprecatedTLS() { // By default, connecting to a server that only uses 3DES should fail. await BrowserTestUtils.withNewTab( { gBrowser, url: "about:blank" }, async browser => { BrowserTestUtils.startLoadingURIString(browser, TRIPLEDES_PAGE); await BrowserTestUtils.waitForErrorPage(browser); } ); // Enabling deprecated TLS should also enable 3DES. Services.prefs.setBoolPref("security.tls.version.enable-deprecated", true); await BrowserTestUtils.withNewTab( { gBrowser, url: "about:blank" }, async browser => { BrowserTestUtils.startLoadingURIString(browser, TRIPLEDES_PAGE); await BrowserTestUtils.browserLoaded(browser, false, TRIPLEDES_PAGE); } ); for (let feltPrivacy of [true, false]) { // 3DES can be disabled separately. Services.prefs.setBoolPref(DES_PREF, false); await SpecialPowers.pushPrefEnv({ set: [["security.certerrors.felt-privacy-v1", feltPrivacy]], }); await BrowserTestUtils.withNewTab( { gBrowser, url: "about:blank" }, async browser => { BrowserTestUtils.startLoadingURIString(browser, TRIPLEDES_PAGE); await BrowserTestUtils.waitForErrorPage(browser); let prefWasReset = TestUtils.waitForPrefChange(DES_PREF); if (feltPrivacy) { await SpecialPowers.spawn(browser, [], async function () { const doc = content.document; const netErrorCard = doc.querySelector("net-error-card")?.wrappedJSObject; Assert.ok(netErrorCard, "netErrorCard is rendered."); netErrorCard.advancedButton.scrollIntoView(); EventUtils.synthesizeMouseAtCenter( netErrorCard.advancedButton, {}, content ); await ContentTaskUtils.waitForCondition( () => ContentTaskUtils.isVisible(netErrorCard.advancedContainer), "Advanced container is visible" ); const prefResetButton = netErrorCard.prefResetButton; Assert.ok(prefResetButton, "prefResetButton exists in the DOM."); netErrorCard.prefResetButton.scrollIntoView(); await ContentTaskUtils.waitForCondition( () => ContentTaskUtils.isVisible(netErrorCard.prefResetButton), "Pref reset button is visible" ); ok( ContentTaskUtils.isVisible(prefResetButton), "prefResetButton is visible" ); prefResetButton.click(); }); } else { await SpecialPowers.spawn(browser, [], async function () { const doc = content.document; const prefResetButton = doc.getElementById("prefResetButton"); Assert.ok(prefResetButton, "prefResetButton exists in the DOM."); await ContentTaskUtils.waitForCondition( () => ContentTaskUtils.isVisible(prefResetButton), "Pref reset button is visible" ); ok( ContentTaskUtils.isVisible(prefResetButton), "prefResetButton is visible" ); prefResetButton.click(); }); } await prefWasReset; } ); } resetPrefs(); }); add_task(async function test_tryAgainButtonAutofocus() { Services.io.offline = true; registerCleanupFunction(() => { Services.io.offline = false; }); let proxyPrefValue = SpecialPowers.getIntPref("network.proxy.type"); await SpecialPowers.pushPrefEnv({ set: [ ["network.proxy.type", 0], ["browser.cache.disk.enable", false], ["browser.cache.memory.enable", false], ["security.certerrors.felt-privacy-v1", true], ], }); await BrowserTestUtils.withNewTab("about:blank", async function (browser) { let netErrorLoaded = BrowserTestUtils.waitForErrorPage(browser); // eslint-disable-next-line sdl/no-insecure-url BrowserTestUtils.startLoadingURIString(browser, "http://example.com/"); await netErrorLoaded; await SpecialPowers.pushPrefEnv({ set: [["network.proxy.type", proxyPrefValue]], }); await SpecialPowers.spawn(browser, [], async function () { const netErrorCard = content.document.querySelector("net-error-card").wrappedJSObject; await netErrorCard.getUpdateComplete(); const tryAgainButton = netErrorCard.tryAgainButton; Assert.ok(tryAgainButton, "tryAgainButton exists"); await tryAgainButton.updateComplete; Assert.equal( netErrorCard.renderRoot.activeElement, tryAgainButton, "tryAgainButton has focus" ); }); }); await SpecialPowers.popPrefEnv(); });