diff options
Diffstat (limited to '')
-rw-r--r-- | browser/base/content/pageinfo/security.js | 426 |
1 files changed, 426 insertions, 0 deletions
diff --git a/browser/base/content/pageinfo/security.js b/browser/base/content/pageinfo/security.js new file mode 100644 index 0000000000..e4d52f889f --- /dev/null +++ b/browser/base/content/pageinfo/security.js @@ -0,0 +1,426 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* 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/. */ + +const { SiteDataManager } = ChromeUtils.import( + "resource:///modules/SiteDataManager.jsm" +); +const { DownloadUtils } = ChromeUtils.importESModule( + "resource://gre/modules/DownloadUtils.sys.mjs" +); + +/* import-globals-from pageInfo.js */ + +ChromeUtils.defineESModuleGetters(this, { + LoginHelper: "resource://gre/modules/LoginHelper.sys.mjs", + PluralForm: "resource://gre/modules/PluralForm.sys.mjs", +}); + +var security = { + async init(uri, windowInfo) { + this.uri = uri; + this.windowInfo = windowInfo; + this.securityInfo = await this._getSecurityInfo(); + }, + + viewCert() { + let certChain = this.securityInfo.certChain; + let certs = certChain.map(elem => + encodeURIComponent(elem.getBase64DERString()) + ); + let certsStringURL = certs.map(elem => `cert=${elem}`); + certsStringURL = certsStringURL.join("&"); + let url = `about:certificate?${certsStringURL}`; + let win = BrowserWindowTracker.getTopWindow(); + win.switchToTabHavingURI(url, true, {}); + }, + + async _getSecurityInfo() { + // We don't have separate info for a frame, return null until further notice + // (see bug 138479) + if (!this.windowInfo.isTopWindow) { + return null; + } + + var ui = security._getSecurityUI(); + if (!ui) { + return null; + } + + var isBroken = ui.state & Ci.nsIWebProgressListener.STATE_IS_BROKEN; + var isMixed = + ui.state & + (Ci.nsIWebProgressListener.STATE_LOADED_MIXED_ACTIVE_CONTENT | + Ci.nsIWebProgressListener.STATE_LOADED_MIXED_DISPLAY_CONTENT); + var isEV = ui.state & Ci.nsIWebProgressListener.STATE_IDENTITY_EV_TOPLEVEL; + + let retval = { + cAName: "", + encryptionAlgorithm: "", + encryptionStrength: 0, + version: "", + isBroken, + isMixed, + isEV, + cert: null, + certificateTransparency: null, + }; + + // Only show certificate info for secure contexts. This prevents us from + // showing certificate data for http origins when using a proxy. + // https://searchfox.org/mozilla-central/rev/9c72508fcf2bba709a5b5b9eae9da35e0c707baa/security/manager/ssl/nsSecureBrowserUI.cpp#62-64 + if (!ui.isSecureContext) { + return retval; + } + + let secInfo = ui.secInfo; + if (!secInfo) { + return retval; + } + + let cert = secInfo.serverCert; + let issuerName = null; + if (cert) { + issuerName = cert.issuerOrganization || cert.issuerName; + } + + let certChainArray = []; + if (secInfo.succeededCertChain.length) { + certChainArray = secInfo.succeededCertChain; + } else { + certChainArray = secInfo.failedCertChain; + } + + retval = { + cAName: issuerName, + encryptionAlgorithm: undefined, + encryptionStrength: undefined, + version: undefined, + isBroken, + isMixed, + isEV, + cert, + certChain: certChainArray, + certificateTransparency: undefined, + }; + + var version; + try { + retval.encryptionAlgorithm = secInfo.cipherName; + retval.encryptionStrength = secInfo.secretKeyLength; + version = secInfo.protocolVersion; + } catch (e) {} + + switch (version) { + case Ci.nsITransportSecurityInfo.SSL_VERSION_3: + retval.version = "SSL 3"; + break; + case Ci.nsITransportSecurityInfo.TLS_VERSION_1: + retval.version = "TLS 1.0"; + break; + case Ci.nsITransportSecurityInfo.TLS_VERSION_1_1: + retval.version = "TLS 1.1"; + break; + case Ci.nsITransportSecurityInfo.TLS_VERSION_1_2: + retval.version = "TLS 1.2"; + break; + case Ci.nsITransportSecurityInfo.TLS_VERSION_1_3: + retval.version = "TLS 1.3"; + break; + } + + // Select the status text to display for Certificate Transparency. + // Since we do not yet enforce the CT Policy on secure connections, + // we must not complain on policy discompliance (it might be viewed + // as a security issue by the user). + switch (secInfo.certificateTransparencyStatus) { + case Ci.nsITransportSecurityInfo.CERTIFICATE_TRANSPARENCY_NOT_APPLICABLE: + case Ci.nsITransportSecurityInfo + .CERTIFICATE_TRANSPARENCY_POLICY_NOT_ENOUGH_SCTS: + case Ci.nsITransportSecurityInfo + .CERTIFICATE_TRANSPARENCY_POLICY_NOT_DIVERSE_SCTS: + retval.certificateTransparency = null; + break; + case Ci.nsITransportSecurityInfo + .CERTIFICATE_TRANSPARENCY_POLICY_COMPLIANT: + retval.certificateTransparency = "Compliant"; + break; + } + + return retval; + }, + + // Find the secureBrowserUI object (if present) + _getSecurityUI() { + if (window.opener.gBrowser) { + return window.opener.gBrowser.securityUI; + } + return null; + }, + + async _updateSiteDataInfo() { + // Save site data info for deleting. + this.siteData = await SiteDataManager.getSite(this.uri.host); + + let clearSiteDataButton = document.getElementById( + "security-clear-sitedata" + ); + let siteDataLabel = document.getElementById( + "security-privacy-sitedata-value" + ); + + if (!this.siteData) { + document.l10n.setAttributes(siteDataLabel, "security-site-data-no"); + clearSiteDataButton.setAttribute("disabled", "true"); + return; + } + + let { usage } = this.siteData; + if (usage > 0) { + let size = DownloadUtils.convertByteUnits(usage); + if (this.siteData.cookies.length) { + document.l10n.setAttributes( + siteDataLabel, + "security-site-data-cookies", + { value: size[0], unit: size[1] } + ); + } else { + document.l10n.setAttributes(siteDataLabel, "security-site-data-only", { + value: size[0], + unit: size[1], + }); + } + } else { + // We're storing cookies, else getSite would have returned null. + document.l10n.setAttributes( + siteDataLabel, + "security-site-data-cookies-only" + ); + } + + clearSiteDataButton.removeAttribute("disabled"); + }, + + /** + * Clear Site Data and Cookies + */ + clearSiteData() { + if (this.siteData) { + let { baseDomain } = this.siteData; + if (SiteDataManager.promptSiteDataRemoval(window, [baseDomain])) { + SiteDataManager.remove(baseDomain).then(() => + this._updateSiteDataInfo() + ); + } + } + }, + + /** + * Open the login manager window + */ + viewPasswords() { + LoginHelper.openPasswordManager(window, { + filterString: this.windowInfo.hostName, + entryPoint: "pageinfo", + }); + }, +}; + +async function securityOnLoad(uri, windowInfo) { + await security.init(uri, windowInfo); + + let info = security.securityInfo; + if ( + !info || + (uri.scheme === "about" && !uri.spec.startsWith("about:certerror")) + ) { + document.getElementById("securityTab").hidden = true; + return; + } + document.getElementById("securityTab").hidden = false; + + /* Set Identity section text */ + setText("security-identity-domain-value", windowInfo.hostName); + + var validity; + if (info.cert && !info.isBroken) { + validity = info.cert.validity.notAfterLocalDay; + + // Try to pull out meaningful values. Technically these fields are optional + // so we'll employ fallbacks where appropriate. The EV spec states that Org + // fields must be specified for subject and issuer so that case is simpler. + if (info.isEV) { + setText("security-identity-owner-value", info.cert.organization); + setText("security-identity-verifier-value", info.cAName); + } else { + // Technically, a non-EV cert might specify an owner in the O field or not, + // depending on the CA's issuing policies. However we don't have any programmatic + // way to tell those apart, and no policy way to establish which organization + // vetting standards are good enough (that's what EV is for) so we default to + // treating these certs as domain-validated only. + document.l10n.setAttributes( + document.getElementById("security-identity-owner-value"), + "page-info-security-no-owner" + ); + setText( + "security-identity-verifier-value", + info.cAName || info.cert.issuerCommonName || info.cert.issuerName + ); + } + } else { + // We don't have valid identity credentials. + document.l10n.setAttributes( + document.getElementById("security-identity-owner-value"), + "page-info-security-no-owner" + ); + document.l10n.setAttributes( + document.getElementById("security-identity-verifier-value"), + "page-info-not-specified" + ); + } + + if (validity) { + setText("security-identity-validity-value", validity); + } else { + document.getElementById("security-identity-validity-row").hidden = true; + } + + /* Manage the View Cert button*/ + var viewCert = document.getElementById("security-view-cert"); + if (info.cert) { + viewCert.collapsed = false; + } else { + viewCert.collapsed = true; + } + + /* Set Privacy & History section text */ + + // Only show quota usage data for websites, not internal sites. + if (uri.scheme == "http" || uri.scheme == "https") { + SiteDataManager.updateSites().then(() => security._updateSiteDataInfo()); + } else { + document.getElementById("security-privacy-sitedata-row").hidden = true; + } + + if (realmHasPasswords(uri)) { + document.l10n.setAttributes( + document.getElementById("security-privacy-passwords-value"), + "saved-passwords-yes" + ); + } else { + document.l10n.setAttributes( + document.getElementById("security-privacy-passwords-value"), + "saved-passwords-no" + ); + } + + document.l10n.setAttributes( + document.getElementById("security-privacy-history-value"), + "security-visits-number", + { visits: previousVisitCount(windowInfo.hostName) } + ); + + /* Set the Technical Detail section messages */ + const pkiBundle = document.getElementById("pkiBundle"); + var hdr; + var msg1; + var msg2; + + if (info.isBroken) { + if (info.isMixed) { + hdr = pkiBundle.getString("pageInfo_MixedContent"); + msg1 = pkiBundle.getString("pageInfo_MixedContent2"); + } else { + hdr = pkiBundle.getFormattedString("pageInfo_BrokenEncryption", [ + info.encryptionAlgorithm, + info.encryptionStrength + "", + info.version, + ]); + msg1 = pkiBundle.getString("pageInfo_WeakCipher"); + } + msg2 = pkiBundle.getString("pageInfo_Privacy_None2"); + } else if (info.encryptionStrength > 0) { + hdr = pkiBundle.getFormattedString( + "pageInfo_EncryptionWithBitsAndProtocol", + [info.encryptionAlgorithm, info.encryptionStrength + "", info.version] + ); + msg1 = pkiBundle.getString("pageInfo_Privacy_Encrypted1"); + msg2 = pkiBundle.getString("pageInfo_Privacy_Encrypted2"); + } else { + hdr = pkiBundle.getString("pageInfo_NoEncryption"); + if (windowInfo.hostName != null) { + msg1 = pkiBundle.getFormattedString("pageInfo_Privacy_None1", [ + windowInfo.hostName, + ]); + } else { + msg1 = pkiBundle.getString("pageInfo_Privacy_None4"); + } + msg2 = pkiBundle.getString("pageInfo_Privacy_None2"); + } + setText("security-technical-shortform", hdr); + setText("security-technical-longform1", msg1); + setText("security-technical-longform2", msg2); + + const ctStatus = document.getElementById( + "security-technical-certificate-transparency" + ); + if (info.certificateTransparency) { + ctStatus.hidden = false; + ctStatus.value = pkiBundle.getString( + "pageInfo_CertificateTransparency_" + info.certificateTransparency + ); + } else { + ctStatus.hidden = true; + } +} + +function setText(id, value) { + var element = document.getElementById(id); + if (!element) { + return; + } + if (element.localName == "input" || element.localName == "label") { + element.value = value; + } else { + element.textContent = value; + } +} + +/** + * Return true iff realm (proto://host:port) (extracted from uri) has + * saved passwords + */ +function realmHasPasswords(uri) { + return Services.logins.countLogins(uri.prePath, "", "") > 0; +} + +/** + * Return the number of previous visits recorded for host before today. + * + * @param host - the domain name to look for in history + */ +function previousVisitCount(host, endTimeReference) { + if (!host) { + return 0; + } + + var historyService = Cc[ + "@mozilla.org/browser/nav-history-service;1" + ].getService(Ci.nsINavHistoryService); + + var options = historyService.getNewQueryOptions(); + options.resultType = options.RESULTS_AS_VISIT; + + // Search for visits to this host before today + var query = historyService.getNewQuery(); + query.endTimeReference = query.TIME_RELATIVE_TODAY; + query.endTime = 0; + query.domain = host; + + var result = historyService.executeQuery(query, options); + result.root.containerOpen = true; + var cc = result.root.childCount; + result.root.containerOpen = false; + return cc; +} |