tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

commit 477a9f6298d4ae7f87ff59007f3cb89d7a82e857
parent fcb93f624bcd427196c509cb0900727e99a00e83
Author: Jack Brown <jbrown@mozilla.com>
Date:   Wed,  7 Jan 2026 19:26:14 +0000

Bug 2003439 - Error handling for invalid certificates - r=niklas,fluent-reviewers,bolsson,keeler

Differential Revision: https://phabricator.services.mozilla.com/D276292

Diffstat:
Mbrowser/base/content/test/about/browser.toml | 2++
Abrowser/base/content/test/about/browser_aboutNetError_invalid_cert_noUserFix.js | 101+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mdom/base/Document.cpp | 69+++++++++++++++++++++++++++++----------------------------------------
Mtoolkit/content/aboutNetError.html | 1+
Mtoolkit/content/net-error-card.mjs | 75+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----
Mtoolkit/locales/en-US/toolkit/neterror/certError.ftl | 5+++++
6 files changed, 209 insertions(+), 44 deletions(-)

diff --git a/browser/base/content/test/about/browser.toml b/browser/base/content/test/about/browser.toml @@ -74,6 +74,8 @@ support-files = [ ["browser_aboutNetError_internet_connection_offline.js"] +["browser_aboutNetError_invalid_cert_noUserFix.js"] + ["browser_aboutNetError_invalid_header.js"] support-files = [ "invalid_header.sjs", diff --git a/browser/base/content/test/about/browser_aboutNetError_invalid_cert_noUserFix.js b/browser/base/content/test/about/browser_aboutNetError_invalid_cert_noUserFix.js @@ -0,0 +1,101 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +add_setup(async function () { + await SpecialPowers.pushPrefEnv({ + set: [["test.wait300msAfterTabSwitch", true]], + }); +}); + +const BAD_CERT = "https://expired.example.com/"; + +add_task(async function checkNoUserFixCertErrors() { + await setSecurityCertErrorsFeltPrivacyToTrue(); + const tab = await openErrorPage(BAD_CERT); + const browser = tab.linkedBrowser; + + await SpecialPowers.spawn(browser, [], async function () { + const noUserFixErrors = [ + "SEC_ERROR_UNKNOWN_CRITICAL_EXTENSION", + "MOZILLA_PKIX_ERROR_INVALID_INTEGER_ENCODING", + "MOZILLA_PKIX_ERROR_ISSUER_NO_LONGER_TRUSTED", + "MOZILLA_PKIX_ERROR_KEY_PINNING_FAILURE", + "MOZILLA_PKIX_ERROR_SIGNATURE_ALGORITHM_MISMATCH", + "SEC_ERROR_BAD_DER", + "SEC_ERROR_BAD_SIGNATURE", + "SEC_ERROR_CERT_NOT_IN_NAME_SPACE", + "SEC_ERROR_EXTENSION_VALUE_INVALID", + "SEC_ERROR_INADEQUATE_CERT_TYPE", + "SEC_ERROR_INADEQUATE_KEY_USAGE", + "SEC_ERROR_INVALID_KEY", + "SEC_ERROR_PATH_LEN_CONSTRAINT_INVALID", + "SEC_ERROR_UNSUPPORTED_EC_POINT_FORM", + "SEC_ERROR_UNSUPPORTED_ELLIPTIC_CURVE", + "SEC_ERROR_UNSUPPORTED_KEYALG", + "SEC_ERROR_UNTRUSTED_CERT", + ]; + + content.document.getFailedCertSecurityInfo = () => ({ + errorCodeString: "", + }); + + const netErrorCard = + content.document.querySelector("net-error-card").wrappedJSObject; + + for (const errorCode of noUserFixErrors) { + const mockErrorInfo = { + errorCodeString: errorCode, + errorIsOverridable: false, + }; + const info = Cu.cloneInto(mockErrorInfo, netErrorCard); + netErrorCard.errorInfo = info; + netErrorCard.advancedShowing = false; + netErrorCard.hideExceptionButton = netErrorCard.shouldHideExceptionButton( + info.errorCodeString + ); + netErrorCard.showCustomNetErrorCard = false; + netErrorCard.requestUpdate(); + await netErrorCard.getUpdateComplete(); + + const advancedButton = netErrorCard.advancedButton; + advancedButton.scrollIntoView(true); + EventUtils.synthesizeMouseAtCenter(advancedButton, {}, content); + + await ContentTaskUtils.waitForCondition( + () => netErrorCard.advancedContainer, + `Advanced section should be rendered for ${errorCode}.` + ); + await ContentTaskUtils.waitForCondition( + () => netErrorCard.whyDangerous, + `The 'Why Dangerous' copy should be rendered for ${errorCode}.` + ); + const l10nId = netErrorCard.getNSSErrorWhyDangerousL10nId( + netErrorCard.whyDangerous.dataset.l10nId + ); + + Assert.ok( + netErrorCard.advancedShowing, + `Advanced details are shown for ${errorCode}.` + ); + Assert.ok( + !netErrorCard.exceptionButton, + `Proceed button should not be shown for ${errorCode}.` + ); + Assert.notEqual( + netErrorCard.whyDangerous.innerHTML.trim(), + "", + `Advanced string exists for ${errorCode}.` + ); + Assert.equal( + netErrorCard.whyDangerous.dataset.l10nId, + l10nId, + `Using the correct copy for ${errorCode}.` + ); + } + }); + + BrowserTestUtils.removeTab(tab); + await SpecialPowers.popPrefEnv(); +}); diff --git a/dom/base/Document.cpp b/dom/base/Document.cpp @@ -1888,29 +1888,25 @@ void Document::GetFailedCertSecurityInfo(FailedCertSecurityInfo& aInfo, } rv = cert->GetValidity(getter_AddRefs(validity)); - if (NS_WARN_IF(NS_FAILED(rv))) { - aRv.Throw(rv); - return; - } - if (NS_WARN_IF(!validity)) { - aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); - return; - } - - PRTime validityResult; - rv = validity->GetNotBefore(&validityResult); - if (NS_WARN_IF(NS_FAILED(rv))) { - aRv.Throw(rv); - return; - } - aInfo.mValidNotBefore = DOMTimeStamp(validityResult / PR_USEC_PER_MSEC); + if (NS_WARN_IF(NS_FAILED(rv)) || NS_WARN_IF(!validity)) { + aInfo.mValidNotBefore = 0; + aInfo.mValidNotAfter = 0; + } else { + PRTime validityResult; + rv = validity->GetNotBefore(&validityResult); + if (NS_WARN_IF(NS_FAILED(rv))) { + aInfo.mValidNotBefore = 0; + } else { + aInfo.mValidNotBefore = DOMTimeStamp(validityResult / PR_USEC_PER_MSEC); + } - rv = validity->GetNotAfter(&validityResult); - if (NS_WARN_IF(NS_FAILED(rv))) { - aRv.Throw(rv); - return; + rv = validity->GetNotAfter(&validityResult); + if (NS_WARN_IF(NS_FAILED(rv))) { + aInfo.mValidNotAfter = 0; + } else { + aInfo.mValidNotAfter = DOMTimeStamp(validityResult / PR_USEC_PER_MSEC); + } } - aInfo.mValidNotAfter = DOMTimeStamp(validityResult / PR_USEC_PER_MSEC); nsAutoString issuerCommonName; nsAutoString certChainPEMString; @@ -1938,25 +1934,18 @@ void Document::GetFailedCertSecurityInfo(FailedCertSecurityInfo& aInfo, } rv = certificate->GetValidity(getter_AddRefs(validity)); - if (NS_WARN_IF(NS_FAILED(rv))) { - aRv.Throw(rv); - return; - } - if (NS_WARN_IF(!validity)) { - aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); - return; - } - - rv = validity->GetNotBefore(&notBefore); - if (NS_WARN_IF(NS_FAILED(rv))) { - aRv.Throw(rv); - return; - } - - rv = validity->GetNotAfter(&notAfter); - if (NS_WARN_IF(NS_FAILED(rv))) { - aRv.Throw(rv); - return; + if (NS_WARN_IF(NS_FAILED(rv)) || NS_WARN_IF(!validity)) { + notBefore = 0; + notAfter = 0; + } else { + rv = validity->GetNotBefore(&notBefore); + if (NS_WARN_IF(NS_FAILED(rv))) { + notBefore = 0; + } + rv = validity->GetNotAfter(&notAfter); + if (NS_WARN_IF(NS_FAILED(rv))) { + notAfter = 0; + } } notBefore = std::max(minValidity, notBefore); diff --git a/toolkit/content/aboutNetError.html b/toolkit/content/aboutNetError.html @@ -20,6 +20,7 @@ <link rel="localization" href="branding/brand.ftl" /> <link rel="localization" href="toolkit/neterror/certError.ftl" /> <link rel="localization" href="toolkit/neterror/netError.ftl" /> + <link rel="localization" href="toolkit/neterror/nsserrors.ftl" /> </head> <body> <div class="container"> diff --git a/toolkit/content/net-error-card.mjs b/toolkit/content/net-error-card.mjs @@ -65,6 +65,26 @@ export class NetErrorCard extends MozLitElement { tryAgainButton: "#tryAgainButton", }; + static NSS_ERRORS = [ + "MOZILLA_PKIX_ERROR_INVALID_INTEGER_ENCODING", + "MOZILLA_PKIX_ERROR_ISSUER_NO_LONGER_TRUSTED", + "MOZILLA_PKIX_ERROR_KEY_PINNING_FAILURE", + "MOZILLA_PKIX_ERROR_SIGNATURE_ALGORITHM_MISMATCH", + "SEC_ERROR_BAD_DER", + "SEC_ERROR_BAD_SIGNATURE", + "SEC_ERROR_CERT_NOT_IN_NAME_SPACE", + "SEC_ERROR_EXTENSION_VALUE_INVALID", + "SEC_ERROR_INADEQUATE_CERT_TYPE", + "SEC_ERROR_INADEQUATE_KEY_USAGE", + "SEC_ERROR_INVALID_KEY", + "SEC_ERROR_PATH_LEN_CONSTRAINT_INVALID", + "SEC_ERROR_UNKNOWN_CRITICAL_EXTENSION", + "SEC_ERROR_UNSUPPORTED_EC_POINT_FORM", + "SEC_ERROR_UNSUPPORTED_ELLIPTIC_CURVE", + "SEC_ERROR_UNSUPPORTED_KEYALG", + "SEC_ERROR_UNTRUSTED_CERT", + ]; + static ERROR_CODES = new Set([ "SEC_ERROR_UNTRUSTED_ISSUER", "SEC_ERROR_REVOKED_CERTIFICATE", @@ -81,6 +101,7 @@ export class NetErrorCard extends MozLitElement { "MOZILLA_PKIX_ERROR_NOT_YET_VALID_CERTIFICATE", "NS_ERROR_BASIC_HTTP_AUTH_DISABLED", "NS_ERROR_NET_EMPTY_RESPONSE", + ...NetErrorCard.NSS_ERRORS, ]); static CUSTOM_ERROR_CODES = { @@ -251,13 +272,30 @@ export class NetErrorCard extends MozLitElement { introContentTemplate() { switch (this.errorInfo.errorCodeString) { - case "SEC_ERROR_UNTRUSTED_ISSUER": + case "MOZILLA_PKIX_ERROR_INVALID_INTEGER_ENCODING": + case "MOZILLA_PKIX_ERROR_ISSUER_NO_LONGER_TRUSTED": + case "MOZILLA_PKIX_ERROR_KEY_PINNING_FAILURE": + case "MOZILLA_PKIX_ERROR_NOT_YET_VALID_CERTIFICATE": + case "MOZILLA_PKIX_ERROR_SELF_SIGNED_CERT": + case "MOZILLA_PKIX_ERROR_SIGNATURE_ALGORITHM_MISMATCH": + case "SEC_ERROR_BAD_DER": + case "SEC_ERROR_BAD_SIGNATURE": + case "SEC_ERROR_CERT_NOT_IN_NAME_SPACE": + case "SEC_ERROR_EXPIRED_CERTIFICATE": + case "SEC_ERROR_EXTENSION_VALUE_INVALID": + case "SEC_ERROR_INADEQUATE_CERT_TYPE": + case "SEC_ERROR_INADEQUATE_KEY_USAGE": + case "SEC_ERROR_INVALID_KEY": + case "SEC_ERROR_PATH_LEN_CONSTRAINT_INVALID": case "SEC_ERROR_REVOKED_CERTIFICATE": + case "SEC_ERROR_UNKNOWN_CRITICAL_EXTENSION": case "SEC_ERROR_UNKNOWN_ISSUER": + case "SEC_ERROR_UNSUPPORTED_EC_POINT_FORM": + case "SEC_ERROR_UNSUPPORTED_ELLIPTIC_CURVE": + case "SEC_ERROR_UNSUPPORTED_KEYALG": + case "SEC_ERROR_UNTRUSTED_CERT": + case "SEC_ERROR_UNTRUSTED_ISSUER": case "SSL_ERROR_BAD_CERT_DOMAIN": - case "SEC_ERROR_EXPIRED_CERTIFICATE": - case "MOZILLA_PKIX_ERROR_SELF_SIGNED_CERT": - case "MOZILLA_PKIX_ERROR_NOT_YET_VALID_CERTIFICATE": return html`<p id="certErrorIntro" data-l10n-id="fp-certerror-intro" @@ -498,6 +536,31 @@ export class NetErrorCard extends MozLitElement { }); break; } + case "MOZILLA_PKIX_ERROR_INVALID_INTEGER_ENCODING": + case "MOZILLA_PKIX_ERROR_ISSUER_NO_LONGER_TRUSTED": + case "MOZILLA_PKIX_ERROR_KEY_PINNING_FAILURE": + case "MOZILLA_PKIX_ERROR_SIGNATURE_ALGORITHM_MISMATCH": + case "SEC_ERROR_BAD_DER": + case "SEC_ERROR_BAD_SIGNATURE": + case "SEC_ERROR_CERT_NOT_IN_NAME_SPACE": + case "SEC_ERROR_EXTENSION_VALUE_INVALID": + case "SEC_ERROR_INADEQUATE_CERT_TYPE": + case "SEC_ERROR_INADEQUATE_KEY_USAGE": + case "SEC_ERROR_INVALID_KEY": + case "SEC_ERROR_PATH_LEN_CONSTRAINT_INVALID": + case "SEC_ERROR_UNKNOWN_CRITICAL_EXTENSION": + case "SEC_ERROR_UNSUPPORTED_EC_POINT_FORM": + case "SEC_ERROR_UNSUPPORTED_ELLIPTIC_CURVE": + case "SEC_ERROR_UNSUPPORTED_KEYALG": + case "SEC_ERROR_UNTRUSTED_CERT": { + content = this.advancedSectionTemplate({ + titleL10nId: "fp-certerror-body-title", + whyDangerousL10nId: this.getNSSErrorWhyDangerousL10nId( + this.errorInfo.errorCodeString + ), + }); + break; + } } return html`<div class="advanced-container"> @@ -506,6 +569,10 @@ export class NetErrorCard extends MozLitElement { </div>`; } + getNSSErrorWhyDangerousL10nId(errorString) { + return errorString.toLowerCase().replace(/_/g, "-"); + } + advancedSectionTemplate(params) { let { whyDangerousL10nId, diff --git a/toolkit/locales/en-US/toolkit/neterror/certError.ftl b/toolkit/locales/en-US/toolkit/neterror/certError.ftl @@ -257,3 +257,8 @@ fp-certerror-pkix-not-yet-valid-why-dangerous-body = { -brand-short-name } doesn # Variables: # $date (Date) - Device's clock date. fp-certerror-pkix-not-yet-valid-what-can-you-do-body = Your device’s clock is set to { DATETIME($date, timeStyle: "short") } { 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. + +# 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. +fp-certerror-invalid-cert-why-dangerous = The owner of { $hostname } hasn’t set it up properly and a secure connection can’t be created.