diff options
Diffstat (limited to 'devtools/client/netmonitor/src/components/request-details/SecurityPanel.js')
-rw-r--r-- | devtools/client/netmonitor/src/components/request-details/SecurityPanel.js | 283 |
1 files changed, 283 insertions, 0 deletions
diff --git a/devtools/client/netmonitor/src/components/request-details/SecurityPanel.js b/devtools/client/netmonitor/src/components/request-details/SecurityPanel.js new file mode 100644 index 0000000000..74dca02cc8 --- /dev/null +++ b/devtools/client/netmonitor/src/components/request-details/SecurityPanel.js @@ -0,0 +1,283 @@ +/* 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 { + Component, + createFactory, +} = require("resource://devtools/client/shared/vendor/react.js"); +const PropTypes = require("resource://devtools/client/shared/vendor/react-prop-types.js"); +const dom = require("resource://devtools/client/shared/vendor/react-dom-factories.js"); +const { + L10N, +} = require("resource://devtools/client/netmonitor/src/utils/l10n.js"); +const { + fetchNetworkUpdatePacket, + getUrlHost, +} = require("resource://devtools/client/netmonitor/src/utils/request-utils.js"); + +// Components +const TreeViewClass = require("resource://devtools/client/shared/components/tree/TreeView.js"); +const PropertiesView = createFactory( + require("resource://devtools/client/netmonitor/src/components/request-details/PropertiesView.js") +); + +loader.lazyGetter(this, "Rep", function () { + return require("resource://devtools/client/shared/components/reps/index.js") + .REPS.Rep; +}); +loader.lazyGetter(this, "MODE", function () { + return require("resource://devtools/client/shared/components/reps/index.js") + .MODE; +}); + +const { div, span } = dom; +const NOT_AVAILABLE = L10N.getStr("netmonitor.security.notAvailable"); +const ERROR_LABEL = L10N.getStr("netmonitor.security.error"); +const CIPHER_SUITE_LABEL = L10N.getStr("netmonitor.security.cipherSuite"); +const WARNING_CIPHER_LABEL = L10N.getStr("netmonitor.security.warning.cipher"); +const ENABLED_LABEL = L10N.getStr("netmonitor.security.enabled"); +const DISABLED_LABEL = L10N.getStr("netmonitor.security.disabled"); +const CONNECTION_LABEL = L10N.getStr("netmonitor.security.connection"); +const PROTOCOL_VERSION_LABEL = L10N.getStr( + "netmonitor.security.protocolVersion" +); +const KEA_GROUP_LABEL = L10N.getStr("netmonitor.security.keaGroup"); +const KEA_GROUP_NONE = L10N.getStr("netmonitor.security.keaGroup.none"); +const KEA_GROUP_CUSTOM = L10N.getStr("netmonitor.security.keaGroup.custom"); +const KEA_GROUP_UNKNOWN = L10N.getStr("netmonitor.security.keaGroup.unknown"); +const SIGNATURE_SCHEME_LABEL = L10N.getStr( + "netmonitor.security.signatureScheme" +); +const SIGNATURE_SCHEME_NONE = L10N.getStr( + "netmonitor.security.signatureScheme.none" +); +const SIGNATURE_SCHEME_UNKNOWN = L10N.getStr( + "netmonitor.security.signatureScheme.unknown" +); +const HSTS_LABEL = L10N.getStr("netmonitor.security.hsts"); +const HPKP_LABEL = L10N.getStr("netmonitor.security.hpkp"); +const CERTIFICATE_LABEL = L10N.getStr("netmonitor.security.certificate"); +const CERTIFICATE_TRANSPARENCY_LABEL = L10N.getStr( + "certmgr.certificateTransparency.label" +); +const CERTIFICATE_TRANSPARENCY_POLICY_COMPLIANT = L10N.getStr( + "certmgr.certificateTransparency.status.ok" +); +const CERTIFICATE_TRANSPARENCY_POLICY_NOT_ENOUGH_SCTS = L10N.getStr( + "certmgr.certificateTransparency.status.notEnoughSCTS" +); +const CERTIFICATE_TRANSPARENCY_POLICY_NOT_DIVERSE_SCTS = L10N.getStr( + "certmgr.certificateTransparency.status.notDiverseSCTS" +); +const SUBJECT_INFO_LABEL = L10N.getStr("certmgr.subjectinfo.label"); +const CERT_DETAIL_COMMON_NAME_LABEL = L10N.getStr("certmgr.certdetail.cn"); +const CERT_DETAIL_ORG_LABEL = L10N.getStr("certmgr.certdetail.o"); +const CERT_DETAIL_ORG_UNIT_LABEL = L10N.getStr("certmgr.certdetail.ou"); +const ISSUER_INFO_LABEL = L10N.getStr("certmgr.issuerinfo.label"); +const PERIOD_OF_VALIDITY_LABEL = L10N.getStr("certmgr.periodofvalidity.label"); +const BEGINS_LABEL = L10N.getStr("certmgr.begins"); +const EXPIRES_LABEL = L10N.getStr("certmgr.expires"); +const FINGERPRINTS_LABEL = L10N.getStr("certmgr.fingerprints.label"); +const SHA256_FINGERPRINT_LABEL = L10N.getStr( + "certmgr.certdetail.sha256fingerprint" +); +const SHA1_FINGERPRINT_LABEL = L10N.getStr( + "certmgr.certdetail.sha1fingerprint" +); + +/* + * Localize special values for key exchange group name, + * certificate signature scheme, and certificate + * transparency status. + */ +const formatSecurityInfo = securityInfo => { + const formattedSecurityInfo = { ...securityInfo }; + + const formatters = { + keaGroupName: value => { + if (value === "none") { + return KEA_GROUP_NONE; + } + if (value === "custom") { + return KEA_GROUP_CUSTOM; + } + if (value === "unknown group") { + return KEA_GROUP_UNKNOWN; + } + return value; + }, + signatureSchemeName: value => { + if (value === "none") { + return SIGNATURE_SCHEME_NONE; + } + if (value === "unknown signature") { + return SIGNATURE_SCHEME_UNKNOWN; + } + return value; + }, + certificateTransparency: value => { + if (value === 5) { + return CERTIFICATE_TRANSPARENCY_POLICY_COMPLIANT; + } + if (value === 6) { + return CERTIFICATE_TRANSPARENCY_POLICY_NOT_ENOUGH_SCTS; + } + if (value === 7) { + return CERTIFICATE_TRANSPARENCY_POLICY_NOT_DIVERSE_SCTS; + } + return value; + }, + }; + + return Object.keys(formatters).reduce((acc, key) => { + const formatter = formatters[key]; + acc[key] = formatter(acc[key]); + return acc; + }, formattedSecurityInfo); +}; + +const getConnectionLabel = securityInfo => ({ + [PROTOCOL_VERSION_LABEL]: securityInfo.protocolVersion || NOT_AVAILABLE, + [CIPHER_SUITE_LABEL]: securityInfo.cipherSuite || NOT_AVAILABLE, + [KEA_GROUP_LABEL]: securityInfo.keaGroupName || NOT_AVAILABLE, + [SIGNATURE_SCHEME_LABEL]: securityInfo.signatureSchemeName || NOT_AVAILABLE, +}); + +const getHostHeaderLabel = securityInfo => ({ + [HSTS_LABEL]: securityInfo.hsts ? ENABLED_LABEL : DISABLED_LABEL, + [HPKP_LABEL]: securityInfo.hpkp ? ENABLED_LABEL : DISABLED_LABEL, +}); + +const getCertificateLabel = securityInfo => { + const { fingerprint, issuer, subject, validity } = securityInfo.cert; + + return { + [SUBJECT_INFO_LABEL]: { + [CERT_DETAIL_COMMON_NAME_LABEL]: subject?.commonName || NOT_AVAILABLE, + [CERT_DETAIL_ORG_LABEL]: subject?.organization || NOT_AVAILABLE, + [CERT_DETAIL_ORG_UNIT_LABEL]: subject?.organizationUnit || NOT_AVAILABLE, + }, + [ISSUER_INFO_LABEL]: { + [CERT_DETAIL_COMMON_NAME_LABEL]: issuer?.commonName || NOT_AVAILABLE, + [CERT_DETAIL_ORG_LABEL]: issuer?.organization || NOT_AVAILABLE, + [CERT_DETAIL_ORG_UNIT_LABEL]: issuer?.organizationUnit || NOT_AVAILABLE, + }, + [PERIOD_OF_VALIDITY_LABEL]: { + [BEGINS_LABEL]: validity?.start || NOT_AVAILABLE, + [EXPIRES_LABEL]: validity?.end || NOT_AVAILABLE, + }, + [FINGERPRINTS_LABEL]: { + [SHA256_FINGERPRINT_LABEL]: fingerprint?.sha256 || NOT_AVAILABLE, + [SHA1_FINGERPRINT_LABEL]: fingerprint?.sha1 || NOT_AVAILABLE, + }, + [CERTIFICATE_TRANSPARENCY_LABEL]: + securityInfo.certificateTransparency || NOT_AVAILABLE, + }; +}; + +const getObject = ({ securityInfo, url }) => { + if (securityInfo.state !== "secure" && securityInfo.state !== "weak") { + return { + [ERROR_LABEL]: securityInfo.errorMessage || NOT_AVAILABLE, + }; + } + + const HOST_HEADER_LABEL = L10N.getFormatStr( + "netmonitor.security.hostHeader", + getUrlHost(url) + ); + const formattedSecurityInfo = formatSecurityInfo(securityInfo); + + return { + [CONNECTION_LABEL]: getConnectionLabel(formattedSecurityInfo), + [HOST_HEADER_LABEL]: getHostHeaderLabel(formattedSecurityInfo), + [CERTIFICATE_LABEL]: getCertificateLabel(formattedSecurityInfo), + }; +}; + +/* + * Security panel component + * If the site is being served over HTTPS, you get an extra tab labeled "Security". + * This contains details about the secure connection used including the protocol, + * the cipher suite, and certificate details + */ +class SecurityPanel extends Component { + static get propTypes() { + return { + connector: PropTypes.object.isRequired, + openLink: PropTypes.func, + request: PropTypes.object.isRequired, + }; + } + + componentDidMount() { + const { request, connector } = this.props; + fetchNetworkUpdatePacket(connector.requestData, request, ["securityInfo"]); + } + + // FIXME: https://bugzilla.mozilla.org/show_bug.cgi?id=1774507 + UNSAFE_componentWillReceiveProps(nextProps) { + const { request, connector } = nextProps; + fetchNetworkUpdatePacket(connector.requestData, request, ["securityInfo"]); + } + + renderValue(props, weaknessReasons = []) { + const { member, value } = props; + + // Hide object summary + if (typeof member.value === "object") { + return null; + } + + return span( + { className: "security-info-value" }, + member.name === ERROR_LABEL + ? // Display multiline text for security error for a label using a rep. + value + : Rep( + Object.assign(props, { + // FIXME: A workaround for the issue in StringRep + // Force StringRep to crop the text everytime + member: Object.assign({}, member, { open: false }), + mode: MODE.TINY, + cropLimit: 60, + noGrip: true, + }) + ), + weaknessReasons.includes("cipher") && member.name === CIPHER_SUITE_LABEL + ? // Display an extra warning icon after the cipher suite + div({ + id: "security-warning-cipher", + className: "security-warning-icon", + title: WARNING_CIPHER_LABEL, + }) + : null + ); + } + + render() { + const { request } = this.props; + const { securityInfo, url } = request; + + if (!securityInfo || !url) { + return null; + } + + const object = getObject({ securityInfo, url }); + return div( + { className: "panel-container security-panel" }, + PropertiesView({ + object, + renderValue: props => + this.renderValue(props, securityInfo.weaknessReasons), + enableFilter: false, + expandedNodes: TreeViewClass.getExpandedNodes(object), + }) + ); + } +} + +module.exports = SecurityPanel; |