summaryrefslogtreecommitdiffstats
path: root/devtools/client/netmonitor/src/components/request-details/SecurityPanel.js
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--devtools/client/netmonitor/src/components/request-details/SecurityPanel.js283
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;