summaryrefslogtreecommitdiffstats
path: root/browser/base/content/aboutNetError.js
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--browser/base/content/aboutNetError.js1265
1 files changed, 1265 insertions, 0 deletions
diff --git a/browser/base/content/aboutNetError.js b/browser/base/content/aboutNetError.js
new file mode 100644
index 0000000000..376d9d9dea
--- /dev/null
+++ b/browser/base/content/aboutNetError.js
@@ -0,0 +1,1265 @@
+/* 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/frame-script */
+
+import { parse } from "chrome://global/content/certviewer/certDecoder.js";
+import { pemToDER } from "chrome://global/content/certviewer/utils.js";
+
+const formatter = new Intl.DateTimeFormat("default");
+
+const HOST_NAME = new URL(RPMGetInnerMostURI(document.location.href)).hostname;
+
+// Used to check if we have a specific localized message for an error.
+const KNOWN_ERROR_TITLE_IDS = new Set([
+ // Error titles:
+ "connectionFailure-title",
+ "deniedPortAccess-title",
+ "dnsNotFound-title",
+ "fileNotFound-title",
+ "fileAccessDenied-title",
+ "generic-title",
+ "captivePortal-title",
+ "malformedURI-title",
+ "netInterrupt-title",
+ "notCached-title",
+ "netOffline-title",
+ "contentEncodingError-title",
+ "unsafeContentType-title",
+ "netReset-title",
+ "netTimeout-title",
+ "unknownProtocolFound-title",
+ "proxyConnectFailure-title",
+ "proxyResolveFailure-title",
+ "redirectLoop-title",
+ "unknownSocketType-title",
+ "nssFailure2-title",
+ "csp-xfo-error-title",
+ "corruptedContentError-title",
+ "remoteXUL-title",
+ "sslv3Used-title",
+ "inadequateSecurityError-title",
+ "blockedByPolicy-title",
+ "clockSkewError-title",
+ "networkProtocolError-title",
+ "nssBadCert-title",
+ "nssBadCert-sts-title",
+ "certerror-mitm-title",
+]);
+
+/* The error message IDs from nsserror.ftl get processed into
+ * aboutNetErrorCodes.js which is loaded before we are: */
+/* global KNOWN_ERROR_MESSAGE_IDS */
+
+// 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]);
+
+// Set to true on init if the error code is nssBadCert.
+let gIsCertError;
+
+function getErrorCode() {
+ return searchParams.get("e");
+}
+
+function getCSSClass() {
+ return searchParams.get("s");
+}
+
+function getDescription() {
+ return searchParams.get("d");
+}
+
+function isCaptive() {
+ return searchParams.get("captive") == "true";
+}
+
+function retryThis(buttonEl) {
+ RPMSendAsyncMessage("Browser:EnableOnlineMode");
+ buttonEl.disabled = true;
+}
+
+function toggleDisplay(node) {
+ const toggle = {
+ "": "block",
+ none: "block",
+ block: "none",
+ };
+ return (node.style.display = toggle[node.style.display]);
+}
+
+function showBlockingErrorReporting() {
+ // Display blocking error reporting UI for XFO error and CSP error.
+ document.getElementById("blockingErrorReporting").style.display = "block";
+}
+
+function showPrefChangeContainer() {
+ const panel = document.getElementById("prefChangeContainer");
+ panel.style.display = "block";
+ document.getElementById("netErrorButtonContainer").style.display = "none";
+ document
+ .getElementById("prefResetButton")
+ .addEventListener("click", function resetPreferences() {
+ RPMSendAsyncMessage("Browser:ResetSSLPreferences");
+ });
+ addAutofocus("#prefResetButton", "beforeend");
+}
+
+function showTls10Container() {
+ const panel = document.getElementById("enableTls10Container");
+ panel.style.display = "block";
+ document.getElementById("netErrorButtonContainer").style.display = "none";
+ const button = document.getElementById("enableTls10Button");
+ button.addEventListener("click", function enableTls10(e) {
+ RPMSetBoolPref("security.tls.version.enable-deprecated", true);
+ retryThis(button);
+ });
+ addAutofocus("#enableTls10Button", "beforeend");
+}
+
+function setupAdvancedButton() {
+ // Get the hostname and add it to the panel
+ var panel = document.getElementById("badCertAdvancedPanel");
+ for (var span of panel.querySelectorAll("span.hostname")) {
+ span.textContent = HOST_NAME;
+ }
+
+ // Register click handler for the weakCryptoAdvancedPanel
+ document
+ .getElementById("advancedButton")
+ .addEventListener("click", togglePanelVisibility);
+
+ function togglePanelVisibility() {
+ toggleDisplay(panel);
+ if (gIsCertError) {
+ // 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.
+ var div = document.getElementById("certificateErrorDebugInformation");
+ div.style.display = "none";
+ }
+
+ if (panel.style.display == "block") {
+ // send event to trigger telemetry ping
+ var event = new CustomEvent("AboutNetErrorUIExpanded", { bubbles: true });
+ document.dispatchEvent(event);
+ }
+ }
+
+ if (!gIsCertError) {
+ return;
+ }
+
+ if (getCSSClass() == "expertBadCert") {
+ toggleDisplay(document.getElementById("badCertAdvancedPanel"));
+ // 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.
+ var div = document.getElementById("certificateErrorDebugInformation");
+ div.style.display = "none";
+ }
+
+ disallowCertOverridesIfNeeded();
+}
+
+function disallowCertOverridesIfNeeded() {
+ var cssClass = getCSSClass();
+ // Disallow overrides if this is a Strict-Transport-Security
+ // host and the cert is bad (STS Spec section 7.3) or if the
+ // certerror is in a frame (bug 633691).
+ if (cssClass == "badStsCert" || window != top) {
+ document
+ .getElementById("exceptionDialogButton")
+ .setAttribute("hidden", "true");
+ }
+ if (cssClass == "badStsCert") {
+ document.getElementById("badStsCertExplanation").removeAttribute("hidden");
+
+ let stsReturnButtonText = document.getElementById("stsReturnButtonText")
+ .textContent;
+ document.getElementById("returnButton").textContent = stsReturnButtonText;
+ document.getElementById(
+ "advancedPanelReturnButton"
+ ).textContent = stsReturnButtonText;
+
+ let stsMitmWhatCanYouDoAboutIt3 = document.getElementById(
+ "stsMitmWhatCanYouDoAboutIt3"
+ ).innerHTML;
+ // eslint-disable-next-line no-unsanitized/property
+ document.getElementById(
+ "mitmWhatCanYouDoAboutIt3"
+ ).innerHTML = stsMitmWhatCanYouDoAboutIt3;
+ }
+}
+
+function setErrorPageStrings(err) {
+ let title = err + "-title";
+
+ let isCertError = err == "nssBadCert";
+ let className = getCSSClass();
+ if (isCertError && (window !== window.top || className == "badStsCert")) {
+ title = err + "-sts-title";
+ }
+
+ let cspXfoError = err === "cspBlocked" || err === "xfoBlocked";
+ if (cspXfoError) {
+ title = "csp-xfo-error-title";
+ }
+
+ let titleElement = document.querySelector(".title-text");
+
+ if (!KNOWN_ERROR_TITLE_IDS.has(title)) {
+ console.error("No strings exist for error:", title);
+ title = "generic-title";
+ }
+ document.l10n.setAttributes(titleElement, title);
+}
+
+function initPage() {
+ var err = getErrorCode();
+ // List of error pages with an illustration.
+ let illustratedErrors = [
+ "malformedURI",
+ "dnsNotFound",
+ "connectionFailure",
+ "netInterrupt",
+ "netTimeout",
+ "netReset",
+ "netOffline",
+ ];
+ if (illustratedErrors.includes(err)) {
+ document.body.classList.add("illustrated", err);
+ }
+ if (err == "blockedByPolicy") {
+ document.body.classList.add("blocked");
+ }
+
+ gIsCertError = err == "nssBadCert";
+ // Only worry about captive portals if this is a cert error.
+ let showCaptivePortalUI = isCaptive() && gIsCertError;
+ if (showCaptivePortalUI) {
+ err = "captivePortal";
+ }
+
+ let l10nErrId = err;
+ let className = getCSSClass();
+ if (className) {
+ document.body.classList.add(className);
+ }
+
+ if (gIsCertError && (window !== window.top || className == "badStsCert")) {
+ l10nErrId += "_sts";
+ }
+
+ let pageTitle = document.getElementById("ept_" + l10nErrId);
+ if (pageTitle) {
+ document.title = pageTitle.textContent;
+ }
+
+ // if it's an unknown error or there's no title or description
+ // defined, get the generic message
+ var errDesc = document.getElementById("ed_" + l10nErrId);
+ if (!errDesc) {
+ errDesc = document.getElementById("ed_generic");
+ }
+
+ setErrorPageStrings(err);
+
+ var sd = document.getElementById("errorShortDescText");
+ if (sd) {
+ if (gIsCertError) {
+ // eslint-disable-next-line no-unsanitized/property
+ sd.innerHTML = errDesc.innerHTML;
+ } else {
+ sd.textContent = getDescription();
+ }
+ }
+
+ if (gIsCertError) {
+ if (showCaptivePortalUI) {
+ initPageCaptivePortal();
+ } else {
+ initPageCertError();
+ updateContainerPosition();
+ }
+
+ initCertErrorPageActions();
+ setTechnicalDetailsOnCertError();
+ return;
+ }
+
+ addAutofocus("#netErrorButtonContainer > .try-again");
+
+ document.body.classList.add("neterror");
+
+ var ld = document.getElementById("errorLongDesc");
+ if (ld) {
+ // eslint-disable-next-line no-unsanitized/property
+ ld.innerHTML = errDesc.innerHTML;
+ }
+
+ if (err == "sslv3Used") {
+ document.getElementById("learnMoreContainer").style.display = "block";
+ document.body.className = "certerror";
+ }
+
+ // remove undisplayed errors to avoid bug 39098
+ var errContainer = document.getElementById("errorContainer");
+ errContainer.remove();
+
+ if (err == "remoteXUL") {
+ // Remove the "Try again" button for remote XUL errors given that
+ // it is useless.
+ document.getElementById("netErrorButtonContainer").style.display = "none";
+ }
+
+ let learnMoreLink = document.getElementById("learnMoreLink");
+ let baseURL = RPMGetFormatURLPref("app.support.baseURL");
+ learnMoreLink.setAttribute("href", baseURL + "connection-not-secure");
+
+ if (err == "cspBlocked" || err == "xfoBlocked") {
+ // Remove the "Try again" button for XFO and CSP violations,
+ // since it's almost certainly useless. (Bug 553180)
+ document.getElementById("netErrorButtonContainer").style.display = "none";
+
+ // Adding a button for opening websites blocked for CSP and XFO violations
+ // in a new window. (Bug 1461195)
+ document.getElementById("errorShortDesc").style.display = "none";
+
+ let hostString = document.location.hostname;
+ let longDescription = document.getElementById("errorLongDesc");
+ document.l10n.setAttributes(longDescription, "csp-xfo-blocked-long-desc", {
+ hostname: hostString,
+ });
+
+ document.getElementById("openInNewWindowContainer").style.display = "block";
+
+ let openInNewWindowButton = document.getElementById(
+ "openInNewWindowButton"
+ );
+ openInNewWindowButton.href = document.location.href;
+
+ // Add a learn more link
+ document.getElementById("learnMoreContainer").style.display = "block";
+ learnMoreLink.setAttribute("href", baseURL + "xframe-neterror-page");
+
+ setupBlockingReportingUI();
+ }
+
+ setNetErrorMessageFromCode();
+
+ // Pinning errors are of type nssFailure2
+ if (err == "nssFailure2") {
+ document.getElementById("learnMoreContainer").style.display = "block";
+
+ const errorCode = document.getNetErrorInfo().errorCodeString;
+ const isTlsVersionError =
+ errorCode == "SSL_ERROR_UNSUPPORTED_VERSION" ||
+ errorCode == "SSL_ERROR_PROTOCOL_VERSION_ALERT";
+ const tls10OverrideEnabled = RPMGetBoolPref(
+ "security.tls.version.enable-deprecated"
+ );
+
+ if (
+ isTlsVersionError &&
+ !tls10OverrideEnabled &&
+ !RPMPrefIsLocked("security.tls.version.min")
+ ) {
+ // security.tls.* prefs may be reset by the user when they
+ // encounter an error, so it's important that this has a
+ // different pref branch.
+ const showOverride = RPMGetBoolPref(
+ "security.certerrors.tls.version.show-override",
+ true
+ );
+
+ // This is probably a TLS 1.0 server; offer to re-enable.
+ if (showOverride) {
+ showTls10Container();
+ }
+ } else {
+ const hasPrefStyleError = [
+ "interrupted", // This happens with subresources that are above the max tls
+ "SSL_ERROR_NO_CIPHERS_SUPPORTED",
+ "SSL_ERROR_NO_CYPHER_OVERLAP",
+ "SSL_ERROR_PROTOCOL_VERSION_ALERT",
+ "SSL_ERROR_SSL_DISABLED",
+ "SSL_ERROR_UNSUPPORTED_VERSION",
+ ].some(substring => {
+ return substring == errorCode;
+ });
+
+ if (hasPrefStyleError) {
+ RPMAddMessageListener("HasChangedCertPrefs", msg => {
+ if (msg.data.hasChangedCertPrefs) {
+ // Configuration overrides might have caused this; offer to reset.
+ showPrefChangeContainer();
+ }
+ });
+ RPMSendAsyncMessage("GetChangedCertPrefs");
+ }
+ }
+ }
+
+ if (err == "sslv3Used") {
+ document.getElementById("advancedButton").style.display = "none";
+ }
+
+ if (err == "inadequateSecurityError" || err == "blockedByPolicy") {
+ // Remove the "Try again" button from pages that don't need it.
+ // For HTTP/2 inadequate security or pages blocked by policy, trying
+ // again won't help.
+ document.getElementById("netErrorButtonContainer").style.display = "none";
+
+ var container = document.getElementById("errorLongDesc");
+ for (var span of container.querySelectorAll("span.hostname")) {
+ span.textContent = HOST_NAME;
+ }
+ }
+}
+
+function setupBlockingReportingUI() {
+ let checkbox = document.getElementById("automaticallyReportBlockingInFuture");
+
+ let reportingAutomatic = RPMGetBoolPref(
+ "security.xfocsp.errorReporting.automatic"
+ );
+ checkbox.checked = !!reportingAutomatic;
+
+ checkbox.addEventListener("change", function({ target: { checked } }) {
+ onSetBlockingReportAutomatic(checked);
+ });
+
+ let reportingEnabled = RPMGetBoolPref(
+ "security.xfocsp.errorReporting.enabled"
+ );
+
+ if (!reportingEnabled) {
+ return;
+ }
+
+ showBlockingErrorReporting();
+
+ if (reportingAutomatic) {
+ reportBlockingError();
+ }
+}
+
+function reportBlockingError() {
+ // We only report if we are in a frame.
+ if (window === window.top) {
+ return;
+ }
+
+ let err = getErrorCode();
+ // Ensure we only deal with XFO and CSP here.
+ if (!["xfoBlocked", "cspBlocked"].includes(err)) {
+ return;
+ }
+
+ let xfo_header = RPMGetHttpResponseHeader("X-Frame-Options");
+ let csp_header = RPMGetHttpResponseHeader("Content-Security-Policy");
+
+ // Extract the 'CSP: frame-ancestors' from the CSP header.
+ let reg = /(?:^|\s)frame-ancestors\s([^;]*)[$]*/i;
+ let match = reg.exec(csp_header);
+ csp_header = match ? match[1] : "";
+
+ // If it's the csp error page without the CSP: frame-ancestors, this means
+ // this error page is not triggered by CSP: frame-ancestors. So, we bail out
+ // early.
+ if (err === "cspBlocked" && !csp_header) {
+ return;
+ }
+
+ let xfoAndCspInfo = {
+ error_type: err === "xfoBlocked" ? "xfo" : "csp",
+ xfo_header,
+ csp_header,
+ };
+
+ RPMSendAsyncMessage("ReportBlockingError", {
+ scheme: document.location.protocol,
+ host: document.location.host,
+ port: parseInt(document.location.port) || -1,
+ path: document.location.pathname,
+ xfoAndCspInfo,
+ });
+}
+
+function onSetBlockingReportAutomatic(checked) {
+ RPMSetBoolPref("security.xfocsp.errorReporting.automatic", checked);
+
+ // If we're enabling reports, send a report for this failure.
+ if (checked) {
+ reportBlockingError();
+ }
+}
+
+async function setNetErrorMessageFromCode() {
+ let hostString = HOST_NAME;
+
+ let port = document.location.port;
+ if (port && port != 443) {
+ hostString += ":" + port;
+ }
+
+ let securityInfo;
+ try {
+ securityInfo = document.getNetErrorInfo();
+ } catch (ex) {
+ // We don't have a securityInfo when this is for example a DNS error.
+ return;
+ }
+
+ let desc = document.getElementById("errorShortDescText");
+ let errorCodeStr = securityInfo.errorCodeString || "";
+ let errorCodeStrId = errorCodeStr
+ .split("_")
+ .join("-")
+ .toLowerCase();
+ let errorCodeMsg = "";
+
+ if (KNOWN_ERROR_MESSAGE_IDS.has(errorCodeStrId)) {
+ [errorCodeMsg] = await document.l10n.formatValues([errorCodeStrId]);
+ }
+
+ if (!errorCodeMsg) {
+ console.warn("This error page has no error code in its security info");
+ document.l10n.setAttributes(desc, "ssl-connection-error", {
+ errorMessage: errorCodeStr,
+ hostname: hostString,
+ });
+ return;
+ }
+
+ document.l10n.setAttributes(desc, "ssl-connection-error", {
+ errorMessage: errorCodeMsg,
+ hostname: hostString,
+ });
+
+ let desc2 = document.getElementById("errorShortDescText2");
+ document.l10n.setAttributes(desc2, "cert-error-code-prefix", {
+ error: errorCodeStr,
+ });
+}
+
+// This function centers the error container after its content updates.
+// It is currently duplicated in NetErrorChild.jsm to avoid having to do
+// async communication to the page that would result in flicker.
+// TODO(johannh): Get rid of this duplication.
+function updateContainerPosition() {
+ let textContainer = document.getElementById("text-container");
+ // Using the vh CSS property our margin adapts nicely to window size changes.
+ // Unfortunately, this doesn't work correctly in iframes, which is why we need
+ // to manually compute the height there.
+ if (window.parent == window) {
+ textContainer.style.marginTop = `calc(50vh - ${textContainer.clientHeight /
+ 2}px)`;
+ } else {
+ let offset =
+ document.documentElement.clientHeight / 2 -
+ textContainer.clientHeight / 2;
+ if (offset > 0) {
+ textContainer.style.marginTop = `${offset}px`;
+ }
+ }
+}
+
+function initPageCaptivePortal() {
+ document.body.className = "captiveportal";
+ document
+ .getElementById("openPortalLoginPageButton")
+ .addEventListener("click", () => {
+ RPMSendAsyncMessage("Browser:OpenCaptivePortalPage");
+ });
+
+ addAutofocus("#openPortalLoginPageButton");
+ setupAdvancedButton();
+
+ // When the portal is freed, an event is sent by the parent process
+ // that we can pick up and attempt to reload the original page.
+ RPMAddMessageListener("AboutNetErrorCaptivePortalFreed", () => {
+ document.location.reload();
+ });
+}
+
+function initPageCertError() {
+ document.body.classList.add("certerror");
+ for (let host of document.querySelectorAll(".hostname")) {
+ host.textContent = HOST_NAME;
+ }
+
+ addAutofocus("#returnButton");
+ setupAdvancedButton();
+ document.getElementById("learnMoreContainer").style.display = "block";
+
+ let hideAddExceptionButton = RPMGetBoolPref(
+ "security.certerror.hideAddException",
+ false
+ );
+ if (hideAddExceptionButton) {
+ document.querySelector(".exceptionDialogButtonContainer").hidden = true;
+ }
+
+ let els = document.querySelectorAll("[data-telemetry-id]");
+ for (let el of els) {
+ el.addEventListener("click", recordClickTelemetry);
+ }
+
+ let failedCertInfo = document.getFailedCertSecurityInfo();
+ // Truncate the error code to avoid going over the allowed
+ // string size limit for telemetry events.
+ let errorCode = failedCertInfo.errorCodeString.substring(0, 40);
+ RPMRecordTelemetryEvent(
+ "security.ui.certerror",
+ "load",
+ "aboutcerterror",
+ errorCode,
+ {
+ has_sts: (getCSSClass() == "badStsCert").toString(),
+ is_frame: (window.parent != window).toString(),
+ }
+ );
+
+ setCertErrorDetails();
+}
+
+function recordClickTelemetry(e) {
+ let target = e.originalTarget;
+ let telemetryId = target.dataset.telemetryId;
+ let failedCertInfo = document.getFailedCertSecurityInfo();
+ // Truncate the error code to avoid going over the allowed
+ // string size limit for telemetry events.
+ let errorCode = failedCertInfo.errorCodeString.substring(0, 40);
+ RPMRecordTelemetryEvent(
+ "security.ui.certerror",
+ "click",
+ telemetryId,
+ errorCode,
+ {
+ has_sts: (getCSSClass() == "badStsCert").toString(),
+ is_frame: (window.parent != window).toString(),
+ }
+ );
+}
+
+function initCertErrorPageActions() {
+ document
+ .getElementById("returnButton")
+ .addEventListener("click", onReturnButtonClick);
+ document
+ .getElementById("advancedPanelReturnButton")
+ .addEventListener("click", onReturnButtonClick);
+ document
+ .getElementById("copyToClipboardTop")
+ .addEventListener("click", copyPEMToClipboard);
+ document
+ .getElementById("copyToClipboardBottom")
+ .addEventListener("click", copyPEMToClipboard);
+ document
+ .getElementById("exceptionDialogButton")
+ .addEventListener("click", addCertException);
+}
+
+function addCertException() {
+ const isPermanent =
+ !RPMIsWindowPrivate() &&
+ RPMGetBoolPref("security.certerrors.permanentOverride");
+ document.addCertException(!isPermanent).then(
+ () => {
+ location.reload();
+ },
+ err => {}
+ );
+}
+
+function onReturnButtonClick(e) {
+ RPMSendAsyncMessage("Browser:SSLErrorGoBack");
+}
+
+async function copyPEMToClipboard(e) {
+ let details = await getFailedCertificatesAsPEMString();
+ navigator.clipboard.writeText(details);
+}
+
+async function getFailedCertificatesAsPEMString() {
+ let location = 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 =
+ location +
+ "\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(event) {
+ // 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
+ // error code (MOZILLA_PKIX_ERROR_MITM_DETECTED).
+ let failedCertInfo = document.getFailedCertSecurityInfo();
+ let mitmPrimingEnabled = RPMGetBoolPref(
+ "security.certerrors.mitm.priming.enabled"
+ );
+ if (
+ mitmPrimingEnabled &&
+ failedCertInfo.errorCodeString == "SEC_ERROR_UNKNOWN_ISSUER" &&
+ // Only do this check for top-level failures.
+ window.parent == window
+ ) {
+ RPMSendAsyncMessage("Browser:PrimeMitm");
+ }
+
+ let learnMoreLink = document.getElementById("learnMoreLink");
+ let baseURL = RPMGetFormatURLPref("app.support.baseURL");
+ learnMoreLink.setAttribute("href", baseURL + "connection-not-secure");
+ let errWhatToDo = document.getElementById(
+ "es_nssBadCert_" + failedCertInfo.errorCodeString
+ );
+ let es = document.getElementById("errorWhatToDoText");
+ let errWhatToDoTitle = document.getElementById("edd_nssBadCert");
+ let est = document.getElementById("errorWhatToDoTitleText");
+ let error = getErrorCode();
+
+ if (error == "sslv3Used") {
+ learnMoreLink.setAttribute("href", baseURL + "sslv3-error-messages");
+ }
+
+ if (error == "nssFailure2") {
+ let shortDesc = document.getElementById("errorShortDescText").textContent;
+ // nssFailure2 also gets us other non-overrideable errors. Choose
+ // a "learn more" link based on description:
+ if (shortDesc.includes("MOZILLA_PKIX_ERROR_KEY_PINNING_FAILURE")) {
+ learnMoreLink.setAttribute(
+ "href",
+ baseURL + "certificate-pinning-reports"
+ );
+ }
+ }
+
+ // This is set to true later if the user's system clock is at fault for this error.
+ let clockSkew = false;
+ document.body.setAttribute("code", failedCertInfo.errorCodeString);
+
+ let titleElement = document.querySelector(".title-text");
+ let desc;
+ switch (failedCertInfo.errorCodeString) {
+ case "SSL_ERROR_BAD_CERT_DOMAIN":
+ case "SEC_ERROR_OCSP_INVALID_SIGNING_CERT":
+ case "SEC_ERROR_UNKNOWN_ISSUER":
+ if (es) {
+ // eslint-disable-next-line no-unsanitized/property
+ es.innerHTML = errWhatToDo.innerHTML;
+ }
+ if (est) {
+ // eslint-disable-next-line no-unsanitized/property
+ est.innerHTML = errWhatToDoTitle.innerHTML;
+ }
+ updateContainerPosition();
+ break;
+
+ // This error code currently only exists for the Symantec distrust
+ // in Firefox 63, so we add copy explaining that to the user.
+ // In case of future distrusts of that scale we might need to add
+ // additional parameters that allow us to identify the affected party
+ // without replicating the complex logic from certverifier code.
+ case "MOZILLA_PKIX_ERROR_ADDITIONAL_POLICY_CONSTRAINT_FAILED":
+ desc = document.getElementById("errorShortDescText2");
+ document.l10n.setAttributes(
+ desc,
+ "cert-error-symantec-distrust-description",
+ {
+ HOST_NAME,
+ }
+ );
+
+ let adminDesc = document.createElement("p");
+ document.l10n.setAttributes(
+ adminDesc,
+ "cert-error-symantec-distrust-admin"
+ );
+
+ learnMoreLink.href = baseURL + "symantec-warning";
+ updateContainerPosition();
+ break;
+
+ case "MOZILLA_PKIX_ERROR_MITM_DETECTED":
+ let autoEnabledEnterpriseRoots = RPMGetBoolPref(
+ "security.enterprise_roots.auto-enabled",
+ false
+ );
+ if (mitmPrimingEnabled && autoEnabledEnterpriseRoots) {
+ RPMSendAsyncMessage("Browser:ResetEnterpriseRootsPref");
+ }
+
+ // We don't actually know what the MitM is called (since we don't
+ // maintain a list), so we'll try and display the common name of the
+ // root issuer to the user. In the worst case they are as clueless as
+ // before, in the best case this gives them an actionable hint.
+ // This may be revised in the future.
+ let names = document.querySelectorAll(".mitm-name");
+ for (let span of names) {
+ span.textContent = failedCertInfo.issuerCommonName;
+ }
+
+ learnMoreLink.href = baseURL + "security-error";
+
+ document.l10n.setAttributes(titleElement, "certerror-mitm-title");
+ desc = document.getElementById("ed_mitm");
+ // eslint-disable-next-line no-unsanitized/property
+ document.getElementById("errorShortDescText").innerHTML = desc.innerHTML;
+
+ // eslint-disable-next-line no-unsanitized/property
+ es.innerHTML = errWhatToDo.innerHTML;
+ // eslint-disable-next-line no-unsanitized/property
+ est.innerHTML = errWhatToDoTitle.innerHTML;
+
+ updateContainerPosition();
+ break;
+
+ case "MOZILLA_PKIX_ERROR_SELF_SIGNED_CERT":
+ learnMoreLink.href = baseURL + "security-error";
+ break;
+
+ // In case the certificate expired we make sure the system clock
+ // matches the remote-settings service (blocklist via Kinto) ping time
+ // and is not before the build date.
+ case "SEC_ERROR_EXPIRED_CERTIFICATE":
+ case "SEC_ERROR_EXPIRED_ISSUER_CERTIFICATE":
+ case "MOZILLA_PKIX_ERROR_NOT_YET_VALID_CERTIFICATE":
+ case "MOZILLA_PKIX_ERROR_NOT_YET_VALID_ISSUER_CERTIFICATE":
+ learnMoreLink.href = baseURL + "time-errors";
+ // We check against the remote-settings server time first if available, because that allows us
+ // to give the user an approximation of what the correct time is.
+ let difference = RPMGetIntPref("services.settings.clock_skew_seconds", 0);
+ let lastFetched =
+ RPMGetIntPref("services.settings.last_update_seconds", 0) * 1000;
+
+ let now = Date.now();
+ let certRange = {
+ notBefore: failedCertInfo.certValidityRangeNotBefore,
+ notAfter: failedCertInfo.certValidityRangeNotAfter,
+ };
+ let approximateDate = now - difference * 1000;
+ // If the difference is more than a day, we last fetched the date in the last 5 days,
+ // and adjusting the date per the interval would make the cert valid, warn the user:
+ if (
+ Math.abs(difference) > 60 * 60 * 24 &&
+ now - lastFetched <= 60 * 60 * 24 * 5 * 1000 &&
+ certRange.notBefore < approximateDate &&
+ certRange.notAfter > approximateDate
+ ) {
+ clockSkew = true;
+ // If there is no clock skew with Kinto servers, check against the build date.
+ // (The Kinto ping could have happened when the time was still right, or not at all)
+ } else {
+ let appBuildID = RPMGetAppBuildID();
+ let year = parseInt(appBuildID.substr(0, 4), 10);
+ let month = parseInt(appBuildID.substr(4, 2), 10) - 1;
+ let day = parseInt(appBuildID.substr(6, 2), 10);
+
+ let buildDate = new Date(year, month, day);
+ let systemDate = new Date();
+
+ // We don't check the notBefore of the cert with the build date,
+ // as it is of course almost certain that it is now later than the build date,
+ // so we shouldn't exclude the possibility that the cert has become valid
+ // since the build date.
+ if (
+ buildDate > systemDate &&
+ new Date(certRange.notAfter) > buildDate
+ ) {
+ clockSkew = true;
+ }
+ }
+
+ let systemDate = formatter.format(new Date());
+ document.getElementById(
+ "wrongSystemTime_systemDate1"
+ ).textContent = systemDate;
+ if (clockSkew) {
+ document.body.classList.add("illustrated", "clockSkewError");
+ document.l10n.setAttributes(titleElement, "clockSkewError-title");
+ let clockErrDesc = document.getElementById("ed_clockSkewError");
+ desc = document.getElementById("errorShortDescText");
+ document.getElementById("errorShortDesc").style.display = "block";
+ if (desc) {
+ // eslint-disable-next-line no-unsanitized/property
+ desc.innerHTML = clockErrDesc.innerHTML;
+ }
+ let errorPageContainer = document.getElementById("errorPageContainer");
+ let textContainer = document.getElementById("text-container");
+ errorPageContainer.style.backgroundPosition = `left top calc(50vh - ${textContainer.clientHeight /
+ 2}px)`;
+ } else {
+ let targetElems = document.querySelectorAll(
+ "#wrongSystemTime_systemDate2"
+ );
+ for (let elem of targetElems) {
+ elem.textContent = systemDate;
+ }
+
+ let errDesc = document.getElementById(
+ "ed_nssBadCert_SEC_ERROR_EXPIRED_CERTIFICATE"
+ );
+ let sd = document.getElementById("errorShortDescText");
+ // eslint-disable-next-line no-unsanitized/property
+ sd.innerHTML = errDesc.innerHTML;
+
+ let span = sd.querySelector(".hostname");
+ span.textContent = HOST_NAME;
+
+ // The secondary description mentions expired certificates explicitly
+ // and should only be shown if the certificate has actually expired
+ // instead of being not yet valid.
+ if (failedCertInfo.errorCodeString == "SEC_ERROR_EXPIRED_CERTIFICATE") {
+ let cssClass = getCSSClass();
+ let stsSuffix = cssClass == "badStsCert" ? "_sts" : "";
+ let errDesc2 = document.getElementById(
+ `ed2_nssBadCert_SEC_ERROR_EXPIRED_CERTIFICATE${stsSuffix}`
+ );
+ let sd2 = document.getElementById("errorShortDescText2");
+ // eslint-disable-next-line no-unsanitized/property
+ sd2.innerHTML = errDesc2.innerHTML;
+ if (
+ Math.abs(difference) <= 60 * 60 * 24 &&
+ now - lastFetched <= 60 * 60 * 24 * 5 * 1000
+ ) {
+ errWhatToDo = document.getElementById(
+ "es_nssBadCert_SSL_ERROR_BAD_CERT_DOMAIN"
+ );
+ }
+ }
+
+ if (es) {
+ // eslint-disable-next-line no-unsanitized/property
+ es.innerHTML = errWhatToDo.innerHTML;
+ }
+ if (est) {
+ // eslint-disable-next-line no-unsanitized/property
+ est.textContent = errWhatToDoTitle.textContent;
+ est.style.fontWeight = "bold";
+ }
+ updateContainerPosition();
+ }
+ break;
+ }
+
+ // Add slightly more alarming UI unless there are indicators that
+ // show that the error is harmless or can not be skipped anyway.
+ let cssClass = getCSSClass();
+ // Don't alarm users when they can't continue to the website anyway...
+ if (
+ cssClass != "badStsCert" &&
+ // Errors in iframes can't be skipped either...
+ window.parent == window &&
+ // Also don't bother if it's just the user's clock being off...
+ !clockSkew &&
+ // Symantec distrust is likely harmless as well.
+ failedCertInfo.errorCodeString !=
+ "MOZILLA_PKIX_ERROR_ADDITIONAL_POLICY_CONSTRAINT_FAILED"
+ ) {
+ document.body.classList.add("caution");
+ }
+}
+
+// The optional argument is only here for testing purposes.
+async function setTechnicalDetailsOnCertError(
+ failedCertInfo = document.getFailedCertSecurityInfo()
+) {
+ let technicalInfo = document.getElementById("badCertTechnicalInfo");
+
+ function setL10NLabel(l10nId, args = {}, attrs = {}, rewrite = true) {
+ let elem = document.createElement("label");
+ if (rewrite) {
+ technicalInfo.textContent = "";
+ }
+ technicalInfo.appendChild(elem);
+
+ let newLines = document.createTextNode("\n \n");
+ technicalInfo.appendChild(newLines);
+
+ if (attrs) {
+ let link = document.createElement("a");
+ for (let attr of Object.keys(attrs)) {
+ link.setAttribute(attr, attrs[attr]);
+ }
+ elem.appendChild(link);
+ }
+
+ if (args) {
+ document.l10n.setAttributes(elem, l10nId, args);
+ } else {
+ document.l10n.setAttributes(elem, l10nId);
+ }
+ }
+
+ let cssClass = getCSSClass();
+ let error = getErrorCode();
+
+ let hostString = HOST_NAME;
+ let port = document.location.port;
+ if (port && port != 443) {
+ hostString += ":" + port;
+ }
+
+ let l10nId;
+ let args = {
+ hostname: hostString,
+ };
+ if (failedCertInfo.isUntrusted) {
+ switch (failedCertInfo.errorCodeString) {
+ case "MOZILLA_PKIX_ERROR_MITM_DETECTED":
+ setL10NLabel("cert-error-mitm-intro");
+ setL10NLabel("cert-error-mitm-mozilla", {}, {}, false);
+ setL10NLabel("cert-error-mitm-connection", {}, {}, false);
+ break;
+ case "SEC_ERROR_UNKNOWN_ISSUER":
+ setL10NLabel("cert-error-trust-unknown-issuer-intro");
+ setL10NLabel("cert-error-trust-unknown-issuer", args, {}, false);
+ break;
+ case "SEC_ERROR_CA_CERT_INVALID":
+ setL10NLabel("cert-error-intro", args);
+ setL10NLabel("cert-error-trust-cert-invalid", {}, {}, false);
+ break;
+ case "SEC_ERROR_UNTRUSTED_ISSUER":
+ setL10NLabel("cert-error-intro", args);
+ setL10NLabel("cert-error-trust-untrusted-issuer", {}, {}, false);
+ break;
+ case "SEC_ERROR_CERT_SIGNATURE_ALGORITHM_DISABLED":
+ setL10NLabel("cert-error-intro", args);
+ setL10NLabel(
+ "cert-error-trust-signature-algorithm-disabled",
+ {},
+ {},
+ false
+ );
+ break;
+ case "SEC_ERROR_EXPIRED_ISSUER_CERTIFICATE":
+ setL10NLabel("cert-error-intro", args);
+ setL10NLabel("cert-error-trust-expired-issuer", {}, {}, false);
+ break;
+ case "MOZILLA_PKIX_ERROR_SELF_SIGNED_CERT":
+ setL10NLabel("cert-error-intro", args);
+ setL10NLabel("cert-error-trust-self-signed", {}, {}, false);
+ break;
+ case "MOZILLA_PKIX_ERROR_ADDITIONAL_POLICY_CONSTRAINT_FAILED":
+ setL10NLabel("cert-error-intro", args);
+ setL10NLabel("cert-error-trust-symantec", {}, {}, false);
+ break;
+ default:
+ setL10NLabel("cert-error-intro", args);
+ setL10NLabel("cert-error-untrusted-default", {}, {}, false);
+ }
+ } else if (failedCertInfo.isDomainMismatch) {
+ let serverCertBase64 = failedCertInfo.certChainStrings[0];
+ let parsed = await parse(pemToDER(serverCertBase64));
+ let subjectAltNamesExtension = parsed.ext.san;
+ let subjectAltNames = [];
+ if (subjectAltNamesExtension) {
+ for (let name of subjectAltNamesExtension.altNames) {
+ if (name[0] == "DNS Name" && name[1].length) {
+ subjectAltNames.push(name[1]);
+ }
+ }
+ }
+ let numSubjectAltNames = subjectAltNames.length;
+ if (numSubjectAltNames != 0) {
+ if (numSubjectAltNames == 1) {
+ args["alt-name"] = subjectAltNames[0];
+
+ // Let's check if we want to make this a link.
+ let okHost = subjectAltNames[0];
+ let href = "";
+ let thisHost = HOST_NAME;
+ let proto = document.location.protocol + "//";
+ // If okHost is a wildcard domain ("*.example.com") let's
+ // use "www" instead. "*.example.com" isn't going to
+ // get anyone anywhere useful. bug 432491
+ okHost = okHost.replace(/^\*\./, "www.");
+ /* case #1:
+ * example.com uses an invalid security certificate.
+ *
+ * The certificate is only valid for www.example.com
+ *
+ * Make sure to include the "." ahead of thisHost so that
+ * a MitM attack on paypal.com doesn't hyperlink to "notpaypal.com"
+ *
+ * We'd normally just use a RegExp here except that we lack a
+ * library function to escape them properly (bug 248062), and
+ * domain names are famous for having '.' characters in them,
+ * which would allow spurious and possibly hostile matches.
+ */
+
+ if (okHost.endsWith("." + thisHost)) {
+ href = proto + okHost;
+ }
+ /* case #2:
+ * browser.garage.maemo.org uses an invalid security certificate.
+ *
+ * The certificate is only valid for garage.maemo.org
+ */
+ if (thisHost.endsWith("." + okHost)) {
+ href = proto + okHost;
+ }
+
+ // If we set a link, meaning there's something helpful for
+ // the user here, expand the section by default
+ if (href && cssClass != "expertBadCert") {
+ document.getElementById("badCertAdvancedPanel").style.display =
+ "block";
+ if (error == "nssBadCert") {
+ // 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.
+ let div = document.getElementById(
+ "certificateErrorDebugInformation"
+ );
+ div.style.display = "none";
+ }
+ }
+
+ // Set the link if we want it.
+ if (href) {
+ setL10NLabel("cert-error-domain-mismatch-single", args, {
+ href,
+ "data-l10n-name": "domain-mismatch-link",
+ id: "cert_domain_link",
+ });
+ } else {
+ setL10NLabel("cert-error-domain-mismatch-single-nolink", args);
+ }
+ } else {
+ let names = subjectAltNames.join(", ");
+ args["subject-alt-names"] = names;
+ setL10NLabel("cert-error-domain-mismatch-multiple", args);
+ }
+ } else {
+ setL10NLabel("cert-error-domain-mismatch", { hostname: hostString });
+ }
+ } else if (failedCertInfo.isNotValidAtThisTime) {
+ let notBefore = failedCertInfo.validNotBefore;
+ let notAfter = failedCertInfo.validNotAfter;
+ args = {
+ hostname: hostString,
+ };
+ if (notBefore && Date.now() < notAfter) {
+ let notBeforeLocalTime = formatter.format(new Date(notBefore));
+ l10nId = "cert-error-not-yet-valid-now";
+ args["not-before-local-time"] = notBeforeLocalTime;
+ } else {
+ let notAfterLocalTime = formatter.format(new Date(notAfter));
+ l10nId = "cert-error-expired-now";
+ args["not-after-local-time"] = notAfterLocalTime;
+ }
+ setL10NLabel(l10nId, args);
+ }
+
+ setL10NLabel(
+ "cert-error-code-prefix-link",
+ { error: failedCertInfo.errorCodeString },
+ {
+ title: failedCertInfo.errorCodeString,
+ id: "errorCode",
+ "data-l10n-name": "error-code-link",
+ "data-telemetry-id": "error_code_link",
+ },
+ false
+ );
+ let errorCodeLink = document.getElementById("errorCode");
+ if (errorCodeLink) {
+ // We're attaching the event listener to the parent element and not on
+ // the errorCodeLink itself because event listeners cannot be attached
+ // to fluent DOM overlays.
+ technicalInfo.addEventListener("click", handleErrorCodeClick);
+ }
+
+ let div = document.getElementById("certificateErrorText");
+ div.textContent = await getFailedCertificatesAsPEMString();
+}
+
+function handleErrorCodeClick(event) {
+ if (event.target.id !== "errorCode") {
+ return;
+ }
+
+ let debugInfo = document.getElementById("certificateErrorDebugInformation");
+ debugInfo.style.display = "block";
+ debugInfo.scrollIntoView({ block: "start", behavior: "smooth" });
+ recordClickTelemetry(event);
+}
+
+/* Only do autofocus if we're the toplevel frame; otherwise we
+ don't want to call attention to ourselves! The key part is
+ that autofocus happens on insertion into the tree, so we
+ can remove the button, add @autofocus, and reinsert the
+ button.
+*/
+function addAutofocus(selector, position = "afterbegin") {
+ if (window.top == window) {
+ var button = document.querySelector(selector);
+ var parent = button.parentNode;
+ button.remove();
+ button.setAttribute("autofocus", "true");
+ parent.insertAdjacentElement(position, button);
+ }
+}
+
+for (let button of document.querySelectorAll(".try-again")) {
+ button.addEventListener("click", function() {
+ retryThis(this);
+ });
+}
+
+window.addEventListener("DOMContentLoaded", () => {
+ // Expose this so tests can call it.
+ window.setTechnicalDetailsOnCertError = setTechnicalDetailsOnCertError;
+
+ initPage();
+ // Dispatch this event so tests can detect that we finished loading the error page.
+ let event = new CustomEvent("AboutNetErrorLoad", { bubbles: true });
+ document.dispatchEvent(event);
+});