mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-27 06:43:32 +00:00
Bug 1873514 - Update about:neterror appearance and strings. r=Gijs,fluent-reviewers,desktop-theme-reviewers,bolsson,sfoster
Differential Revision: https://phabricator.services.mozilla.com/D217621
This commit is contained in:
parent
833dde518e
commit
5319e76bba
@ -2654,6 +2654,7 @@ pref("browser.toolbars.bookmarks.showOtherBookmarks", true);
|
||||
|
||||
// Felt Privacy pref to control simplified private browsing UI
|
||||
pref("browser.privatebrowsing.felt-privacy-v1", false);
|
||||
pref("security.certerrors.felt-privacy-v1", false);
|
||||
|
||||
// Prefs to control the Firefox Account toolbar menu.
|
||||
// This pref will surface existing Firefox Account information
|
||||
|
@ -10,68 +10,76 @@
|
||||
// dialog, using that to add an exception, and finally successfully visiting
|
||||
// the site, including showing the right identity box and control center icons.
|
||||
add_task(async function () {
|
||||
await BrowserTestUtils.openNewForegroundTab(gBrowser);
|
||||
await loadBadCertPage("https://expired.example.com");
|
||||
for (let feltPrivacyEnabled of [true, false]) {
|
||||
await SpecialPowers.pushPrefEnv({
|
||||
set: [["security.certerrors.felt-privacy-v1", feltPrivacyEnabled]],
|
||||
});
|
||||
|
||||
let { gIdentityHandler } = gBrowser.ownerGlobal;
|
||||
let promisePanelOpen = BrowserTestUtils.waitForEvent(
|
||||
gBrowser.ownerGlobal,
|
||||
"popupshown",
|
||||
true,
|
||||
event => event.target == gIdentityHandler._identityPopup
|
||||
);
|
||||
gIdentityHandler._identityIconBox.click();
|
||||
await promisePanelOpen;
|
||||
await BrowserTestUtils.openNewForegroundTab(gBrowser);
|
||||
await loadBadCertPage("https://expired.example.com", feltPrivacyEnabled);
|
||||
|
||||
let promiseViewShown = BrowserTestUtils.waitForEvent(
|
||||
gIdentityHandler._identityPopup,
|
||||
"ViewShown"
|
||||
);
|
||||
document.getElementById("identity-popup-security-button").click();
|
||||
await promiseViewShown;
|
||||
let { gIdentityHandler } = gBrowser.ownerGlobal;
|
||||
let promisePanelOpen = BrowserTestUtils.waitForEvent(
|
||||
gBrowser.ownerGlobal,
|
||||
"popupshown",
|
||||
true,
|
||||
event => event.target == gIdentityHandler._identityPopup
|
||||
);
|
||||
gIdentityHandler._identityIconBox.click();
|
||||
await promisePanelOpen;
|
||||
|
||||
is_element_visible(
|
||||
document.getElementById("identity-icon"),
|
||||
"Should see identity icon"
|
||||
);
|
||||
let identityIconImage = gBrowser.ownerGlobal
|
||||
.getComputedStyle(document.getElementById("identity-icon"))
|
||||
.getPropertyValue("list-style-image");
|
||||
let securityViewBG = gBrowser.ownerGlobal
|
||||
.getComputedStyle(
|
||||
document
|
||||
.getElementById("identity-popup-securityView")
|
||||
.getElementsByClassName("identity-popup-security-connection")[0]
|
||||
)
|
||||
.getPropertyValue("list-style-image");
|
||||
let securityContentBG = gBrowser.ownerGlobal
|
||||
.getComputedStyle(
|
||||
document
|
||||
.getElementById("identity-popup-mainView")
|
||||
.getElementsByClassName("identity-popup-security-connection")[0]
|
||||
)
|
||||
.getPropertyValue("list-style-image");
|
||||
is(
|
||||
identityIconImage,
|
||||
'url("chrome://global/skin/icons/security-warning.svg")',
|
||||
"Using expected icon image in the identity block"
|
||||
);
|
||||
is(
|
||||
securityViewBG,
|
||||
'url("chrome://global/skin/icons/security-warning.svg")',
|
||||
"Using expected icon image in the Control Center main view"
|
||||
);
|
||||
is(
|
||||
securityContentBG,
|
||||
'url("chrome://global/skin/icons/security-warning.svg")',
|
||||
"Using expected icon image in the Control Center subview"
|
||||
);
|
||||
let promiseViewShown = BrowserTestUtils.waitForEvent(
|
||||
gIdentityHandler._identityPopup,
|
||||
"ViewShown"
|
||||
);
|
||||
document.getElementById("identity-popup-security-button").click();
|
||||
await promiseViewShown;
|
||||
|
||||
gIdentityHandler._identityPopup.hidePopup();
|
||||
is_element_visible(
|
||||
document.getElementById("identity-icon"),
|
||||
"Should see identity icon"
|
||||
);
|
||||
let identityIconImage = gBrowser.ownerGlobal
|
||||
.getComputedStyle(document.getElementById("identity-icon"))
|
||||
.getPropertyValue("list-style-image");
|
||||
let securityViewBG = gBrowser.ownerGlobal
|
||||
.getComputedStyle(
|
||||
document
|
||||
.getElementById("identity-popup-securityView")
|
||||
.getElementsByClassName("identity-popup-security-connection")[0]
|
||||
)
|
||||
.getPropertyValue("list-style-image");
|
||||
let securityContentBG = gBrowser.ownerGlobal
|
||||
.getComputedStyle(
|
||||
document
|
||||
.getElementById("identity-popup-mainView")
|
||||
.getElementsByClassName("identity-popup-security-connection")[0]
|
||||
)
|
||||
.getPropertyValue("list-style-image");
|
||||
is(
|
||||
identityIconImage,
|
||||
'url("chrome://global/skin/icons/security-warning.svg")',
|
||||
"Using expected icon image in the identity block"
|
||||
);
|
||||
is(
|
||||
securityViewBG,
|
||||
'url("chrome://global/skin/icons/security-warning.svg")',
|
||||
"Using expected icon image in the Control Center main view"
|
||||
);
|
||||
is(
|
||||
securityContentBG,
|
||||
'url("chrome://global/skin/icons/security-warning.svg")',
|
||||
"Using expected icon image in the Control Center subview"
|
||||
);
|
||||
|
||||
let certOverrideService = Cc[
|
||||
"@mozilla.org/security/certoverride;1"
|
||||
].getService(Ci.nsICertOverrideService);
|
||||
certOverrideService.clearValidityOverride("expired.example.com", -1, {});
|
||||
BrowserTestUtils.removeTab(gBrowser.selectedTab);
|
||||
gIdentityHandler._identityPopup.hidePopup();
|
||||
|
||||
let certOverrideService = Cc[
|
||||
"@mozilla.org/security/certoverride;1"
|
||||
].getService(Ci.nsICertOverrideService);
|
||||
certOverrideService.clearValidityOverride("expired.example.com", -1, {});
|
||||
BrowserTestUtils.removeTab(gBrowser.selectedTab);
|
||||
|
||||
await SpecialPowers.popPrefEnv();
|
||||
}
|
||||
});
|
||||
|
@ -1,59 +1,89 @@
|
||||
function remote(task) {
|
||||
return SpecialPowers.spawn(gBrowser.selectedBrowser, [], task);
|
||||
}
|
||||
|
||||
add_task(async function () {
|
||||
gBrowser.selectedTab = BrowserTestUtils.addTab(gBrowser);
|
||||
for (let feltPrivacyEnabled of [false, true]) {
|
||||
await SpecialPowers.pushPrefEnv({
|
||||
set: [["security.certerrors.felt-privacy-v1", feltPrivacyEnabled]],
|
||||
});
|
||||
|
||||
let promise = BrowserTestUtils.waitForErrorPage(gBrowser.selectedBrowser);
|
||||
BrowserTestUtils.startLoadingURIString(
|
||||
gBrowser,
|
||||
"https://nocert.example.com/"
|
||||
);
|
||||
await promise;
|
||||
gBrowser.selectedTab = BrowserTestUtils.addTab(gBrowser);
|
||||
|
||||
await remote(() => {
|
||||
// Confirm that we are displaying the contributed error page, not the default
|
||||
let uri = content.document.documentURI;
|
||||
Assert.ok(
|
||||
uri.startsWith("about:certerror"),
|
||||
"Broken page should go to about:certerror, not about:neterror"
|
||||
let promise = BrowserTestUtils.waitForErrorPage(gBrowser.selectedBrowser);
|
||||
BrowserTestUtils.startLoadingURIString(
|
||||
gBrowser,
|
||||
"https://nocert.example.com/"
|
||||
);
|
||||
});
|
||||
await promise;
|
||||
|
||||
await remote(() => {
|
||||
let div = content.document.getElementById("badCertAdvancedPanel");
|
||||
// Confirm that the expert section is collapsed
|
||||
Assert.ok(div, "Advanced content div should exist");
|
||||
Assert.equal(
|
||||
div.ownerGlobal.getComputedStyle(div).display,
|
||||
"none",
|
||||
"Advanced content should not be visible by default"
|
||||
);
|
||||
});
|
||||
await SpecialPowers.spawn(gBrowser.selectedBrowser, [], async () => {
|
||||
// Confirm that we are displaying the contributed error page, not the default
|
||||
let uri = content.document.documentURI;
|
||||
Assert.ok(
|
||||
uri.startsWith("about:certerror"),
|
||||
"Broken page should go to about:certerror, not about:neterror"
|
||||
);
|
||||
});
|
||||
|
||||
// Tweak the expert mode pref
|
||||
Services.prefs.setBoolPref("browser.xul.error_pages.expert_bad_cert", true);
|
||||
if (feltPrivacyEnabled) {
|
||||
await SpecialPowers.spawn(gBrowser.selectedBrowser, [], async () => {
|
||||
let netErrorCard =
|
||||
content.document.querySelector("net-error-card").wrappedJSObject;
|
||||
await netErrorCard.getUpdateComplete();
|
||||
Assert.ok(
|
||||
!netErrorCard.advancedShowing,
|
||||
"Advanced showing attribute should be true"
|
||||
);
|
||||
Assert.ok(!netErrorCard.advancedContainer);
|
||||
});
|
||||
} else {
|
||||
await SpecialPowers.spawn(gBrowser.selectedBrowser, [], async () => {
|
||||
let div = content.document.getElementById("badCertAdvancedPanel");
|
||||
// Confirm that the expert section is collapsed
|
||||
Assert.ok(div, "Advanced content div should exist");
|
||||
Assert.equal(
|
||||
div.ownerGlobal.getComputedStyle(div).display,
|
||||
"none",
|
||||
"Advanced content should not be visible by default"
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
promise = BrowserTestUtils.waitForErrorPage(gBrowser.selectedBrowser);
|
||||
gBrowser.reload();
|
||||
await promise;
|
||||
// Tweak the expert mode pref
|
||||
Services.prefs.setBoolPref("browser.xul.error_pages.expert_bad_cert", true);
|
||||
|
||||
await remote(() => {
|
||||
let div = content.document.getElementById("badCertAdvancedPanel");
|
||||
Assert.ok(div, "Advanced content div should exist");
|
||||
Assert.equal(
|
||||
div.ownerGlobal.getComputedStyle(div).display,
|
||||
"block",
|
||||
"Advanced content should be visible by default"
|
||||
);
|
||||
});
|
||||
promise = BrowserTestUtils.waitForErrorPage(gBrowser.selectedBrowser);
|
||||
gBrowser.reload();
|
||||
await promise;
|
||||
|
||||
// Clean up
|
||||
gBrowser.removeCurrentTab();
|
||||
if (
|
||||
Services.prefs.prefHasUserValue("browser.xul.error_pages.expert_bad_cert")
|
||||
) {
|
||||
Services.prefs.clearUserPref("browser.xul.error_pages.expert_bad_cert");
|
||||
if (feltPrivacyEnabled) {
|
||||
await SpecialPowers.spawn(gBrowser.selectedBrowser, [], async () => {
|
||||
let netErrorCard =
|
||||
content.document.querySelector("net-error-card").wrappedJSObject;
|
||||
await netErrorCard.getUpdateComplete();
|
||||
Assert.ok(
|
||||
netErrorCard.advancedShowing,
|
||||
"Advanced showing attribute should be true"
|
||||
);
|
||||
Assert.ok(ContentTaskUtils.isVisible(netErrorCard.advancedContainer));
|
||||
});
|
||||
} else {
|
||||
await SpecialPowers.spawn(gBrowser.selectedBrowser, [], async () => {
|
||||
let div = content.document.getElementById("badCertAdvancedPanel");
|
||||
Assert.ok(div, "Advanced content div should exist");
|
||||
Assert.equal(
|
||||
div.ownerGlobal.getComputedStyle(div).display,
|
||||
"block",
|
||||
"Advanced content should be visible by default"
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
// Clean up
|
||||
gBrowser.removeCurrentTab();
|
||||
if (
|
||||
Services.prefs.prefHasUserValue("browser.xul.error_pages.expert_bad_cert")
|
||||
) {
|
||||
Services.prefs.clearUserPref("browser.xul.error_pages.expert_bad_cert");
|
||||
}
|
||||
|
||||
await SpecialPowers.popPrefEnv();
|
||||
}
|
||||
});
|
||||
|
@ -289,13 +289,31 @@ function promiseOnBookmarkItemAdded(aExpectedURI) {
|
||||
});
|
||||
}
|
||||
|
||||
async function loadBadCertPage(url) {
|
||||
async function loadBadCertPage(url, feltPrivacy = false) {
|
||||
let loaded = BrowserTestUtils.waitForErrorPage(gBrowser.selectedBrowser);
|
||||
BrowserTestUtils.startLoadingURIString(gBrowser.selectedBrowser, url);
|
||||
await loaded;
|
||||
|
||||
await SpecialPowers.spawn(gBrowser.selectedBrowser, [], async function () {
|
||||
content.document.getElementById("exceptionDialogButton").click();
|
||||
});
|
||||
await SpecialPowers.spawn(
|
||||
gBrowser.selectedBrowser,
|
||||
[feltPrivacy],
|
||||
async isFeltPrivacy => {
|
||||
if (isFeltPrivacy) {
|
||||
let netErrorCard =
|
||||
content.document.querySelector("net-error-card").wrappedJSObject;
|
||||
await netErrorCard.getUpdateComplete();
|
||||
netErrorCard.advancedButton.click();
|
||||
await ContentTaskUtils.waitForCondition(() => {
|
||||
return (
|
||||
netErrorCard.exceptionButton &&
|
||||
!netErrorCard.exceptionButton.disabled
|
||||
);
|
||||
}, "Waiting for exception button");
|
||||
netErrorCard.exceptionButton.click();
|
||||
} else {
|
||||
content.document.getElementById("exceptionDialogButton").click();
|
||||
}
|
||||
}
|
||||
);
|
||||
await BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser);
|
||||
}
|
||||
|
@ -7,39 +7,70 @@
|
||||
// with www. when we encounter a SSL_ERROR_BAD_CERT_DOMAIN error.
|
||||
// For example, https://example.com -> https://www.example.com.
|
||||
|
||||
async function verifyErrorPage(errorPageURL) {
|
||||
async function verifyErrorPage(errorPageURL, feltPrivacy = false) {
|
||||
let certErrorLoaded = BrowserTestUtils.waitForErrorPage(
|
||||
gBrowser.selectedBrowser
|
||||
);
|
||||
BrowserTestUtils.startLoadingURIString(gBrowser, errorPageURL);
|
||||
await certErrorLoaded;
|
||||
|
||||
await SpecialPowers.spawn(gBrowser.selectedBrowser, [], async function () {
|
||||
let ec;
|
||||
await ContentTaskUtils.waitForCondition(() => {
|
||||
ec = content.document.getElementById("errorCode");
|
||||
return ec.textContent;
|
||||
}, "Error code has been set inside the advanced button panel");
|
||||
is(
|
||||
ec.textContent,
|
||||
"SSL_ERROR_BAD_CERT_DOMAIN",
|
||||
"Correct error code is shown"
|
||||
);
|
||||
});
|
||||
await SpecialPowers.spawn(
|
||||
gBrowser.selectedBrowser,
|
||||
[feltPrivacy],
|
||||
async isFeltPrivacy => {
|
||||
let ec;
|
||||
if (isFeltPrivacy) {
|
||||
let netErrorCard =
|
||||
content.document.querySelector("net-error-card").wrappedJSObject;
|
||||
await netErrorCard.getUpdateComplete();
|
||||
netErrorCard.advancedButton.click();
|
||||
await ContentTaskUtils.waitForCondition(() => {
|
||||
return (ec = netErrorCard.errorCode);
|
||||
}, "Error code has been set inside the net-error-card advanced panel");
|
||||
|
||||
is(
|
||||
ec.textContent.split(" ").at(-1),
|
||||
"SSL_ERROR_BAD_CERT_DOMAIN",
|
||||
"Correct error code is shown"
|
||||
);
|
||||
} else {
|
||||
await ContentTaskUtils.waitForCondition(() => {
|
||||
ec = content.document.getElementById("errorCode");
|
||||
return ec.textContent;
|
||||
}, "Error code has been set inside the advanced button panel");
|
||||
is(
|
||||
ec.textContent,
|
||||
"SSL_ERROR_BAD_CERT_DOMAIN",
|
||||
"Correct error code is shown"
|
||||
);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
// Turn off the pref and ensure that we show the error page as expected.
|
||||
add_task(async function testNoFixupDisabledByPref() {
|
||||
await SpecialPowers.pushPrefEnv({
|
||||
set: [["security.bad_cert_domain_error.url_fix_enabled", false]],
|
||||
});
|
||||
gBrowser.selectedTab = BrowserTestUtils.addTab(gBrowser);
|
||||
for (let feltPrivacyEnabled of [true, false]) {
|
||||
await SpecialPowers.pushPrefEnv({
|
||||
set: [
|
||||
["security.bad_cert_domain_error.url_fix_enabled", false],
|
||||
["security.certerrors.felt-privacy-v1", feltPrivacyEnabled],
|
||||
],
|
||||
});
|
||||
gBrowser.selectedTab = BrowserTestUtils.addTab(gBrowser);
|
||||
|
||||
await verifyErrorPage("https://badcertdomain.example.com");
|
||||
await verifyErrorPage("https://www.badcertdomain2.example.com");
|
||||
await verifyErrorPage(
|
||||
"https://badcertdomain.example.com",
|
||||
feltPrivacyEnabled
|
||||
);
|
||||
await verifyErrorPage(
|
||||
"https://www.badcertdomain2.example.com",
|
||||
feltPrivacyEnabled
|
||||
);
|
||||
|
||||
BrowserTestUtils.removeTab(gBrowser.selectedTab);
|
||||
await SpecialPowers.popPrefEnv();
|
||||
BrowserTestUtils.removeTab(gBrowser.selectedTab);
|
||||
await SpecialPowers.popPrefEnv();
|
||||
}
|
||||
});
|
||||
|
||||
// Test that "www." is prefixed to a https url when we encounter a bad cert domain
|
||||
@ -63,22 +94,35 @@ add_task(async function testAddPrefixForBadCertDomain() {
|
||||
// Test that we don't prefix "www." to a https url when we encounter a bad cert domain
|
||||
// error under certain conditions.
|
||||
add_task(async function testNoFixupCases() {
|
||||
gBrowser.selectedTab = BrowserTestUtils.addTab(gBrowser);
|
||||
for (let feltPrivacyEnabled of [true, false]) {
|
||||
await SpecialPowers.pushPrefEnv({
|
||||
set: [["security.certerrors.felt-privacy-v1", feltPrivacyEnabled]],
|
||||
});
|
||||
gBrowser.selectedTab = BrowserTestUtils.addTab(gBrowser);
|
||||
|
||||
// Test for when "www." form is not present in the certificate.
|
||||
await verifyErrorPage("https://mismatch.badcertdomain.example.com");
|
||||
// Test for when "www." form is not present in the certificate.
|
||||
await verifyErrorPage(
|
||||
"https://mismatch.badcertdomain.example.com",
|
||||
feltPrivacyEnabled
|
||||
);
|
||||
|
||||
// Test that urls with IP addresses are not fixed.
|
||||
await SpecialPowers.pushPrefEnv({
|
||||
set: [["network.proxy.allow_hijacking_localhost", true]],
|
||||
});
|
||||
await verifyErrorPage("https://127.0.0.3:433");
|
||||
await SpecialPowers.popPrefEnv();
|
||||
// Test that urls with IP addresses are not fixed.
|
||||
await SpecialPowers.pushPrefEnv({
|
||||
set: [["network.proxy.allow_hijacking_localhost", true]],
|
||||
});
|
||||
await verifyErrorPage("https://127.0.0.3:433", feltPrivacyEnabled);
|
||||
await SpecialPowers.popPrefEnv();
|
||||
|
||||
// Test that urls with ports are not fixed.
|
||||
await verifyErrorPage("https://badcertdomain.example.com:82");
|
||||
// Test that urls with ports are not fixed.
|
||||
await verifyErrorPage(
|
||||
"https://badcertdomain.example.com:82",
|
||||
feltPrivacyEnabled
|
||||
);
|
||||
|
||||
BrowserTestUtils.removeTab(gBrowser.selectedTab);
|
||||
BrowserTestUtils.removeTab(gBrowser.selectedTab);
|
||||
|
||||
await SpecialPowers.popPrefEnv();
|
||||
}
|
||||
});
|
||||
|
||||
// Test removing "www." prefix if the "www."-less form is included in the
|
||||
|
@ -5,23 +5,27 @@
|
||||
/* eslint-env mozilla/remote-page */
|
||||
/* eslint-disable import/no-unassigned-import */
|
||||
|
||||
import { NetErrorCard } from "chrome://global/content/net-error-card.mjs";
|
||||
import {
|
||||
parse,
|
||||
pemToDER,
|
||||
} from "chrome://global/content/certviewer/certDecoder.mjs";
|
||||
gIsCertError,
|
||||
gErrorCode,
|
||||
gHasSts,
|
||||
searchParams,
|
||||
getHostName,
|
||||
getSubjectAltNames,
|
||||
getFailedCertificatesAsPEMString,
|
||||
recordSecurityUITelemetry,
|
||||
getCSSClass,
|
||||
} from "chrome://global/content/aboutNetErrorHelpers.mjs";
|
||||
|
||||
const formatter = new Intl.DateTimeFormat();
|
||||
|
||||
const HOST_NAME = getHostName();
|
||||
|
||||
function getHostName() {
|
||||
try {
|
||||
return new URL(RPMGetInnerMostURI(document.location.href)).hostname;
|
||||
} catch (error) {
|
||||
console.error("Could not parse URL", error);
|
||||
}
|
||||
return "";
|
||||
}
|
||||
const FELT_PRIVACY_REFRESH = RPMGetBoolPref(
|
||||
"security.certerrors.felt-privacy-v1",
|
||||
false
|
||||
);
|
||||
|
||||
// Used to check if we have a specific localized message for an error.
|
||||
const KNOWN_ERROR_TITLE_IDS = new Set([
|
||||
@ -66,25 +70,6 @@ const KNOWN_ERROR_TITLE_IDS = new Set([
|
||||
/* global KNOWN_ERROR_MESSAGE_IDS */
|
||||
const ERROR_MESSAGES_FTL = "toolkit/neterror/nsserrors.ftl";
|
||||
|
||||
// The following parameters are parsed from the error URL:
|
||||
// e - the error code
|
||||
// s - custom CSS class to allow alternate styling/favicons
|
||||
// d - error description
|
||||
// captive - "true" to indicate we're behind a captive portal.
|
||||
// Any other value is ignored.
|
||||
|
||||
// Note that this file uses document.documentURI to get
|
||||
// the URL (with the format from above). This is because
|
||||
// document.location.href gets the current URI off the docshell,
|
||||
// which is the URL displayed in the location bar, i.e.
|
||||
// the URI that the user attempted to load.
|
||||
|
||||
let searchParams = new URLSearchParams(document.documentURI.split("?")[1]);
|
||||
|
||||
let gErrorCode = searchParams.get("e");
|
||||
let gIsCertError = gErrorCode == "nssBadCert";
|
||||
let gHasSts = gIsCertError && getCSSClass() === "badStsCert";
|
||||
|
||||
// If the location of the favicon changes, FAVICON_CERTERRORPAGE_URL and/or
|
||||
// FAVICON_ERRORPAGE_URL in toolkit/components/places/nsFaviconService.idl
|
||||
// should also be updated.
|
||||
@ -93,10 +78,6 @@ document.getElementById("favicon").href =
|
||||
? "chrome://global/skin/icons/warning.svg"
|
||||
: "chrome://global/skin/icons/info.svg";
|
||||
|
||||
function getCSSClass() {
|
||||
return searchParams.get("s");
|
||||
}
|
||||
|
||||
function getDescription() {
|
||||
return searchParams.get("d");
|
||||
}
|
||||
@ -994,64 +975,6 @@ function initPageCertError() {
|
||||
setCertErrorDetails();
|
||||
}
|
||||
|
||||
async function recordSecurityUITelemetry(category, name, errorInfo) {
|
||||
// Truncate the error code to avoid going over the allowed
|
||||
// string size limit for telemetry events.
|
||||
let errorCode = errorInfo.errorCodeString.substring(0, 40);
|
||||
let extraKeys = {
|
||||
value: errorCode,
|
||||
is_frame: window.parent != window,
|
||||
};
|
||||
if (category == "securityUiCerterror") {
|
||||
extraKeys.has_sts = gHasSts;
|
||||
}
|
||||
if (name.startsWith("load")) {
|
||||
extraKeys.channel_status = errorInfo.channelStatus;
|
||||
}
|
||||
if (category == "securityUiCerterror" && name.startsWith("load")) {
|
||||
extraKeys.issued_by_cca = false;
|
||||
extraKeys.hyphen_compat = false;
|
||||
// This issue only applies to certificate domain name mismatch errors where
|
||||
// the first label in the domain name starts or ends with a hyphen.
|
||||
let label = HOST_NAME.substring(0, HOST_NAME.indexOf("."));
|
||||
if (
|
||||
errorCode == "SSL_ERROR_BAD_CERT_DOMAIN" &&
|
||||
(label.startsWith("-") || label.endsWith("-"))
|
||||
) {
|
||||
try {
|
||||
let subjectAltNames = await getSubjectAltNames(errorInfo);
|
||||
for (let subjectAltName of subjectAltNames) {
|
||||
// If the certificate has a wildcard entry that matches the domain
|
||||
// name (e.g. '*.example.com' matches 'foo-.example.com'), then
|
||||
// this error is probably due to Firefox disallowing hyphens in
|
||||
// domain names when matching wildcard entries.
|
||||
if (
|
||||
subjectAltName.startsWith("*.") &&
|
||||
subjectAltName.substring(1) == HOST_NAME.substring(label.length)
|
||||
) {
|
||||
extraKeys.hyphen_compat = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.error("error parsing certificate:", e);
|
||||
}
|
||||
}
|
||||
let issuer = errorInfo.certChainStrings.at(-1);
|
||||
if (issuer && errorCode == "SEC_ERROR_UNKNOWN_ISSUER") {
|
||||
try {
|
||||
let parsed = await parse(pemToDER(issuer));
|
||||
extraKeys.issued_by_cca =
|
||||
parsed.issuer.dn == "c=IN, o=India PKI, cn=CCA India 2022 SPL" ||
|
||||
parsed.issuer.dn == "c=IN, o=India PKI, cn=CCA India 2015 SPL";
|
||||
} catch (e) {
|
||||
console.error("error parsing issuer certificate:", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
RPMRecordGleanEvent(category, name, extraKeys);
|
||||
}
|
||||
|
||||
function recordClickTelemetry(e) {
|
||||
let target = e.originalTarget;
|
||||
let telemetryId = target.dataset.telemetryId;
|
||||
@ -1109,44 +1032,6 @@ function copyPEMToClipboard() {
|
||||
navigator.clipboard.writeText(errorText.textContent);
|
||||
}
|
||||
|
||||
async function getFailedCertificatesAsPEMString() {
|
||||
let locationUrl = document.location.href;
|
||||
let failedCertInfo = document.getFailedCertSecurityInfo();
|
||||
let errorMessage = failedCertInfo.errorMessage;
|
||||
let hasHSTS = failedCertInfo.hasHSTS.toString();
|
||||
let hasHPKP = failedCertInfo.hasHPKP.toString();
|
||||
let [hstsLabel, hpkpLabel, failedChainLabel] =
|
||||
await document.l10n.formatValues([
|
||||
{ id: "cert-error-details-hsts-label", args: { hasHSTS } },
|
||||
{ id: "cert-error-details-key-pinning-label", args: { hasHPKP } },
|
||||
{ id: "cert-error-details-cert-chain-label" },
|
||||
]);
|
||||
|
||||
let certStrings = failedCertInfo.certChainStrings;
|
||||
let failedChainCertificates = "";
|
||||
for (let der64 of certStrings) {
|
||||
let wrapped = der64.replace(/(\S{64}(?!$))/g, "$1\r\n");
|
||||
failedChainCertificates +=
|
||||
"-----BEGIN CERTIFICATE-----\r\n" +
|
||||
wrapped +
|
||||
"\r\n-----END CERTIFICATE-----\r\n";
|
||||
}
|
||||
|
||||
let details =
|
||||
locationUrl +
|
||||
"\r\n\r\n" +
|
||||
errorMessage +
|
||||
"\r\n\r\n" +
|
||||
hstsLabel +
|
||||
"\r\n" +
|
||||
hpkpLabel +
|
||||
"\r\n\r\n" +
|
||||
failedChainLabel +
|
||||
"\r\n\r\n" +
|
||||
failedChainCertificates;
|
||||
return details;
|
||||
}
|
||||
|
||||
function setCertErrorDetails() {
|
||||
// Check if the connection is being man-in-the-middled. When the parent
|
||||
// detects an intercepted connection, the page may be reloaded with a new
|
||||
@ -1380,21 +1265,6 @@ function setCertErrorDetails() {
|
||||
}
|
||||
}
|
||||
|
||||
async function getSubjectAltNames(failedCertInfo) {
|
||||
const serverCertBase64 = failedCertInfo.certChainStrings[0];
|
||||
const parsed = await parse(pemToDER(serverCertBase64));
|
||||
const subjectAltNamesExtension = parsed.ext.san;
|
||||
const subjectAltNames = [];
|
||||
if (subjectAltNamesExtension) {
|
||||
for (let [key, value] of subjectAltNamesExtension.altNames) {
|
||||
if (key === "DNS Name" && value.length) {
|
||||
subjectAltNames.push(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
return subjectAltNames;
|
||||
}
|
||||
|
||||
// The optional argument is only here for testing purposes.
|
||||
function setTechnicalDetailsOnCertError(
|
||||
failedCertInfo = document.getFailedCertSecurityInfo()
|
||||
@ -1603,13 +1473,36 @@ function setFocus(selector, position = "afterbegin") {
|
||||
}
|
||||
}
|
||||
|
||||
for (let button of document.querySelectorAll(".try-again")) {
|
||||
button.addEventListener("click", function () {
|
||||
retryThis(this);
|
||||
});
|
||||
function shouldUseFeltPrivacyRefresh() {
|
||||
if (!FELT_PRIVACY_REFRESH) {
|
||||
return false;
|
||||
}
|
||||
|
||||
let failedCertInfo;
|
||||
try {
|
||||
failedCertInfo = document.getFailedCertSecurityInfo();
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
|
||||
return NetErrorCard.ERROR_CODES.has(failedCertInfo.errorCodeString);
|
||||
}
|
||||
|
||||
initPage();
|
||||
if (!shouldUseFeltPrivacyRefresh()) {
|
||||
for (let button of document.querySelectorAll(".try-again")) {
|
||||
button.addEventListener("click", function () {
|
||||
retryThis(this);
|
||||
});
|
||||
}
|
||||
|
||||
// Dispatch this event so tests can detect that we finished loading the error page.
|
||||
document.dispatchEvent(new CustomEvent("AboutNetErrorLoad", { bubbles: true }));
|
||||
initPage();
|
||||
|
||||
// Dispatch this event so tests can detect that we finished loading the error page.
|
||||
document.dispatchEvent(
|
||||
new CustomEvent("AboutNetErrorLoad", { bubbles: true })
|
||||
);
|
||||
} else {
|
||||
customElements.define("net-error-card", NetErrorCard);
|
||||
document.body.classList.add("felt-privacy-body");
|
||||
document.body.replaceChildren(document.createElement("net-error-card"));
|
||||
}
|
||||
|
156
toolkit/content/aboutNetErrorHelpers.mjs
Normal file
156
toolkit/content/aboutNetErrorHelpers.mjs
Normal file
@ -0,0 +1,156 @@
|
||||
/* 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/. */
|
||||
|
||||
/* eslint-env mozilla/remote-page */
|
||||
|
||||
import {
|
||||
parse,
|
||||
pemToDER,
|
||||
} from "chrome://global/content/certviewer/certDecoder.mjs";
|
||||
|
||||
// The following parameters are parsed from the error URL:
|
||||
// e - the error code
|
||||
// s - custom CSS class to allow alternate styling/favicons
|
||||
// d - error description
|
||||
// captive - "true" to indicate we're behind a captive portal.
|
||||
// Any other value is ignored.
|
||||
|
||||
// Note that this file uses document.documentURI to get
|
||||
// the URL (with the format from above). This is because
|
||||
// document.location.href gets the current URI off the docshell,
|
||||
// which is the URL displayed in the location bar, i.e.
|
||||
// the URI that the user attempted to load.
|
||||
|
||||
export let searchParams = new URLSearchParams(
|
||||
document.documentURI.split("?")[1]
|
||||
);
|
||||
|
||||
export let gErrorCode = searchParams.get("e");
|
||||
export let gIsCertError = gErrorCode == "nssBadCert";
|
||||
export let gHasSts = gIsCertError && getCSSClass() === "badStsCert";
|
||||
const HOST_NAME = getHostName();
|
||||
|
||||
export function getCSSClass() {
|
||||
return searchParams.get("s");
|
||||
}
|
||||
|
||||
export function getHostName() {
|
||||
try {
|
||||
return new URL(RPMGetInnerMostURI(document.location.href)).hostname;
|
||||
} catch (error) {
|
||||
console.error("Could not parse URL", error);
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
export async function getFailedCertificatesAsPEMString() {
|
||||
let locationUrl = document.location.href;
|
||||
let failedCertInfo = document.getFailedCertSecurityInfo();
|
||||
let errorMessage = failedCertInfo.errorMessage;
|
||||
let hasHSTS = failedCertInfo.hasHSTS.toString();
|
||||
let hasHPKP = failedCertInfo.hasHPKP.toString();
|
||||
let [hstsLabel, hpkpLabel, failedChainLabel] =
|
||||
await document.l10n.formatValues([
|
||||
{ id: "cert-error-details-hsts-label", args: { hasHSTS } },
|
||||
{ id: "cert-error-details-key-pinning-label", args: { hasHPKP } },
|
||||
{ id: "cert-error-details-cert-chain-label" },
|
||||
]);
|
||||
|
||||
let certStrings = failedCertInfo.certChainStrings;
|
||||
let failedChainCertificates = "";
|
||||
for (let der64 of certStrings) {
|
||||
let wrapped = der64.replace(/(\S{64}(?!$))/g, "$1\r\n");
|
||||
failedChainCertificates +=
|
||||
"-----BEGIN CERTIFICATE-----\r\n" +
|
||||
wrapped +
|
||||
"\r\n-----END CERTIFICATE-----\r\n";
|
||||
}
|
||||
|
||||
let details =
|
||||
locationUrl +
|
||||
"\r\n\r\n" +
|
||||
errorMessage +
|
||||
"\r\n\r\n" +
|
||||
hstsLabel +
|
||||
"\r\n" +
|
||||
hpkpLabel +
|
||||
"\r\n\r\n" +
|
||||
failedChainLabel +
|
||||
"\r\n\r\n" +
|
||||
failedChainCertificates;
|
||||
return details;
|
||||
}
|
||||
|
||||
export async function getSubjectAltNames(failedCertInfo) {
|
||||
const serverCertBase64 = failedCertInfo.certChainStrings[0];
|
||||
const parsed = await parse(pemToDER(serverCertBase64));
|
||||
const subjectAltNamesExtension = parsed.ext.san;
|
||||
const subjectAltNames = [];
|
||||
if (subjectAltNamesExtension) {
|
||||
for (let [key, value] of subjectAltNamesExtension.altNames) {
|
||||
if (key === "DNS Name" && value.length) {
|
||||
subjectAltNames.push(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
return subjectAltNames;
|
||||
}
|
||||
|
||||
export async function recordSecurityUITelemetry(category, name, errorInfo) {
|
||||
// Truncate the error code to avoid going over the allowed
|
||||
// string size limit for telemetry events.
|
||||
let errorCode = errorInfo.errorCodeString.substring(0, 40);
|
||||
let extraKeys = {
|
||||
value: errorCode,
|
||||
is_frame: window.parent != window,
|
||||
};
|
||||
if (category == "securityUiCerterror") {
|
||||
extraKeys.has_sts = gHasSts;
|
||||
}
|
||||
if (name.startsWith("load")) {
|
||||
extraKeys.channel_status = errorInfo.channelStatus;
|
||||
}
|
||||
if (category == "securityUiCerterror" && name.startsWith("load")) {
|
||||
extraKeys.issued_by_cca = false;
|
||||
extraKeys.hyphen_compat = false;
|
||||
// This issue only applies to certificate domain name mismatch errors where
|
||||
// the first label in the domain name starts or ends with a hyphen.
|
||||
let label = HOST_NAME.substring(0, HOST_NAME.indexOf("."));
|
||||
if (
|
||||
errorCode == "SSL_ERROR_BAD_CERT_DOMAIN" &&
|
||||
(label.startsWith("-") || label.endsWith("-"))
|
||||
) {
|
||||
try {
|
||||
let subjectAltNames = await getSubjectAltNames(errorInfo);
|
||||
for (let subjectAltName of subjectAltNames) {
|
||||
// If the certificate has a wildcard entry that matches the domain
|
||||
// name (e.g. '*.example.com' matches 'foo-.example.com'), then
|
||||
// this error is probably due to Firefox disallowing hyphens in
|
||||
// domain names when matching wildcard entries.
|
||||
if (
|
||||
subjectAltName.startsWith("*.") &&
|
||||
subjectAltName.substring(1) == HOST_NAME.substring(label.length)
|
||||
) {
|
||||
extraKeys.hyphen_compat = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.error("error parsing certificate:", e);
|
||||
}
|
||||
}
|
||||
let issuer = errorInfo.certChainStrings.at(-1);
|
||||
if (issuer && errorCode == "SEC_ERROR_UNKNOWN_ISSUER") {
|
||||
try {
|
||||
let parsed = await parse(pemToDER(issuer));
|
||||
extraKeys.issued_by_cca =
|
||||
parsed.issuer.dn == "c=IN, o=India PKI, cn=CCA India 2022 SPL" ||
|
||||
parsed.issuer.dn == "c=IN, o=India PKI, cn=CCA India 2015 SPL";
|
||||
} catch (e) {
|
||||
console.error("error parsing issuer certificate:", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
RPMRecordGleanEvent(category, name, extraKeys);
|
||||
}
|
@ -9,6 +9,8 @@ toolkit.jar:
|
||||
content/global/aboutLogging.html
|
||||
content/global/aboutNetError.mjs
|
||||
content/global/aboutNetError.html
|
||||
content/global/aboutNetErrorHelpers.mjs
|
||||
content/global/net-error-card.mjs
|
||||
content/global/aboutNetworking.js
|
||||
content/global/aboutNetworking.html
|
||||
#ifndef ANDROID
|
||||
|
523
toolkit/content/net-error-card.mjs
Normal file
523
toolkit/content/net-error-card.mjs
Normal file
@ -0,0 +1,523 @@
|
||||
/* 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/. */
|
||||
|
||||
/* eslint-disable import/no-unassigned-import */
|
||||
/* eslint-env mozilla/remote-page */
|
||||
|
||||
import {
|
||||
getCSSClass,
|
||||
getHostName,
|
||||
getSubjectAltNames,
|
||||
getFailedCertificatesAsPEMString,
|
||||
recordSecurityUITelemetry,
|
||||
} from "chrome://global/content/aboutNetErrorHelpers.mjs";
|
||||
import { html } from "chrome://global/content/vendor/lit.all.mjs";
|
||||
import { MozLitElement } from "chrome://global/content/lit-utils.mjs";
|
||||
import "chrome://global/content/elements/moz-button-group.mjs";
|
||||
import "chrome://global/content/elements/moz-button.mjs";
|
||||
import "chrome://global/content/elements/moz-support-link.mjs";
|
||||
|
||||
const HOST_NAME = getHostName();
|
||||
|
||||
export class NetErrorCard extends MozLitElement {
|
||||
static properties = {
|
||||
hostname: { type: String },
|
||||
domainMismatchNames: { type: String },
|
||||
advancedShowing: { type: Boolean, reflect: true },
|
||||
certErrorDebugInfoShowing: { type: Boolean, reflect: true },
|
||||
certificateErrorText: { type: String },
|
||||
};
|
||||
|
||||
static queries = {
|
||||
copyButtonTop: "#copyToClipboardTop",
|
||||
exceptionButton: "#exception-button",
|
||||
errorCode: "#errorCode",
|
||||
advancedContainer: ".advanced-container",
|
||||
advancedButton: "#advanced-button",
|
||||
};
|
||||
|
||||
static ERROR_CODES = new Set([
|
||||
"SEC_ERROR_UNKNOWN_ISSUER",
|
||||
"SSL_ERROR_BAD_CERT_DOMAIN",
|
||||
"MOZILLA_PKIX_ERROR_SELF_SIGNED_CERT",
|
||||
"SEC_ERROR_EXPIRED_CERTIFICATE",
|
||||
"SEC_ERROR_EXPIRED_ISSUER_CERTIFICATE",
|
||||
]);
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.domainMismatchNames = null;
|
||||
this.advancedShowing = false;
|
||||
this.certErrorDebugInfoShowing = false;
|
||||
this.certificateErrorText = null;
|
||||
this.domainMismatchNamesPromise = null;
|
||||
this.certificateErrorTextPromise = null;
|
||||
}
|
||||
|
||||
async getUpdateComplete() {
|
||||
const result = await super.getUpdateComplete();
|
||||
|
||||
if (this.domainMismatchNames && this.certificateErrorText) {
|
||||
return result;
|
||||
}
|
||||
|
||||
await Promise.all([
|
||||
this.getDomainMismatchNames(),
|
||||
this.getCertificateErrorText(),
|
||||
]);
|
||||
|
||||
await Promise.all([
|
||||
this.domainMismatchNamesPromise,
|
||||
this.certificateErrorTextPromise,
|
||||
]);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
connectedCallback() {
|
||||
super.connectedCallback();
|
||||
|
||||
this.init();
|
||||
}
|
||||
|
||||
firstUpdated() {
|
||||
// Dispatch this event so tests can detect that we finished loading the error page.
|
||||
document.dispatchEvent(
|
||||
new CustomEvent("AboutNetErrorLoad", { bubbles: true })
|
||||
);
|
||||
}
|
||||
|
||||
init() {
|
||||
document.l10n.setAttributes(
|
||||
document.querySelector("title"),
|
||||
"fp-certerror-page-title"
|
||||
);
|
||||
|
||||
this.failedCertInfo = document.getFailedCertSecurityInfo();
|
||||
|
||||
this.hostname = HOST_NAME;
|
||||
const { port } = document.location;
|
||||
if (port && port != 443) {
|
||||
this.hostname += ":" + port;
|
||||
}
|
||||
|
||||
if (getCSSClass() == "expertBadCert") {
|
||||
this.toggleAdvancedShowing();
|
||||
}
|
||||
}
|
||||
|
||||
introContentTemplate() {
|
||||
switch (this.failedCertInfo.errorCodeString) {
|
||||
case "SEC_ERROR_UNKNOWN_ISSUER":
|
||||
case "SSL_ERROR_BAD_CERT_DOMAIN":
|
||||
case "SEC_ERROR_EXPIRED_CERTIFICATE":
|
||||
case "MOZILLA_PKIX_ERROR_SELF_SIGNED_CERT":
|
||||
return html`<p
|
||||
data-l10n-id="fp-certerror-intro"
|
||||
data-l10n-args='{"hostname": "${this.hostname}"}'
|
||||
></p>`;
|
||||
case "SEC_ERROR_EXPIRED_ISSUER_CERTIFICATE":
|
||||
return html`<p
|
||||
data-l10n-id="fp-certerror-expired-intro"
|
||||
data-l10n-args='{"hostname": "${this.hostname}"}'
|
||||
></p>`;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
advancedContainerTemplate() {
|
||||
if (!this.advancedShowing) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let content;
|
||||
|
||||
switch (this.failedCertInfo.errorCodeString) {
|
||||
case "SEC_ERROR_UNKNOWN_ISSUER": {
|
||||
content = this.advancedSectionTemplate({
|
||||
whyDangerousL10nId: "fp-certerror-unknown-issuer-why-dangerous-body",
|
||||
whatCanYouDoL10nId:
|
||||
"fp-certerror-unknown-issuer-what-can-you-do-body",
|
||||
learnMoreL10nId: "fp-learn-more-about-cert-issues",
|
||||
learnMoreSupportPage: "connection-not-secure",
|
||||
viewCert: true,
|
||||
viewDateTime: true,
|
||||
});
|
||||
break;
|
||||
}
|
||||
case "SSL_ERROR_BAD_CERT_DOMAIN": {
|
||||
if (!this.domainMismatchNames) {
|
||||
this.getDomainMismatchNames();
|
||||
return null;
|
||||
}
|
||||
|
||||
content = this.advancedSectionTemplate({
|
||||
whyDangerousL10nId: "fp-certerror-bad-domain-why-dangerous-body",
|
||||
whyDangerousL10nArgs: {
|
||||
hostname: this.hostname,
|
||||
validHosts: this.domainMismatchNames ?? "",
|
||||
},
|
||||
whatCanYouDoL10nId: "fp-certerror-bad-domain-what-can-you-do-body",
|
||||
learnMoreL10nId: "fp-learn-more-about-secure-connection-failures",
|
||||
learnMoreSupportPage: "connection-not-secure",
|
||||
viewCert: true,
|
||||
viewDateTime: true,
|
||||
});
|
||||
break;
|
||||
}
|
||||
case "SEC_ERROR_EXPIRED_CERTIFICATE": {
|
||||
const notBefore = this.failedCertInfo.validNotBefore;
|
||||
const notAfter = this.failedCertInfo.validNotAfter;
|
||||
if (notBefore && Date.now() < notAfter) {
|
||||
content = this.advancedSectionTemplate({
|
||||
whyDangerousL10nId: "fp-certerror-not-yet-valid-why-dangerous-body",
|
||||
whyDangerousL10nArgs: {
|
||||
date: notBefore,
|
||||
},
|
||||
whatCanYouDoL10nId: "fp-certerror-expired-what-can-you-do-body",
|
||||
whatCanYouDoL10nArgs: {
|
||||
date: Date.now(),
|
||||
},
|
||||
learnMoreL10nId: "fp-learn-more-about-time-related-errors",
|
||||
learnMoreSupportPage: "time-errors",
|
||||
viewCert: true,
|
||||
viewDateTime: true,
|
||||
});
|
||||
} else {
|
||||
content = this.advancedSectionTemplate({
|
||||
whyDangerousL10nId: "fp-certerror-expired-why-dangerous-body",
|
||||
whyDangerousL10nArgs: {
|
||||
date: notAfter,
|
||||
},
|
||||
whatCanYouDoL10nId: "fp-certerror-expired-what-can-you-do-body",
|
||||
whatCanYouDoL10nArgs: {
|
||||
date: Date.now(),
|
||||
},
|
||||
learnMoreL10nId: "fp-learn-more-about-time-related-errors",
|
||||
learnMoreSupportPage: "time-errors",
|
||||
viewCert: true,
|
||||
viewDateTime: true,
|
||||
});
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "MOZILLA_PKIX_ERROR_SELF_SIGNED_CERT": {
|
||||
content = this.advancedSectionTemplate({
|
||||
whyDangerousL10nId: "fp-certerror-self-signed-why-dangerous-body",
|
||||
whatCanYouDoL10nId: "fp-certerror-self-signed-what-can-you-do-body",
|
||||
importantNote: "fp-certerror-self-signed-important-note",
|
||||
viewCert: true,
|
||||
viewDateTime: true,
|
||||
});
|
||||
break;
|
||||
}
|
||||
case "SEC_ERROR_EXPIRED_ISSUER_CERTIFICATE": {
|
||||
const notAfter = this.failedCertInfo.validNotAfter;
|
||||
content = this.advancedSectionTemplate({
|
||||
whyDangerousL10nId: "fp-certerror-expired-why-dangerous-body",
|
||||
whyDangerousL10nArgs: {
|
||||
date: notAfter,
|
||||
},
|
||||
whatCanYouDoL10nId: "fp-certerror-expired-what-can-you-do-body",
|
||||
whatCanYouDoL10nArgs: {
|
||||
date: Date.now(),
|
||||
},
|
||||
learnMoreL10nId: "fp-learn-more-about-time-related-errors",
|
||||
learnMoreSupportPage: "time-errors",
|
||||
viewCert: true,
|
||||
viewDateTime: true,
|
||||
});
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return html`<div class="advanced-container">
|
||||
<h2 data-l10n-id="fp-certerror-advanced-title"></h2>
|
||||
${content}
|
||||
</div>`;
|
||||
}
|
||||
|
||||
advancedSectionTemplate(params) {
|
||||
let {
|
||||
whyDangerousL10nId,
|
||||
whyDangerousL10nArgs,
|
||||
whatCanYouDoL10nId,
|
||||
whatCanYouDoL10nArgs,
|
||||
importantNote,
|
||||
learnMoreL10nId,
|
||||
learnMoreSupportPage,
|
||||
viewCert,
|
||||
viewDateTime,
|
||||
} = params;
|
||||
return html`<p>
|
||||
${whyDangerousL10nId
|
||||
? html`<strong
|
||||
data-l10n-id="fp-certerror-why-site-dangerous"
|
||||
></strong>
|
||||
<span
|
||||
data-l10n-id="${whyDangerousL10nId}"
|
||||
data-l10n-args=${JSON.stringify(whyDangerousL10nArgs)}
|
||||
></span>`
|
||||
: null}
|
||||
</p>
|
||||
${whatCanYouDoL10nId
|
||||
? html`<p>
|
||||
<strong data-l10n-id="fp-certerror-what-can-you-do"></strong>
|
||||
<span
|
||||
data-l10n-id="${whatCanYouDoL10nId}"
|
||||
data-l10n-args=${JSON.stringify(whatCanYouDoL10nArgs)}
|
||||
></span>
|
||||
</p>`
|
||||
: null}
|
||||
${importantNote ? html`<p data-l10n-id="${importantNote}"></p>` : null}
|
||||
${learnMoreL10nId
|
||||
? html`<p>
|
||||
<a
|
||||
is="moz-support-link"
|
||||
support-page="${learnMoreSupportPage}"
|
||||
data-l10n-id="${learnMoreL10nId}"
|
||||
data-telemetry-id="learn_more_link"
|
||||
@click=${this.handleTelemetryClick}
|
||||
></a>
|
||||
</p>`
|
||||
: null}
|
||||
${viewCert
|
||||
? html`<p>
|
||||
<a
|
||||
id="viewCertificate"
|
||||
data-l10n-id="fp-certerror-view-certificate-link"
|
||||
href="javascript:void(0)"
|
||||
></a>
|
||||
</p>`
|
||||
: null}
|
||||
<p>
|
||||
<a
|
||||
id="errorCode"
|
||||
data-l10n-id="fp-cert-error-code"
|
||||
data-l10n-name="error-code-link"
|
||||
data-telemetry-id="error_code_link"
|
||||
data-l10n-args='{"error": "${this.failedCertInfo.errorCodeString}"}'
|
||||
@click=${this.toggleCertErrorDebugInfoShowing}
|
||||
href="#certificateErrorDebugInformation"
|
||||
></a>
|
||||
</p>
|
||||
${viewDateTime
|
||||
? html`<p
|
||||
data-l10n-id="fp-datetime"
|
||||
data-l10n-args=${JSON.stringify({ datetime: Date.now() })}
|
||||
></p>`
|
||||
: null}
|
||||
<moz-button
|
||||
id="exception-button"
|
||||
data-l10n-id="fp-certerror-override-exception-button"
|
||||
data-l10n-args=${JSON.stringify({ hostname: this.hostname })}
|
||||
data-telemetry-id="exception_button"
|
||||
@click=${this.handleProceedToUrlClick}
|
||||
></moz-button>`;
|
||||
}
|
||||
|
||||
async getDomainMismatchNames() {
|
||||
if (this.domainMismatchNamesPromise) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.domainMismatchNamesPromise = getSubjectAltNames(this.failedCertInfo);
|
||||
let subjectAltNames = await this.domainMismatchNamesPromise;
|
||||
this.domainMismatchNames = subjectAltNames.join(", ");
|
||||
}
|
||||
|
||||
async getCertificateErrorText() {
|
||||
if (this.certificateErrorTextPromise) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.certificateErrorTextPromise = getFailedCertificatesAsPEMString();
|
||||
this.certificateErrorText = await this.certificateErrorTextPromise;
|
||||
}
|
||||
|
||||
certErrorDebugInfoTemplate() {
|
||||
if (!this.certErrorDebugInfoShowing) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!this.certificateErrorText) {
|
||||
this.getCertificateErrorText();
|
||||
return null;
|
||||
}
|
||||
|
||||
return html`<div
|
||||
id="certificateErrorDebugInformation"
|
||||
class="advanced-panel"
|
||||
>
|
||||
<moz-button
|
||||
id="copyToClipboardTop"
|
||||
data-telemetry-id="clipboard_button_top"
|
||||
data-l10n-id="neterror-copy-to-clipboard-button"
|
||||
@click=${this.copyCertErrorTextToClipboard}
|
||||
></moz-button>
|
||||
<div id="certificateErrorText">${this.certificateErrorText}</div>
|
||||
<moz-button
|
||||
data-telemetry-id="clipboard_button_bot"
|
||||
data-l10n-id="neterror-copy-to-clipboard-button"
|
||||
@click=${this.copyCertErrorTextToClipboard}
|
||||
></moz-button>
|
||||
</div>`;
|
||||
}
|
||||
|
||||
handleGoBackClick(e) {
|
||||
this.handleTelemetryClick(e);
|
||||
RPMSendAsyncMessage("Browser:SSLErrorGoBack");
|
||||
}
|
||||
|
||||
handleProceedToUrlClick(e) {
|
||||
this.handleTelemetryClick(e);
|
||||
const isPermanent =
|
||||
!RPMIsWindowPrivate() &&
|
||||
RPMGetBoolPref("security.certerrors.permanentOverride");
|
||||
document.addCertException(!isPermanent).then(
|
||||
() => {
|
||||
location.reload();
|
||||
},
|
||||
() => {}
|
||||
);
|
||||
}
|
||||
|
||||
toggleAdvancedShowing(e) {
|
||||
if (e) {
|
||||
this.handleTelemetryClick(e);
|
||||
}
|
||||
|
||||
this.advancedShowing = !this.advancedShowing;
|
||||
|
||||
if (!this.advancedShowing) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.revealAdvancedContainer();
|
||||
}
|
||||
|
||||
async revealAdvancedContainer() {
|
||||
await this.getUpdateComplete();
|
||||
|
||||
// Toggling the advanced panel must ensure that the debugging
|
||||
// information panel is hidden as well, since it's opened by the
|
||||
// error code link in the advanced panel.
|
||||
this.certErrorDebugInfoShowing = false;
|
||||
|
||||
// Reveal, but disabled (and grayed-out) for 3.0s.
|
||||
this.exceptionButton.disabled = true;
|
||||
|
||||
// -
|
||||
|
||||
if (this.resetReveal) {
|
||||
this.resetReveal(); // Reset if previous is pending.
|
||||
}
|
||||
let wasReset = false;
|
||||
this.resetReveal = () => {
|
||||
wasReset = true;
|
||||
};
|
||||
|
||||
// Wait for 10 frames to ensure that the warning text is rendered
|
||||
// and gets all the way to the screen for the user to read it.
|
||||
// This is only ~0.160s at 60Hz, so it's not too much extra time that we're
|
||||
// taking to ensure that we're caught up with rendering, on top of the
|
||||
// (by default) whole second(s) we're going to wait based on the
|
||||
// security.dialog_enable_delay pref.
|
||||
// The catching-up to rendering is the important part, not the
|
||||
// N-frame-delay here.
|
||||
for (let i = 0; i < 10; i++) {
|
||||
await new Promise(requestAnimationFrame);
|
||||
}
|
||||
|
||||
// Wait another Nms (default: 1000) for the user to be very sure. (Sorry speed readers!)
|
||||
const securityDelayMs = RPMGetIntPref("security.dialog_enable_delay", 1000);
|
||||
await new Promise(go => setTimeout(go, securityDelayMs));
|
||||
|
||||
if (wasReset || !this.advancedShowing) {
|
||||
this.resetReveal = null;
|
||||
return;
|
||||
}
|
||||
|
||||
// Enable and un-gray-out.
|
||||
this.exceptionButton.disabled = false;
|
||||
}
|
||||
|
||||
async toggleCertErrorDebugInfoShowing(event) {
|
||||
this.handleTelemetryClick(event);
|
||||
event.preventDefault();
|
||||
|
||||
this.certErrorDebugInfoShowing = !this.certErrorDebugInfoShowing;
|
||||
|
||||
if (this.certErrorDebugInfoShowing) {
|
||||
await this.getUpdateComplete();
|
||||
this.copyButtonTop.scrollIntoView({
|
||||
block: "start",
|
||||
behavior: "smooth",
|
||||
});
|
||||
this.copyButtonTop.focus();
|
||||
}
|
||||
}
|
||||
|
||||
copyCertErrorTextToClipboard(e) {
|
||||
this.handleTelemetryClick(e);
|
||||
navigator.clipboard.writeText(this.certificateErrorText);
|
||||
}
|
||||
|
||||
handleTelemetryClick(event) {
|
||||
let target = event.originalTarget;
|
||||
if (!target.hasAttribute("data-telemetry-id")) {
|
||||
target = target.getRootNode().host;
|
||||
}
|
||||
let telemetryId = target.dataset.telemetryId;
|
||||
void recordSecurityUITelemetry(
|
||||
"securityUiCerterror",
|
||||
"click" +
|
||||
telemetryId
|
||||
.split("_")
|
||||
.map(word => word[0].toUpperCase() + word.slice(1))
|
||||
.join(""),
|
||||
this.failedCertInfo
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
if (!this.failedCertInfo) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return html`<link
|
||||
rel="stylesheet"
|
||||
href="chrome://global/skin/aboutNetError.css"
|
||||
/>
|
||||
<article class="felt-privacy-container">
|
||||
<div class="img-container">
|
||||
<img src="chrome://global/skin/illustrations/security-error.svg" />
|
||||
</div>
|
||||
<div class="container">
|
||||
<h1 data-l10n-id="fp-certerror-body-title"></h1>
|
||||
${this.introContentTemplate()}
|
||||
<moz-button-group
|
||||
><moz-button
|
||||
type="primary"
|
||||
data-l10n-id="fp-certerror-return-to-previous-page-recommended-button"
|
||||
data-telemetry-id="return_button_adv"
|
||||
@click=${this.handleGoBackClick}
|
||||
></moz-button
|
||||
><moz-button
|
||||
id="advanced-button"
|
||||
data-l10n-id="${this.advancedShowing
|
||||
? "fp-certerror-hide-advanced-button"
|
||||
: "fp-certerror-advanced-button"}"
|
||||
data-telemetry-id="advanced_button"
|
||||
@click=${this.toggleAdvancedShowing}
|
||||
></moz-button
|
||||
></moz-button-group>
|
||||
${this.advancedContainerTemplate()}
|
||||
${this.certErrorDebugInfoTemplate()}
|
||||
</div>
|
||||
</article>`;
|
||||
}
|
||||
}
|
@ -146,3 +146,74 @@ networkProtocolError-title = Network Protocol Error
|
||||
nssBadCert-title = Warning: Potential Security Risk Ahead
|
||||
nssBadCert-sts-title = Did Not Connect: Potential Security Issue
|
||||
certerror-mitm-title = Software is Preventing { -brand-short-name } From Safely Connecting to This Site
|
||||
|
||||
## Felt Privacy V1 Strings
|
||||
|
||||
fp-certerror-page-title = Warning: Security Risk
|
||||
fp-certerror-body-title = Be careful. Something doesn’t look right.
|
||||
|
||||
fp-certerror-why-site-dangerous = What makes the site look dangerous?
|
||||
fp-certerror-what-can-you-do = What can you do about it?
|
||||
|
||||
fp-certerror-advanced-title = Advanced
|
||||
|
||||
fp-certerror-advanced-button = Advanced
|
||||
fp-certerror-hide-advanced-button = Hide advanced
|
||||
|
||||
## Variables:
|
||||
## $hostname (String) - Hostname of the website to which the user was trying to connect.
|
||||
|
||||
fp-certerror-override-exception-button = Proceed to { $hostname } (Risky)
|
||||
fp-certerror-intro = { -brand-short-name } spotted a potentially serious security issue with <strong>{ $hostname }</strong>. Someone pretending to be the site could try to steal things like credit card info, passwords, or emails.
|
||||
fp-certerror-expired-into = { -brand-short-name } spotted a security issue with <strong>{ $hostname }</strong>. Either the site isn’t set up right or your device’s clock is set to the wrong date/time.
|
||||
|
||||
##
|
||||
|
||||
fp-certerror-view-certificate-link = View the site’s certificate
|
||||
fp-certerror-return-to-previous-page-recommended-button = Go back (Recommended)
|
||||
|
||||
# This string appears after the following string: "What makes the site look dangerous?" (fp-certerror-why-site-dangerous)
|
||||
# Variables:
|
||||
# $hostname (String) - Hostname of the website to which the user was trying to connect.
|
||||
# $validHosts (String) - Valid hostnames.
|
||||
fp-certerror-bad-domain-why-dangerous-body = The site is set up to allow only secure connections, but there’s a problem with the site’s certificate. It’s possible that a bad actor is trying to impersonate the site. Sites use certificates issued by a certificate authority to prove they’re really who they say they are. { -brand-short-name } doesn’t trust this site because its certificate isn’t valid for { $hostname }. The certificate is only valid for: { $validHosts }.
|
||||
# This string appears after the following string: "What can you do about it?" (fp-certerror-what-can-you-do)
|
||||
fp-certerror-bad-domain-what-can-you-do-body = Probably nothing, since it’s likely there’s a problem with the site itself. Sites use certificates issued by a certificate authority to prove they’re really who they say they are. But if you’re on a corporate network, your support team may have more info. If you’re using antivirus software, try searching for potential conflicts or known issues.
|
||||
|
||||
# This string appears after the following string: "What makes the site look dangerous?" (fp-certerror-why-site-dangerous)
|
||||
fp-certerror-unknown-issuer-why-dangerous-body = There’s an issue with the site’s certificate. It’s possible that a bad actor is trying to impersonate the site. Sites use certificates issued by a certificate authority to prove they’re really who they say they are. { -brand-short-name } doesn’t trust this site because we can’t tell who issued the certificate, it’s self-signed, or the site isn’t sending intermediate certificates we trust.
|
||||
# This string appears after the following string: "What can you do about it?" (fp-certerror-what-can-you-do)
|
||||
fp-certerror-unknown-issuer-what-can-you-do-body = Probably nothing, since it’s likely there’s a problem with the site itself. But if you’re on a corporate network, your support team may have more info. If you’re using antivirus software, it may need to be configured to work with { -brand-short-name }.
|
||||
|
||||
# This string appears after the following string: "What makes the site look dangerous?" (fp-certerror-why-site-dangerous)
|
||||
fp-certerror-self-signed-why-dangerous-body = Because there’s an issue with the site’s certificate. Sites use certificates issued by a certificate authority to prove they’re really who they say they are. This site’s certificate is self-signed. It wasn’t issued by a recognized certificate authority – so we don’t trust it by default.
|
||||
# This string appears after the following string: "What can you do about it?" (fp-certerror-what-can-you-do)
|
||||
fp-certerror-self-signed-what-can-you-do-body = Not much. It’s likely there’s a problem with the site itself.
|
||||
fp-certerror-self-signed-important-note = IMPORTANT NOTE: If you are trying to visit this site on a corporate intranet, your IT staff may use self-signed certificates. They can help you check their authenticity.
|
||||
|
||||
# This string appears after the following string: "What makes the site look dangerous?" (fp-certerror-why-site-dangerous)
|
||||
# Variables:
|
||||
# $date (Date) - Certificate expiration date.
|
||||
fp-certerror-expired-why-dangerous-body = Sites use certificates issued by a certificate authority to prove they’re really who they say they are. { -brand-short-name } doesn’t trust this site because it looks like the certificate expired on { DATETIME($date, month: "numeric", day: "numeric", year: "numeric") }.
|
||||
|
||||
# This string appears after the following string: "What makes the site look dangerous?" (fp-certerror-why-site-dangerous)
|
||||
# Variables:
|
||||
# $date (Date) - Certificate start date.
|
||||
fp-certerror-not-yet-valid-why-dangerous-body = Sites use certificates issued by a certificate authority to prove they’re really who they say they are. { -brand-short-name } doesn’t trust this site because it looks like the certificate will not be valid until { DATETIME($date, month: "numeric", day: "numeric", year: "numeric") }.
|
||||
|
||||
# This string appears after the following string: "What can you do about it?" (fp-certerror-what-can-you-do)
|
||||
# Variables:
|
||||
# $date (Date) - Clock date.
|
||||
fp-certerror-expired-what-can-you-do-body = Your device’s clock is set to { DATETIME($date, month: "numeric", day: "numeric", year: "numeric") }. If this is correct, the security issue is probably with the site itself. If it’s wrong, you can change it in your device’s system settings.
|
||||
|
||||
# Variables:
|
||||
# $error (string) - NSS error code string that specifies type of cert error. e.g. unknown issuer, invalid cert, etc.
|
||||
fp-cert-error-code = Error Code: { $error }
|
||||
|
||||
# Variables:
|
||||
# $datetime (Date) - Current datetime.
|
||||
fp-datetime = { DATETIME($datetime, month: "short", year: "numeric", day: "numeric") } { DATETIME($datetime, timeStyle: "long") }
|
||||
|
||||
fp-learn-more-about-secure-connection-failures = Learn more about secure connection failures
|
||||
fp-learn-more-about-cert-issues = Learn more about these kinds of certificate issues
|
||||
fp-learn-more-about-time-related-errors = Learn more about troubleshooting time-related errors
|
||||
|
@ -49,6 +49,7 @@ export let RemotePageAccessManager = {
|
||||
"security.enterprise_roots.auto-enabled",
|
||||
"security.certerror.hideAddException",
|
||||
"network.trr.display_fallback_warning",
|
||||
"security.certerrors.felt-privacy-v1",
|
||||
],
|
||||
RPMGetIntPref: [
|
||||
"security.dialog_enable_delay",
|
||||
@ -102,6 +103,7 @@ export let RemotePageAccessManager = {
|
||||
"security.xfocsp.errorReporting.enabled",
|
||||
"security.xfocsp.hideOpenInNewWindow",
|
||||
"network.trr.display_fallback_warning",
|
||||
"security.certerrors.felt-privacy-v1",
|
||||
],
|
||||
RPMSetPref: [
|
||||
"security.xfocsp.errorReporting.automatic",
|
||||
|
@ -165,6 +165,35 @@ button:disabled {
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
/* Felt Privacy v1 LARGER SCREEN! */
|
||||
/* stylelint-disable-next-line media-query-no-invalid */
|
||||
@media (-moz-bool-pref: "security.certerrors.felt-privacy-v1") {
|
||||
.felt-privacy-body {
|
||||
justify-content: start;
|
||||
}
|
||||
|
||||
net-error-card {
|
||||
width: min(100%, 720px);
|
||||
min-width: min-content;
|
||||
margin-top: 150px;
|
||||
}
|
||||
|
||||
.felt-privacy-container {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.img-container > img {
|
||||
width: 8.75em;
|
||||
margin-block-start: 2em;
|
||||
margin-inline-end: 2.5em;
|
||||
}
|
||||
|
||||
#viewCertificate {
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 959px) {
|
||||
#certificateErrorText {
|
||||
/* The encoded certificate chain looks better when we're not
|
||||
|
@ -132,6 +132,7 @@
|
||||
skin/classic/global/illustrations/about-rights.svg (../../shared/illustrations/about-rights.svg)
|
||||
skin/classic/global/illustrations/about-license.svg (../../shared/illustrations/about-license.svg)
|
||||
skin/classic/global/illustrations/error-malformed-url.svg (../../shared/illustrations/error-malformed-url.svg)
|
||||
skin/classic/global/illustrations/security-error.svg (../../shared/illustrations/security-error.svg)
|
||||
skin/classic/global/media/picture-in-picture-open.svg (../../shared/media/picture-in-picture-open.svg)
|
||||
skin/classic/global/media/picture-in-picture-closed.svg (../../shared/media/picture-in-picture-closed.svg)
|
||||
skin/classic/global/icons/minus.svg (../../shared/icons/minus.svg)
|
||||
|
4
toolkit/themes/shared/illustrations/security-error.svg
Normal file
4
toolkit/themes/shared/illustrations/security-error.svg
Normal file
File diff suppressed because one or more lines are too long
After Width: | Height: | Size: 11 KiB |
Loading…
Reference in New Issue
Block a user