Source code
Revision control
Copy as Markdown
Other Tools
/* 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,
/* eslint-env mozilla/remote-page */
import {
parse,
pemToDER,
} from "chrome://global/content/certviewer/certDecoder.mjs";
// The following parameters are parsed from the error URL:
// e - the error code
// s - custom CSS class to allow alternate styling/favicons
// d - error description
// captive - "true" to indicate we're behind a captive portal.
// Any other value is ignored.
// Note that this file uses document.documentURI to get
// the URL (with the format from above). This is because
// document.location.href gets the current URI off the docshell,
// which is the URL displayed in the location bar, i.e.
// the URI that the user attempted to load.
export let searchParams = new URLSearchParams(
document.documentURI.split("?")[1]
);
export let gErrorCode = searchParams.get("e");
export let gIsCertError = gErrorCode == "nssBadCert";
export let gHasSts = gIsCertError && getCSSClass() === "badStsCert";
const HOST_NAME = getHostName();
export function getCSSClass() {
return searchParams.get("s");
}
export function getHostName() {
try {
return new URL(RPMGetInnerMostURI(document.location.href)).hostname;
} catch (error) {
console.error("Could not parse URL", error);
}
return "";
}
export async function getFailedCertificatesAsPEMString() {
let locationUrl = document.location.href;
let failedCertInfo = document.getFailedCertSecurityInfo();
let errorMessage = failedCertInfo.errorMessage;
let hasHSTS = failedCertInfo.hasHSTS.toString();
let hasHPKP = failedCertInfo.hasHPKP.toString();
let [hstsLabel, hpkpLabel, failedChainLabel] =
await document.l10n.formatValues([
{ id: "cert-error-details-hsts-label", args: { hasHSTS } },
{ id: "cert-error-details-key-pinning-label", args: { hasHPKP } },
{ id: "cert-error-details-cert-chain-label" },
]);
let certStrings = failedCertInfo.certChainStrings;
let failedChainCertificates = "";
for (let der64 of certStrings) {
let wrapped = der64.replace(/(\S{64}(?!$))/g, "$1\r\n");
failedChainCertificates +=
"-----BEGIN CERTIFICATE-----\r\n" +
wrapped +
"\r\n-----END CERTIFICATE-----\r\n";
}
let details =
locationUrl +
"\r\n\r\n" +
errorMessage +
"\r\n\r\n" +
hstsLabel +
"\r\n" +
hpkpLabel +
"\r\n\r\n" +
failedChainLabel +
"\r\n\r\n" +
failedChainCertificates;
return details;
}
export async function getSubjectAltNames(failedCertInfo) {
const serverCertBase64 = failedCertInfo.certChainStrings[0];
const parsed = await parse(pemToDER(serverCertBase64));
const subjectAltNamesExtension = parsed.ext.san;
const subjectAltNames = [];
if (subjectAltNamesExtension) {
for (let [key, value] of subjectAltNamesExtension.altNames) {
if (key === "DNS Name" && value.length) {
subjectAltNames.push(value);
}
}
}
return subjectAltNames;
}
export async function recordSecurityUITelemetry(category, name, errorInfo) {
// Truncate the error code to avoid going over the allowed
// string size limit for telemetry events.
let errorCode = errorInfo.errorCodeString.substring(0, 40);
let extraKeys = {
value: errorCode,
is_frame: window.parent != window,
};
if (category == "securityUiCerterror") {
extraKeys.has_sts = gHasSts;
}
if (name.startsWith("load")) {
extraKeys.channel_status = errorInfo.channelStatus;
}
if (category == "securityUiCerterror" && name.startsWith("load")) {
extraKeys.issued_by_cca = false;
extraKeys.hyphen_compat = false;
// This issue only applies to certificate domain name mismatch errors where
// the first label in the domain name starts or ends with a hyphen.
let label = HOST_NAME.substring(0, HOST_NAME.indexOf("."));
if (
errorCode == "SSL_ERROR_BAD_CERT_DOMAIN" &&
(label.startsWith("-") || label.endsWith("-"))
) {
try {
let subjectAltNames = await getSubjectAltNames(errorInfo);
for (let subjectAltName of subjectAltNames) {
// If the certificate has a wildcard entry that matches the domain
// name (e.g. '*.example.com' matches 'foo-.example.com'), then
// this error is probably due to Firefox disallowing hyphens in
// domain names when matching wildcard entries.
if (
subjectAltName.startsWith("*.") &&
subjectAltName.substring(1) == HOST_NAME.substring(label.length)
) {
extraKeys.hyphen_compat = true;
break;
}
}
} catch (e) {
console.error("error parsing certificate:", e);
}
}
let issuer = errorInfo.certChainStrings.at(-1);
if (issuer && errorCode == "SEC_ERROR_UNKNOWN_ISSUER") {
try {
let parsed = await parse(pemToDER(issuer));
extraKeys.issued_by_cca =
parsed.issuer.dn == "c=IN, o=India PKI, cn=CCA India 2022 SPL" ||
parsed.issuer.dn == "c=IN, o=India PKI, cn=CCA India 2015 SPL";
} catch (e) {
console.error("error parsing issuer certificate:", e);
}
}
}
RPMRecordGleanEvent(category, name, extraKeys);
}