gecko-dev/toolkit/modules/addons/SecurityInfo.jsm
Kris Maglione e930b89c34 Bug 1514594: Part 3 - Change ChromeUtils.import API.
***
Bug 1514594: Part 3a - Change ChromeUtils.import to return an exports object; not pollute global. r=mccr8

This changes the behavior of ChromeUtils.import() to return an exports object,
rather than a module global, in all cases except when `null` is passed as a
second argument, and changes the default behavior not to pollute the global
scope with the module's exports. Thus, the following code written for the old
model:

  ChromeUtils.import("resource://gre/modules/Services.jsm");

is approximately the same as the following, in the new model:

  var {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm");

Since the two behaviors are mutually incompatible, this patch will land with a
scripted rewrite to update all existing callers to use the new model rather
than the old.
***
Bug 1514594: Part 3b - Mass rewrite all JS code to use the new ChromeUtils.import API. rs=Gijs

This was done using the followng script:

https://bitbucket.org/kmaglione/m-c-rewrites/src/tip/processors/cu-import-exports.jsm
***
Bug 1514594: Part 3c - Update ESLint plugin for ChromeUtils.import API changes. r=Standard8

Differential Revision: https://phabricator.services.mozilla.com/D16747
***
Bug 1514594: Part 3d - Remove/fix hundreds of duplicate imports from sync tests. r=Gijs

Differential Revision: https://phabricator.services.mozilla.com/D16748
***
Bug 1514594: Part 3e - Remove no-op ChromeUtils.import() calls. r=Gijs

Differential Revision: https://phabricator.services.mozilla.com/D16749
***
Bug 1514594: Part 3f.1 - Cleanup various test corner cases after mass rewrite. r=Gijs
***
Bug 1514594: Part 3f.2 - Cleanup various non-test corner cases after mass rewrite. r=Gijs

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

--HG--
extra : rebase_source : 359574ee3064c90f33bf36c2ebe3159a24cc8895
extra : histedit_source : b93c8f42808b1599f9122d7842d2c0b3e656a594%2C64a3a4e3359dc889e2ab2b49461bab9e27fc10a7
2019-01-17 10:18:31 -08:00

300 lines
11 KiB
JavaScript

/* 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/. */
"use strict";
const EXPORTED_SYMBOLS = ["SecurityInfo"];
const {XPCOMUtils} = ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
const wpl = Ci.nsIWebProgressListener;
XPCOMUtils.defineLazyServiceGetter(this, "NSSErrorsService",
"@mozilla.org/nss_errors_service;1",
"nsINSSErrorsService");
XPCOMUtils.defineLazyServiceGetter(this, "sss",
"@mozilla.org/ssservice;1",
"nsISiteSecurityService");
// NOTE: SecurityInfo is largely reworked from the devtools NetworkHelper with changes
// to better support the WebRequest api. The objects returned are formatted specifically
// to pass through as part of a response to webRequest listeners.
const SecurityInfo = {
/**
* Extracts security information from nsIChannel.securityInfo.
*
* @param {nsIChannel} channel
* If null channel is assumed to be insecure.
* @param {Object} options
*
* @returns {Object}
* Returns an object containing following members:
* - state: The security of the connection used to fetch this
* request. Has one of following string values:
* * "insecure": the connection was not secure (only http)
* * "weak": the connection has minor security issues
* * "broken": secure connection failed (e.g. expired cert)
* * "secure": the connection was properly secured.
* If state == broken:
* - errorMessage: full error message from
* nsITransportSecurityInfo.
* If state == secure:
* - protocolVersion: one of TLSv1, TLSv1.1, TLSv1.2, TLSv1.3.
* - cipherSuite: the cipher suite used in this connection.
* - cert: information about certificate used in this connection.
* See parseCertificateInfo for the contents.
* - hsts: true if host uses Strict Transport Security,
* false otherwise
* - hpkp: true if host uses Public Key Pinning, false otherwise
* If state == weak: Same as state == secure and
* - weaknessReasons: list of reasons that cause the request to be
* considered weak. See getReasonsForWeakness.
*/
getSecurityInfo(channel, options = {}) {
const info = {
state: "insecure",
};
/**
* Different scenarios to consider here and how they are handled:
* - request is HTTP, the connection is not secure
* => securityInfo is null
* => state === "insecure"
*
* - request is HTTPS, the connection is secure
* => .securityState has STATE_IS_SECURE flag
* => state === "secure"
*
* - request is HTTPS, the connection has security issues
* => .securityState has STATE_IS_INSECURE flag
* => .errorCode is an NSS error code.
* => state === "broken"
*
* - request is HTTPS, the connection was terminated before the security
* could be validated
* => .securityState has STATE_IS_INSECURE flag
* => .errorCode is NOT an NSS error code.
* => .errorMessage is not available.
* => state === "insecure"
*
* - request is HTTPS but it uses a weak cipher or old protocol, see
* https://hg.mozilla.org/mozilla-central/annotate/def6ed9d1c1a/
* security/manager/ssl/nsNSSCallbacks.cpp#l1233
* - request is mixed content (which makes no sense whatsoever)
* => .securityState has STATE_IS_BROKEN flag
* => .errorCode is NOT an NSS error code
* => .errorMessage is not available
* => state === "weak"
*/
let securityInfo = channel.securityInfo;
if (!securityInfo) {
return info;
}
securityInfo.QueryInterface(Ci.nsITransportSecurityInfo);
if (NSSErrorsService.isNSSErrorCode(securityInfo.errorCode)) {
// The connection failed.
info.state = "broken";
info.errorMessage = securityInfo.errorMessage;
if (options.certificateChain && securityInfo.failedCertChain) {
info.certificates = this.getCertificateChain(
securityInfo.failedCertChain, options);
}
return info;
}
const state = securityInfo.securityState;
let uri = channel.URI;
if (uri && !uri.schemeIs("https") && !uri.schemeIs("wss")) {
// it is not enough to look at the transport security info -
// schemes other than https and wss are subject to
// downgrade/etc at the scheme level and should always be
// considered insecure.
// Leave info.state = "insecure";
} else if (state & wpl.STATE_IS_SECURE) {
// The connection is secure if the scheme is sufficient
info.state = "secure";
} else if (state & wpl.STATE_IS_BROKEN) {
// The connection is not secure, there was no error but there's some
// minor security issues.
info.state = "weak";
info.weaknessReasons = this.getReasonsForWeakness(state);
} else if (state & wpl.STATE_IS_INSECURE) {
// This was most likely an https request that was aborted before
// validation. Return info as info.state = insecure.
return info;
} else {
// No known STATE_IS_* flags.
return info;
}
// Cipher suite.
info.cipherSuite = securityInfo.cipherName;
// Key exchange group name.
if (securityInfo.keaGroupName !== "none") {
info.keaGroupName = securityInfo.keaGroupName;
}
// Certificate signature scheme.
if (securityInfo.signatureSchemeName !== "none") {
info.signatureSchemeName = securityInfo.signatureSchemeName;
}
info.isDomainMismatch = securityInfo.isDomainMismatch;
info.isExtendedValidation = securityInfo.isExtendedValidation;
info.isNotValidAtThisTime = securityInfo.isNotValidAtThisTime;
info.isUntrusted = securityInfo.isUntrusted;
info.certificateTransparencyStatus = this.getTransparencyStatus(
securityInfo.certificateTransparencyStatus);
// Protocol version.
info.protocolVersion = this.formatSecurityProtocol(securityInfo.protocolVersion);
if (options.certificateChain && securityInfo.succeededCertChain) {
info.certificates = this.getCertificateChain(securityInfo.succeededCertChain, options);
} else {
info.certificates = [this.parseCertificateInfo(securityInfo.serverCert, options)];
}
// HSTS and HPKP if available.
if (uri && uri.host) {
// SiteSecurityService uses different storage if the channel is
// private. Thus we must give isSecureURI correct flags or we
// might get incorrect results.
let flags = 0;
if (channel instanceof Ci.nsIPrivateBrowsingChannel && channel.isChannelPrivate) {
flags = Ci.nsISocketProvider.NO_PERMANENT_STORAGE;
}
info.hsts = sss.isSecureURI(sss.HEADER_HSTS, uri, flags);
info.hpkp = sss.isSecureURI(sss.HEADER_HPKP, uri, flags);
} else {
info.hsts = false;
info.hpkp = false;
}
return info;
},
getCertificateChain(certChain, options = {}) {
let certificates = [];
for (let cert of certChain.getEnumerator()) {
certificates.push(this.parseCertificateInfo(cert, options));
}
return certificates;
},
/**
* Takes an nsIX509Cert and returns an object with certificate information.
*
* @param {nsIX509Cert} cert
* The certificate to extract the information from.
* @param {Object} options
* @returns {Object}
* An object with following format:
* {
* subject: subjectName,
* issuer: issuerName,
* validity: { start, end },
* fingerprint: { sha1, sha256 }
* }
*/
parseCertificateInfo(cert, options = {}) {
if (!cert) {
return {};
}
let certData = {
subject: cert.subjectName,
issuer: cert.issuerName,
validity: {
start: cert.validity.notBefore ? Math.trunc(cert.validity.notBefore / 1000) : 0,
end: cert.validity.notAfter ? Math.trunc(cert.validity.notAfter / 1000) : 0,
},
fingerprint: {
sha1: cert.sha1Fingerprint,
sha256: cert.sha256Fingerprint,
},
serialNumber: cert.serialNumber,
isBuiltInRoot: cert.isBuiltInRoot,
subjectPublicKeyInfoDigest: {
sha256: cert.sha256SubjectPublicKeyInfoDigest,
},
};
if (options.rawDER) {
certData.rawDER = cert.getRawDER({});
}
return certData;
},
// Bug 1355903 Transparency is currently disabled using security.pki.certificate_transparency.mode
getTransparencyStatus(status) {
switch (status) {
case Ci.nsITransportSecurityInfo.CERTIFICATE_TRANSPARENCY_NOT_APPLICABLE:
return "not_applicable";
case Ci.nsITransportSecurityInfo.CERTIFICATE_TRANSPARENCY_POLICY_COMPLIANT:
return "policy_compliant";
case Ci.nsITransportSecurityInfo.CERTIFICATE_TRANSPARENCY_POLICY_NOT_ENOUGH_SCTS:
return "policy_not_enough_scts";
case Ci.nsITransportSecurityInfo.CERTIFICATE_TRANSPARENCY_POLICY_NOT_DIVERSE_SCTS:
return "policy_not_diverse_scts";
}
return "unknown";
},
/**
* Takes protocolVersion of TransportSecurityInfo object and returns human readable
* description.
*
* @param {number} version
* One of nsITransportSecurityInfo version constants.
* @returns {string}
* One of TLSv1, TLSv1.1, TLSv1.2, TLSv1.3 if version
* is valid, Unknown otherwise.
*/
formatSecurityProtocol(version) {
switch (version) {
case Ci.nsITransportSecurityInfo.TLS_VERSION_1:
return "TLSv1";
case Ci.nsITransportSecurityInfo.TLS_VERSION_1_1:
return "TLSv1.1";
case Ci.nsITransportSecurityInfo.TLS_VERSION_1_2:
return "TLSv1.2";
case Ci.nsITransportSecurityInfo.TLS_VERSION_1_3:
return "TLSv1.3";
}
return "unknown";
},
/**
* Takes the securityState bitfield and returns reasons for weak connection
* as an array of strings.
*
* @param {number} state
* nsITransportSecurityInfo.securityState.
*
* @returns {array<string>}
* List of weakness reasons. A subset of { cipher } where
* * cipher: The cipher suite is consireded to be weak (RC4).
*/
getReasonsForWeakness(state) {
// If there's non-fatal security issues the request has STATE_IS_BROKEN
// flag set. See https://hg.mozilla.org/mozilla-central/file/44344099d119
// /security/manager/ssl/nsNSSCallbacks.cpp#l1233
let reasons = [];
if (state & wpl.STATE_IS_BROKEN) {
if (state & wpl.STATE_USES_WEAK_CRYPTO) {
reasons.push("cipher");
}
}
return reasons;
},
};