diff --git a/browser/actors/NetErrorChild.jsm b/browser/actors/NetErrorChild.jsm index b6d5543fc867..1014bb8c2a00 100644 --- a/browser/actors/NetErrorChild.jsm +++ b/browser/actors/NetErrorChild.jsm @@ -17,44 +17,19 @@ XPCOMUtils.defineLazyGlobalGetters(this, ["URL"]); XPCOMUtils.defineLazyGetter(this, "gPipNSSBundle", function() { return Services.strings.createBundle("chrome://pipnss/locale/pipnss.properties"); }); -XPCOMUtils.defineLazyPreferenceGetter(this, "mitmErrorPageEnabled", - "browser.security.newcerterrorpage.mitm.enabled"); -XPCOMUtils.defineLazyPreferenceGetter(this, "mitmPrimingEnabled", - "security.certerrors.mitm.priming.enabled"); XPCOMUtils.defineLazyGetter(this, "gNSSErrorsBundle", function() { return Services.strings.createBundle("chrome://pipnss/locale/nsserrors.properties"); }); - const SEC_ERROR_BASE = Ci.nsINSSErrorsService.NSS_SEC_ERROR_BASE; -const MOZILLA_PKIX_ERROR_BASE = Ci.nsINSSErrorsService.MOZILLA_PKIX_ERROR_BASE; - -const SEC_ERROR_EXPIRED_CERTIFICATE = SEC_ERROR_BASE + 11; -const SEC_ERROR_UNKNOWN_ISSUER = SEC_ERROR_BASE + 13; -const SEC_ERROR_EXPIRED_ISSUER_CERTIFICATE = SEC_ERROR_BASE + 30; const SEC_ERROR_REUSED_ISSUER_AND_SERIAL = SEC_ERROR_BASE + 138; -const SEC_ERROR_OCSP_INVALID_SIGNING_CERT = SEC_ERROR_BASE + 144; -const MOZILLA_PKIX_ERROR_NOT_YET_VALID_CERTIFICATE = MOZILLA_PKIX_ERROR_BASE + 5; -const MOZILLA_PKIX_ERROR_NOT_YET_VALID_ISSUER_CERTIFICATE = MOZILLA_PKIX_ERROR_BASE + 6; -const MOZILLA_PKIX_ERROR_ADDITIONAL_POLICY_CONSTRAINT_FAILED = MOZILLA_PKIX_ERROR_BASE + 13; -const MOZILLA_PKIX_ERROR_SELF_SIGNED_CERT = MOZILLA_PKIX_ERROR_BASE + 14; -const MOZILLA_PKIX_ERROR_MITM_DETECTED = MOZILLA_PKIX_ERROR_BASE + 15; - const SSL_ERROR_BASE = Ci.nsINSSErrorsService.NSS_SSL_ERROR_BASE; -const SSL_ERROR_BAD_CERT_DOMAIN = SSL_ERROR_BASE + 12; const SSL_ERROR_SSL_DISABLED = SSL_ERROR_BASE + 20; const SSL_ERROR_SSL2_DISABLED = SSL_ERROR_BASE + 14; -const PREF_SERVICES_SETTINGS_CLOCK_SKEW_SECONDS = "services.settings.clock_skew_seconds"; -const PREF_SERVICES_SETTINGS_LAST_FETCHED = "services.settings.last_update_seconds"; - const PREF_SSL_IMPACT_ROOTS = ["security.tls.version.", "security.ssl3."]; -let formatter = new Services.intl.DateTimeFormat(undefined, { - dateStyle: "long", -}); - function getSerializedSecurityInfo(docShell) { let serhelper = Cc["@mozilla.org/network/serialization-helper;1"] .getService(Ci.nsISerializationHelper); @@ -86,287 +61,6 @@ class NetErrorChild extends ActorChild { }; } - _getCertValidityRange(docShell) { - let {securityInfo} = docShell.failedChannel; - securityInfo.QueryInterface(Ci.nsITransportSecurityInfo); - let notBefore = 0; - let notAfter = Number.MAX_SAFE_INTEGER; - for (let cert of securityInfo.failedCertChain.getEnumerator()) { - notBefore = Math.max(notBefore, cert.validity.notBefore); - notAfter = Math.min(notAfter, cert.validity.notAfter); - } - // nsIX509Cert reports in PR_Date terms, which uses microseconds. Convert: - notBefore /= 1000; - notAfter /= 1000; - return {notBefore, notAfter}; - } - - // eslint-disable-next-line complexity - onCertErrorDetails(msg, docShell) { - let doc = docShell.document; - - // This function centers the error container after its content updates. - // It is currently duplicated in aboutNetError.js 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 = doc.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 (doc.ownerGlobal.parent == doc.ownerGlobal) { - textContainer.style.marginTop = `calc(50vh - ${textContainer.clientHeight / 2}px)`; - } else { - let offset = (doc.documentElement.clientHeight / 2) - (textContainer.clientHeight / 2); - if (offset > 0) { - textContainer.style.marginTop = `${offset}px`; - } - } - } - - // 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). - if (mitmPrimingEnabled && msg.data.code == SEC_ERROR_UNKNOWN_ISSUER && - // Only do this check for top-level failures. - doc.ownerGlobal.top === doc.ownerGlobal) { - this.mm.sendAsyncMessage("Browser:PrimeMitm"); - } - - let div = doc.getElementById("certificateErrorText"); - div.textContent = msg.data.info; - let learnMoreLink = doc.getElementById("learnMoreLink"); - let baseURL = Services.urlFormatter.formatURLPref("app.support.baseURL"); - learnMoreLink.setAttribute("href", baseURL + "connection-not-secure"); - let errWhatToDo = doc.getElementById("es_nssBadCert_" + msg.data.codeString); - let es = doc.getElementById("errorWhatToDoText"); - let errWhatToDoTitle = doc.getElementById("edd_nssBadCert"); - let est = doc.getElementById("errorWhatToDoTitleText"); - let searchParams = new URLSearchParams(doc.documentURI.split("?")[1]); - let error = searchParams.get("e"); - - if (error == "sslv3Used") { - learnMoreLink.setAttribute("href", baseURL + "sslv3-error-messages"); - } - - if (error == "nssFailure2") { - let shortDesc = doc.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; - - doc.body.setAttribute("code", msg.data.codeString); - - // Need to do this here (which is not exactly at load but a few ticks later), - // because this is the first time we have access to the error code. - this.recordLoadEvent(doc); - - switch (msg.data.code) { - 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: - let description = gPipNSSBundle.formatStringFromName( - "certErrorSymantecDistrustDescription1", [doc.location.hostname]); - let descriptionContainer = doc.getElementById("errorShortDescText2"); - descriptionContainer.textContent = description; - - let adminDescription = doc.createElement("p"); - adminDescription.textContent = - gPipNSSBundle.GetStringFromName("certErrorSymantecDistrustAdministrator"); - descriptionContainer.append(adminDescription); - - learnMoreLink.href = baseURL + "symantec-warning"; - - updateContainerPosition(); - break; - case MOZILLA_PKIX_ERROR_MITM_DETECTED: - if (mitmErrorPageEnabled) { - let autoEnabledEnterpriseRoots = - Services.prefs.getBoolPref("security.enterprise_roots.auto-enabled", false); - if (mitmPrimingEnabled && autoEnabledEnterpriseRoots) { - // If we automatically tried to import enterprise root certs but it didn't - // fix the MITM, reset the pref. - this.mm.sendAsyncMessage("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 {securityInfo} = docShell.failedChannel; - securityInfo.QueryInterface(Ci.nsITransportSecurityInfo); - let mitmName = null; - for (let cert of securityInfo.failedCertChain.getEnumerator()) { - mitmName = cert.issuerCommonName; - } - for (let span of doc.querySelectorAll(".mitm-name")) { - span.textContent = mitmName; - } - - learnMoreLink.href = baseURL + "security-error"; - - let title = doc.getElementById("et_mitm"); - let desc = doc.getElementById("ed_mitm"); - doc.querySelector(".title-text").textContent = title.textContent; - // eslint-disable-next-line no-unsanitized/property - doc.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; - } - // If the condition is false, fall through... - 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 = Services.prefs.getIntPref(PREF_SERVICES_SETTINGS_CLOCK_SKEW_SECONDS, 0); - let lastFetched = Services.prefs.getIntPref(PREF_SERVICES_SETTINGS_LAST_FETCHED, 0) * 1000; - - let now = Date.now(); - let certRange = this._getCertValidityRange(docShell); - - 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 && - 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 = Services.appinfo.appBuildID; - - 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()); - doc.getElementById("wrongSystemTime_systemDate1").textContent = systemDate; - if (clockSkew) { - doc.body.classList.add("illustrated", "clockSkewError"); - let clockErrTitle = doc.getElementById("et_clockSkewError"); - let clockErrDesc = doc.getElementById("ed_clockSkewError"); - // eslint-disable-next-line no-unsanitized/property - doc.querySelector(".title-text").textContent = clockErrTitle.textContent; - let desc = doc.getElementById("errorShortDescText"); - doc.getElementById("errorShortDesc").style.display = "block"; - doc.getElementById("certificateErrorReporting").style.display = "none"; - if (desc) { - // eslint-disable-next-line no-unsanitized/property - desc.innerHTML = clockErrDesc.innerHTML; - } - let errorPageContainer = doc.getElementById("errorPageContainer"); - let textContainer = doc.getElementById("text-container"); - errorPageContainer.style.backgroundPosition = `left top calc(50vh - ${textContainer.clientHeight / 2}px)`; - } else { - let targetElems = doc.querySelectorAll("#wrongSystemTime_systemDate2"); - for (let elem of targetElems) { - elem.textContent = systemDate; - } - - let errDesc = doc.getElementById("ed_nssBadCert_SEC_ERROR_EXPIRED_CERTIFICATE"); - let sd = doc.getElementById("errorShortDescText"); - // eslint-disable-next-line no-unsanitized/property - sd.innerHTML = errDesc.innerHTML; - - let span = sd.querySelector(".hostname"); - span.textContent = doc.location.hostname; - - // 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 (msg.data.code == SEC_ERROR_EXPIRED_CERTIFICATE) { - let {cssClass} = this.getParams(doc); - let stsSuffix = cssClass == "badStsCert" ? "_sts" : ""; - let errDesc2 = doc.getElementById( - `ed2_nssBadCert_SEC_ERROR_EXPIRED_CERTIFICATE${stsSuffix}`); - let sd2 = doc.getElementById("errorShortDescText2"); - // eslint-disable-next-line no-unsanitized/property - sd2.innerHTML = errDesc2.innerHTML; - } - - 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} = this.getParams(doc); - // Don't alarm users when they can't continue to the website anyway... - if (cssClass != "badStsCert" && - // Errors in iframes can't be skipped either... - doc.ownerGlobal.parent == doc.ownerGlobal && - // Also don't bother if it's just the user's clock being off... - !clockSkew && - // Symantec distrust is likely harmless as well. - msg.data.code != MOZILLA_PKIX_ERROR_ADDITIONAL_POLICY_CONSTRAINT_FAILED) { - doc.body.classList.add("caution"); - } - } - handleEvent(aEvent) { // Documents have a null ownerDocument. let doc = aEvent.originalTarget.ownerDocument || aEvent.originalTarget; @@ -405,7 +99,10 @@ class NetErrorChild extends ActorChild { return; } - this.onCertErrorDetails(msg, frameDocShell); + let data = msg.data; + let win = frameDocShell.document.ownerGlobal; + let event = Cu.cloneInto({ detail: data }, win); + win.dispatchEvent(new win.CustomEvent("ShowCertErrorDetails", event)); } } @@ -547,16 +244,6 @@ class NetErrorChild extends ActorChild { return searchParams.get("s"); } - recordLoadEvent(doc) { - let cssClass = this.getCSSClass(doc); - // Telemetry values for events are max. 80 bytes. - let errorCode = doc.body.getAttribute("code").substring(0, 40); - Services.telemetry.recordEvent("security.ui.certerror", "load", "aboutcerterror", errorCode, { - "has_sts": (cssClass == "badStsCert").toString(), - "is_frame": (doc.ownerGlobal.parent != doc.ownerGlobal).toString(), - }); - } - recordClick(element) { let telemetryId = element.dataset.telemetryId; if (!telemetryId) { diff --git a/browser/base/content/aboutNetError.js b/browser/base/content/aboutNetError.js index 45b79cd813c3..24420a69f3a0 100644 --- a/browser/base/content/aboutNetError.js +++ b/browser/base/content/aboutNetError.js @@ -1,5 +1,7 @@ /* eslint-env mozilla/frame-script */ +const formatter = new Intl.DateTimeFormat("default"); + // The following parameters are parsed from the error URL: // e - the error code // s - custom CSS class to allow alternate styling/favicons @@ -361,10 +363,248 @@ function initPageCertError() { } }, true, true); + let failedCertInfo = document.getFailedCertSecurityInfo(); + RPMSendAsyncMessage("RecordCertErrorLoad", { + // Telemetry values for events are max. 80 bytes. + errorCode: failedCertInfo.errorCodeString.substring(0, 40), + has_sts: getCSSClass() == "badStsCert", + is_frame: window.parent != window, + }); + window.addEventListener("ShowCertErrorDetails", setCertErrorDetails); + setTechnicalDetailsOnCertError(); + let event = new CustomEvent("AboutNetErrorLoad", {bubbles: true}); document.getElementById("advancedButton").dispatchEvent(event); +} - setTechnicalDetailsOnCertError(); +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 div = document.getElementById("certificateErrorText"); + div.textContent = event.detail.info; + 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 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"); + let hostname = document.location.hostname; + document.l10n.setAttributes(desc, "cert-error-symantec-distrust-description", { + hostname, + }); + + 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"); + 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"; + + let title = document.getElementById("et_mitm"); + desc = document.getElementById("ed_mitm"); + document.querySelector(".title-text").textContent = title.textContent; + // 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 = event.detail.clockSkewDifference; + let lastFetched = event.detail.settingsLastFetched * 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 && + 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 = event.detail.appBuildID; + 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"); + let clockErrTitle = document.getElementById("et_clockSkewError"); + let clockErrDesc = document.getElementById("ed_clockSkewError"); + // eslint-disable-next-line no-unsanitized/property + document.querySelector(".title-text").textContent = clockErrTitle.textContent; + desc = document.getElementById("errorShortDescText"); + document.getElementById("errorShortDesc").style.display = "block"; + document.getElementById("certificateErrorReporting").style.display = "none"; + 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 = document.location.hostname; + + // 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 (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.erroCodeString != "MOZILLA_PKIX_ERROR_ADDITIONAL_POLICY_CONSTRAINT_FAILED") { + document.body.classList.add("caution"); + } } function setTechnicalDetailsOnCertError() { @@ -530,7 +770,6 @@ function setTechnicalDetailsOnCertError() { if (failedCertInfo.isNotValidAtThisTime) { let notBefore = failedCertInfo.validNotBefore; let notAfter = failedCertInfo.validNotAfter; - let formatter = new Intl.DateTimeFormat("default"); args = { hostname: hostString, }; diff --git a/browser/base/content/browser.js b/browser/base/content/browser.js index 8e07727647e2..ef3f3c440f21 100644 --- a/browser/base/content/browser.js +++ b/browser/base/content/browser.js @@ -3034,8 +3034,6 @@ var BrowserOnClick = { mm.addMessageListener("Browser:ResetSSLPreferences", this); mm.addMessageListener("Browser:SSLErrorReportTelemetry", this); mm.addMessageListener("Browser:SSLErrorGoBack", this); - mm.addMessageListener("Browser:PrimeMitm", this); - mm.addMessageListener("Browser:ResetEnterpriseRootsPref", this); }, uninit() { @@ -3047,8 +3045,6 @@ var BrowserOnClick = { mm.removeMessageListener("Browser:ResetSSLPreferences", this); mm.removeMessageListener("Browser:SSLErrorReportTelemetry", this); mm.removeMessageListener("Browser:SSLErrorGoBack", this); - mm.removeMessageListener("Browser:PrimeMitm", this); - mm.removeMessageListener("Browser:ResetEnterpriseRootsPref", this); }, receiveMessage(msg) { @@ -3096,76 +3092,9 @@ var BrowserOnClick = { case "Browser:SSLErrorGoBack": goBackFromErrorPage(); break; - case "Browser:PrimeMitm": - this.primeMitm(msg.target); - break; - case "Browser:ResetEnterpriseRootsPref": - Services.prefs.clearUserPref("security.enterprise_roots.enabled"); - Services.prefs.clearUserPref("security.enterprise_roots.auto-enabled"); - break; } }, - /** - * This function does a canary request to a reliable, maintained endpoint, in - * order to help network code detect a system-wide man-in-the-middle. - */ - primeMitm(browser) { - // If we already have a mitm canary issuer stored, then don't bother with the - // extra request. This will be cleared on every update ping. - if (Services.prefs.getStringPref("security.pki.mitm_canary_issuer", null)) { - return; - } - - let url = Services.prefs.getStringPref("security.certerrors.mitm.priming.endpoint"); - let request = new XMLHttpRequest({mozAnon: true}); - request.open("HEAD", url); - request.channel.loadFlags |= Ci.nsIRequest.LOAD_BYPASS_CACHE; - request.channel.loadFlags |= Ci.nsIRequest.INHIBIT_CACHING; - - request.addEventListener("error", event => { - // Make sure the user is still on the cert error page. - if (!browser.documentURI.spec.startsWith("about:certerror")) { - return; - } - - let secInfo = request.channel.securityInfo.QueryInterface(Ci.nsITransportSecurityInfo); - if (secInfo.errorCode != SEC_ERROR_UNKNOWN_ISSUER) { - return; - } - - // When we get to this point there's already something deeply wrong, it's very likely - // that there is indeed a system-wide MitM. - if (secInfo.serverCert && secInfo.serverCert.issuerName) { - // Grab the issuer of the certificate used in the exchange and store it so that our - // network-level MitM detection code has a comparison baseline. - Services.prefs.setStringPref("security.pki.mitm_canary_issuer", secInfo.serverCert.issuerName); - - // MitM issues are sometimes caused by software not registering their root certs in the - // Firefox root store. We might opt for using third party roots from the system root store. - if (Services.prefs.getBoolPref("security.certerrors.mitm.auto_enable_enterprise_roots")) { - if (!Services.prefs.getBoolPref("security.enterprise_roots.enabled")) { - // Loading enterprise roots happens on a background thread, so wait for import to finish. - BrowserUtils.promiseObserved("psm:enterprise-certs-imported").then(() => { - if (browser.documentURI.spec.startsWith("about:certerror")) { - browser.reload(); - } - }); - - Services.prefs.setBoolPref("security.enterprise_roots.enabled", true); - // Record that this pref was automatically set. - Services.prefs.setBoolPref("security.enterprise_roots.auto-enabled", true); - } - } else { - // Need to reload the page to make sure network code picks up the canary issuer pref. - browser.reload(); - } - } - }); - - request.send(null); - }, - onCertError(browser, elementId, isTopFrame, location, securityInfoAsString, frameId) { let securityInfo; let cert; @@ -3214,11 +3143,15 @@ var BrowserOnClick = { securityInfo = getSecurityInfo(securityInfoAsString); let errorInfo = getDetailedCertErrorInfo(location, securityInfo); + let clockSkewDifference = Services.prefs.getIntPref("services.settings.clock_skew_seconds", 0); + let settingsLastFetched = Services.prefs.getIntPref("services.settings.last_update_seconds", 0); + let appBuildID = Services.appinfo.appBuildID; browser.messageManager.sendAsyncMessage("CertErrorDetails", { - code: securityInfo.errorCode, - info: errorInfo, - codeString: securityInfo.errorCodeString, - frameId, + info: errorInfo, + clockSkewDifference, + settingsLastFetched, + appBuildID, + frameId, }); break; diff --git a/browser/base/content/test/about/browser_aboutCertError_clockSkew.js b/browser/base/content/test/about/browser_aboutCertError_clockSkew.js index 5e38cd19605e..29d55b2cb9e4 100644 --- a/browser/base/content/test/about/browser_aboutCertError_clockSkew.js +++ b/browser/base/content/test/about/browser_aboutCertError_clockSkew.js @@ -40,9 +40,7 @@ add_task(async function checkWrongSystemTimeWarning() { // Pretend that we recently updated our kinto clock skew pref Services.prefs.setIntPref(PREF_SERVICES_SETTINGS_LAST_FETCHED, Math.floor(Date.now() / 1000)); - let formatter = new Services.intl.DateTimeFormat(undefined, { - dateStyle: "long", - }); + let formatter = new Intl.DateTimeFormat("default"); // For this test, we want to trick Firefox into believing that // the local system time (as returned by Date.now()) is wrong. diff --git a/browser/base/content/test/about/browser_aboutCertError_telemetry.js b/browser/base/content/test/about/browser_aboutCertError_telemetry.js index 786f8fa0f702..2dc87958f46e 100644 --- a/browser/base/content/test/about/browser_aboutCertError_telemetry.js +++ b/browser/base/content/test/about/browser_aboutCertError_telemetry.js @@ -64,7 +64,7 @@ add_task(async function checkTelemetryClickEvents() { let loadEvents = await TestUtils.waitForCondition(() => { let events = Services.telemetry.snapshotEvents( - Ci.nsITelemetry.DATASET_PRERELEASE_CHANNELS, true).content; + Ci.nsITelemetry.DATASET_PRERELEASE_CHANNELS, true).parent; if (events && events.length) { events = events.filter(e => e[1] == "security.ui.certerror" && e[2] == "load"); if (events.length == 1 && events[0][5].is_frame == useFrame.toString()) { diff --git a/browser/components/about/AboutNetErrorHandler.jsm b/browser/components/about/AboutNetErrorHandler.jsm index 0906e581e287..a04dfdfbe7ae 100644 --- a/browser/components/about/AboutNetErrorHandler.jsm +++ b/browser/components/about/AboutNetErrorHandler.jsm @@ -8,14 +8,23 @@ var EXPORTED_SYMBOLS = ["AboutNetErrorHandler"]; const {RemotePages} = ChromeUtils.import("resource://gre/modules/remotepagemanager/RemotePageManagerParent.jsm"); const {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm"); +ChromeUtils.defineModuleGetter(this, "BrowserUtils", "resource://gre/modules/BrowserUtils.jsm"); var AboutNetErrorHandler = { _inited: false, + _topics: [ + "Browser:OpenCaptivePortalPage", + "Browser:PrimeMitm", + "Browser:ResetEnterpriseRootsPref", + "RecordCertErrorLoad", + ], init() { this._boundReceiveMessage = this.receiveMessage.bind(this); this.pageListener = new RemotePages(["about:certerror", "about:neterror"]); - this.pageListener.addMessageListener("Browser:OpenCaptivePortalPage", this._boundReceiveMessage); + for (let topic of this._topics) { + this.pageListener.addMessageListener(topic, this._boundReceiveMessage); + } this._inited = true; Services.obs.addObserver(this, "captive-portal-login-abort"); @@ -27,7 +36,9 @@ var AboutNetErrorHandler = { return; } - this.pageListener.removeMessageListener("Browser:OpenCaptivePortalPage", this._boundReceiveMessage); + for (let topic of this._topics) { + this.pageListener.removeMessageListener(topic, this._boundReceiveMessage); + } this.pageListener.destroy(); Services.obs.removeObserver(this, "captive-portal-login-abort"); @@ -50,6 +61,79 @@ var AboutNetErrorHandler = { case "Browser:OpenCaptivePortalPage": Services.obs.notifyObservers(null, "ensure-captive-portal-tab"); break; + case "Browser:PrimeMitm": + this.primeMitm(msg.target.browser); + break; + case "Browser:ResetEnterpriseRootsPref": + Services.prefs.clearUserPref("security.enterprise_roots.enabled"); + Services.prefs.clearUserPref("security.enterprise_roots.auto-enabled"); + break; + case "RecordCertErrorLoad": + Services.telemetry.recordEvent("security.ui.certerror", "load", "aboutcerterror", msg.data.errorCode, { + has_sts: msg.data.has_sts.toString(), + is_frame: msg.data.is_frame.toString(), + }); + break; } }, + + /** + * This function does a canary request to a reliable, maintained endpoint, in + * order to help network code detect a system-wide man-in-the-middle. + */ + primeMitm(browser) { + // If we already have a mitm canary issuer stored, then don't bother with the + // extra request. This will be cleared on every update ping. + if (Services.prefs.getStringPref("security.pki.mitm_canary_issuer", null)) { + return; + } + + let url = Services.prefs.getStringPref("security.certerrors.mitm.priming.endpoint"); + let request = new XMLHttpRequest({mozAnon: true}); + request.open("HEAD", url); + request.channel.loadFlags |= Ci.nsIRequest.LOAD_BYPASS_CACHE; + request.channel.loadFlags |= Ci.nsIRequest.INHIBIT_CACHING; + + request.addEventListener("error", event => { + // Make sure the user is still on the cert error page. + if (!browser.documentURI.spec.startsWith("about:certerror")) { + return; + } + + let secInfo = request.channel.securityInfo.QueryInterface(Ci.nsITransportSecurityInfo); + if (secInfo.errorCodeString != "SEC_ERROR_UNKNOWN_ISSUER") { + return; + } + + // When we get to this point there's already something deeply wrong, it's very likely + // that there is indeed a system-wide MitM. + if (secInfo.serverCert && secInfo.serverCert.issuerName) { + // Grab the issuer of the certificate used in the exchange and store it so that our + // network-level MitM detection code has a comparison baseline. + Services.prefs.setStringPref("security.pki.mitm_canary_issuer", secInfo.serverCert.issuerName); + + // MitM issues are sometimes caused by software not registering their root certs in the + // Firefox root store. We might opt for using third party roots from the system root store. + if (Services.prefs.getBoolPref("security.certerrors.mitm.auto_enable_enterprise_roots")) { + if (!Services.prefs.getBoolPref("security.enterprise_roots.enabled")) { + // Loading enterprise roots happens on a background thread, so wait for import to finish. + BrowserUtils.promiseObserved("psm:enterprise-certs-imported").then(() => { + if (browser.documentURI.spec.startsWith("about:certerror")) { + browser.reload(); + } + }); + + Services.prefs.setBoolPref("security.enterprise_roots.enabled", true); + // Record that this pref was automatically set. + Services.prefs.setBoolPref("security.enterprise_roots.auto-enabled", true); + } + } else { + // Need to reload the page to make sure network code picks up the canary issuer pref. + browser.reload(); + } + } + }); + + request.send(null); + }, }; diff --git a/browser/locales/en-US/browser/aboutCertError.ftl b/browser/locales/en-US/browser/aboutCertError.ftl index 4137cd797ac3..ea48c9c6991a 100644 --- a/browser/locales/en-US/browser/aboutCertError.ftl +++ b/browser/locales/en-US/browser/aboutCertError.ftl @@ -64,3 +64,9 @@ cert-error-not-yet-valid-now = Websites prove their identity via certificates, w # $error (String) - NSS error code string that specifies type of cert error. e.g. unknown issuer, invalid cert, etc. cert-error-code-prefix-link = Error code: { $error } +# Variables: +# $hostname (String) - Hostname of the website with cert error. +cert-error-symantec-distrust-description = Websites prove their identity via certificates, which are issued by certificate authorities. Most browsers no longer trust certificates issued by GeoTrust, RapidSSL, Symantec, Thawte, and VeriSign. { $hostname } uses a certificate from one of these authorities and so the website’s identity cannot be proven. + +cert-error-symantec-distrust-admin = You may notify the website’s administrator about this problem. + diff --git a/browser/themes/shared/aboutNetError.css b/browser/themes/shared/aboutNetError.css index f539f61f4287..9cbd88515f19 100644 --- a/browser/themes/shared/aboutNetError.css +++ b/browser/themes/shared/aboutNetError.css @@ -247,10 +247,6 @@ body:not(.neterror) #advancedButton { display: none; } -#wrongSystemTimePanel { - display: none; -} - #wrongSystemTimeWithoutReferencePanel { display: none; } diff --git a/python/l10n/fluent_migrations/bug_1552333_aboutCertError.py b/python/l10n/fluent_migrations/bug_1552333_aboutCertError.py new file mode 100644 index 000000000000..f4c5e3decb09 --- /dev/null +++ b/python/l10n/fluent_migrations/bug_1552333_aboutCertError.py @@ -0,0 +1,37 @@ +# coding=utf8 + +# Any copyright is dedicated to the Public Domain. +# http://creativecommons.org/publicdomain/zero/1.0/ + +from __future__ import absolute_import +import fluent.syntax.ast as FTL +from fluent.migrate.helpers import transforms_from +from fluent.migrate.helpers import VARIABLE_REFERENCE +from fluent.migrate import COPY, REPLACE + +def migrate(ctx): + """Bug 1552333 - Migrate strings from pipnss.properties to aboutCertError.ftl""" + ctx.add_transforms( + 'browser/browser/aboutCertError.ftl', + 'browser/browser/aboutCertError.ftl', + transforms_from( +""" +cert-error-symantec-distrust-admin = { COPY(from_path, "certErrorSymantecDistrustAdministrator") } +""", from_path="security/manager/chrome/pipnss/pipnss.properties")) + ctx.add_transforms( + 'browser/browser/aboutCertError.ftl', + 'browser/browser/aboutCertError.ftl', + [ + FTL.Message( + id=FTL.Identifier('cert-error-symantec-distrust-description'), + value=REPLACE( + 'security/manager/chrome/pipnss/pipnss.properties', + 'certErrorSymantecDistrustDescription1', + { + "%1$S": VARIABLE_REFERENCE("hostname"), + }, + normalize_printf=True + ), + ), + ] + ) diff --git a/security/manager/locales/en-US/chrome/pipnss/pipnss.properties b/security/manager/locales/en-US/chrome/pipnss/pipnss.properties index fdb2672e6c39..9cf7b84d3e0d 100755 --- a/security/manager/locales/en-US/chrome/pipnss/pipnss.properties +++ b/security/manager/locales/en-US/chrome/pipnss/pipnss.properties @@ -285,8 +285,6 @@ certErrorMitM2=%S is backed by the non-profit Mozilla, which administers a compl # LOCALIZATION NOTE (certErrorMitM3): %S is brandShortName certErrorMitM3=%S uses the Mozilla CA store to verify that a connection is secure, rather than certificates supplied by the user’s operating system. So, if an antivirus program or a network is intercepting a connection with a security certificate issued by a CA that is not in the Mozilla CA store, the connection is considered unsafe. -# LOCALIZATION NOTE (certErrorSymantecDistrustDescription1): %S will be replaced by the domain for which the certificate is valid. -certErrorSymantecDistrustDescription1=Websites prove their identity via certificates, which are issued by certificate authorities. Most browsers no longer trust certificates issued by GeoTrust, RapidSSL, Symantec, Thawte, and VeriSign. %S uses a certificate from one of these authorities and so the website’s identity cannot be proven. certErrorSymantecDistrustAdministrator=You may notify the website’s administrator about this problem. # LOCALIZATION NOTE (certErrorCodePrefix3): %S is replaced by the error code. diff --git a/toolkit/components/remotepagemanager/MessagePort.jsm b/toolkit/components/remotepagemanager/MessagePort.jsm index 4fd6c479c6c4..d8e213c12832 100644 --- a/toolkit/components/remotepagemanager/MessagePort.jsm +++ b/toolkit/components/remotepagemanager/MessagePort.jsm @@ -25,6 +25,11 @@ ChromeUtils.defineModuleGetter(this, "UpdateUtils", */ let RPMAccessManager = { accessMap: { + "about:certerror": { + "getFormatURLPref": ["app.support.baseURL"], + "getBoolPref": ["security.certerrors.mitm.priming.enabled", + "security.enterprise_roots.auto-enabled"], + }, "about:privatebrowsing": { // "sendAsyncMessage": handled within AboutPrivateBrowsingHandler.jsm "getFormatURLPref": ["app.support.baseURL"], @@ -41,7 +46,11 @@ let RPMAccessManager = { if (!aPrincipal || !aPrincipal.URI) { return false; } + let uri = aPrincipal.URI.asciiSpec; + if (uri.startsWith("about:certerror")) { + uri = "about:certerror"; + } // check if there is an entry for that requestying URI in the accessMap; // if not, deny access. diff --git a/toolkit/components/telemetry/Events.yaml b/toolkit/components/telemetry/Events.yaml index 4c8c8c8b125b..a1d978562367 100644 --- a/toolkit/components/telemetry/Events.yaml +++ b/toolkit/components/telemetry/Events.yaml @@ -1052,7 +1052,7 @@ security.ui.certerror: - rtestard@mozilla.com - seceng-telemetry@mozilla.com release_channel_collection: opt-out - record_in_processes: ["content"] + record_in_processes: ["main", "content"] products: - firefox extra_keys: