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:
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(¬Before);
- if (NS_WARN_IF(NS_FAILED(rv))) {
- aRv.Throw(rv);
- return;
- }
-
- rv = validity->GetNotAfter(¬After);
- 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(¬Before);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ notBefore = 0;
+ }
+ rv = validity->GetNotAfter(¬After);
+ 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.