tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

SecurityPanel.js (9976B)


      1 /* This Source Code Form is subject to the terms of the Mozilla Public
      2 * License, v. 2.0. If a copy of the MPL was not distributed with this
      3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      4 
      5 "use strict";
      6 
      7 const {
      8  Component,
      9  createFactory,
     10 } = require("resource://devtools/client/shared/vendor/react.mjs");
     11 const PropTypes = require("resource://devtools/client/shared/vendor/react-prop-types.mjs");
     12 const dom = require("resource://devtools/client/shared/vendor/react-dom-factories.js");
     13 const {
     14  L10N,
     15 } = require("resource://devtools/client/netmonitor/src/utils/l10n.js");
     16 const {
     17  fetchNetworkUpdatePacket,
     18  getUrlHost,
     19 } = require("resource://devtools/client/netmonitor/src/utils/request-utils.js");
     20 
     21 // Components
     22 const TreeViewClass = ChromeUtils.importESModule(
     23  "resource://devtools/client/shared/components/tree/TreeView.mjs"
     24 ).default;
     25 const PropertiesView = createFactory(
     26  require("resource://devtools/client/netmonitor/src/components/request-details/PropertiesView.js")
     27 );
     28 
     29 loader.lazyGetter(this, "Rep", function () {
     30  return ChromeUtils.importESModule(
     31    "resource://devtools/client/shared/components/reps/index.mjs"
     32  ).REPS.Rep;
     33 });
     34 loader.lazyGetter(this, "MODE", function () {
     35  return ChromeUtils.importESModule(
     36    "resource://devtools/client/shared/components/reps/index.mjs"
     37  ).MODE;
     38 });
     39 
     40 const { div, span } = dom;
     41 const NOT_AVAILABLE = L10N.getStr("netmonitor.security.notAvailable");
     42 const ERROR_LABEL = L10N.getStr("netmonitor.security.error");
     43 const CIPHER_SUITE_LABEL = L10N.getStr("netmonitor.security.cipherSuite");
     44 const WARNING_CIPHER_LABEL = L10N.getStr("netmonitor.security.warning.cipher");
     45 const ENABLED_LABEL = L10N.getStr("netmonitor.security.enabled");
     46 const DISABLED_LABEL = L10N.getStr("netmonitor.security.disabled");
     47 const CONNECTION_LABEL = L10N.getStr("netmonitor.security.connection");
     48 const PROTOCOL_VERSION_LABEL = L10N.getStr(
     49  "netmonitor.security.protocolVersion"
     50 );
     51 const KEA_GROUP_LABEL = L10N.getStr("netmonitor.security.keaGroup");
     52 const KEA_GROUP_NONE = L10N.getStr("netmonitor.security.keaGroup.none");
     53 const KEA_GROUP_CUSTOM = L10N.getStr("netmonitor.security.keaGroup.custom");
     54 const KEA_GROUP_UNKNOWN = L10N.getStr("netmonitor.security.keaGroup.unknown");
     55 const SIGNATURE_SCHEME_LABEL = L10N.getStr(
     56  "netmonitor.security.signatureScheme"
     57 );
     58 const SIGNATURE_SCHEME_NONE = L10N.getStr(
     59  "netmonitor.security.signatureScheme.none"
     60 );
     61 const SIGNATURE_SCHEME_UNKNOWN = L10N.getStr(
     62  "netmonitor.security.signatureScheme.unknown"
     63 );
     64 const HSTS_LABEL = L10N.getStr("netmonitor.security.hsts");
     65 const HPKP_LABEL = L10N.getStr("netmonitor.security.hpkp");
     66 const CERTIFICATE_LABEL = L10N.getStr("netmonitor.security.certificate");
     67 const CERTIFICATE_TRANSPARENCY_LABEL = L10N.getStr(
     68  "certmgr.certificateTransparency.label"
     69 );
     70 const CERTIFICATE_TRANSPARENCY_POLICY_COMPLIANT = L10N.getStr(
     71  "certmgr.certificateTransparency.status.ok"
     72 );
     73 const CERTIFICATE_TRANSPARENCY_POLICY_NOT_ENOUGH_SCTS = L10N.getStr(
     74  "certmgr.certificateTransparency.status.notEnoughSCTS"
     75 );
     76 const CERTIFICATE_TRANSPARENCY_POLICY_NOT_DIVERSE_SCTS = L10N.getStr(
     77  "certmgr.certificateTransparency.status.notDiverseSCTS"
     78 );
     79 const SUBJECT_INFO_LABEL = L10N.getStr("certmgr.subjectinfo.label");
     80 const CERT_DETAIL_COMMON_NAME_LABEL = L10N.getStr("certmgr.certdetail.cn");
     81 const CERT_DETAIL_ORG_LABEL = L10N.getStr("certmgr.certdetail.o");
     82 const CERT_DETAIL_ORG_UNIT_LABEL = L10N.getStr("certmgr.certdetail.ou");
     83 const ISSUER_INFO_LABEL = L10N.getStr("certmgr.issuerinfo.label");
     84 const PERIOD_OF_VALIDITY_LABEL = L10N.getStr("certmgr.periodofvalidity.label");
     85 const BEGINS_LABEL = L10N.getStr("certmgr.begins");
     86 const EXPIRES_LABEL = L10N.getStr("certmgr.expires");
     87 const FINGERPRINTS_LABEL = L10N.getStr("certmgr.fingerprints.label");
     88 const SHA256_FINGERPRINT_LABEL = L10N.getStr(
     89  "certmgr.certdetail.sha256fingerprint"
     90 );
     91 const SHA1_FINGERPRINT_LABEL = L10N.getStr(
     92  "certmgr.certdetail.sha1fingerprint"
     93 );
     94 
     95 /*
     96 * Localize special values for key exchange group name,
     97 * certificate signature scheme, and certificate
     98 * transparency status.
     99 */
    100 const formatSecurityInfo = securityInfo => {
    101  const formattedSecurityInfo = { ...securityInfo };
    102 
    103  const formatters = {
    104    keaGroupName: value => {
    105      if (value === "none") {
    106        return KEA_GROUP_NONE;
    107      }
    108      if (value === "custom") {
    109        return KEA_GROUP_CUSTOM;
    110      }
    111      if (value === "unknown group") {
    112        return KEA_GROUP_UNKNOWN;
    113      }
    114      return value;
    115    },
    116    signatureSchemeName: value => {
    117      if (value === "none") {
    118        return SIGNATURE_SCHEME_NONE;
    119      }
    120      if (value === "unknown signature") {
    121        return SIGNATURE_SCHEME_UNKNOWN;
    122      }
    123      return value;
    124    },
    125    certificateTransparency: value => {
    126      if (value === 5) {
    127        return CERTIFICATE_TRANSPARENCY_POLICY_COMPLIANT;
    128      }
    129      if (value === 6) {
    130        return CERTIFICATE_TRANSPARENCY_POLICY_NOT_ENOUGH_SCTS;
    131      }
    132      if (value === 7) {
    133        return CERTIFICATE_TRANSPARENCY_POLICY_NOT_DIVERSE_SCTS;
    134      }
    135      return value;
    136    },
    137  };
    138 
    139  return Object.keys(formatters).reduce((acc, key) => {
    140    const formatter = formatters[key];
    141    acc[key] = formatter(acc[key]);
    142    return acc;
    143  }, formattedSecurityInfo);
    144 };
    145 
    146 const getConnectionLabel = securityInfo => ({
    147  [PROTOCOL_VERSION_LABEL]: securityInfo.protocolVersion || NOT_AVAILABLE,
    148  [CIPHER_SUITE_LABEL]: securityInfo.cipherSuite || NOT_AVAILABLE,
    149  [KEA_GROUP_LABEL]: securityInfo.keaGroupName || NOT_AVAILABLE,
    150  [SIGNATURE_SCHEME_LABEL]: securityInfo.signatureSchemeName || NOT_AVAILABLE,
    151 });
    152 
    153 const getHostHeaderLabel = securityInfo => ({
    154  [HSTS_LABEL]: securityInfo.hsts ? ENABLED_LABEL : DISABLED_LABEL,
    155  [HPKP_LABEL]: securityInfo.hpkp ? ENABLED_LABEL : DISABLED_LABEL,
    156 });
    157 
    158 const getCertificateLabel = securityInfo => {
    159  const { fingerprint, issuer, subject, validity } = securityInfo.cert;
    160 
    161  return {
    162    [SUBJECT_INFO_LABEL]: {
    163      [CERT_DETAIL_COMMON_NAME_LABEL]: subject?.commonName || NOT_AVAILABLE,
    164      [CERT_DETAIL_ORG_LABEL]: subject?.organization || NOT_AVAILABLE,
    165      [CERT_DETAIL_ORG_UNIT_LABEL]: subject?.organizationUnit || NOT_AVAILABLE,
    166    },
    167    [ISSUER_INFO_LABEL]: {
    168      [CERT_DETAIL_COMMON_NAME_LABEL]: issuer?.commonName || NOT_AVAILABLE,
    169      [CERT_DETAIL_ORG_LABEL]: issuer?.organization || NOT_AVAILABLE,
    170      [CERT_DETAIL_ORG_UNIT_LABEL]: issuer?.organizationUnit || NOT_AVAILABLE,
    171    },
    172    [PERIOD_OF_VALIDITY_LABEL]: {
    173      [BEGINS_LABEL]: validity?.start || NOT_AVAILABLE,
    174      [EXPIRES_LABEL]: validity?.end || NOT_AVAILABLE,
    175    },
    176    [FINGERPRINTS_LABEL]: {
    177      [SHA256_FINGERPRINT_LABEL]: fingerprint?.sha256 || NOT_AVAILABLE,
    178      [SHA1_FINGERPRINT_LABEL]: fingerprint?.sha1 || NOT_AVAILABLE,
    179    },
    180    [CERTIFICATE_TRANSPARENCY_LABEL]:
    181      securityInfo.certificateTransparency || NOT_AVAILABLE,
    182  };
    183 };
    184 
    185 const getObject = ({ securityInfo, url }) => {
    186  if (securityInfo.state !== "secure" && securityInfo.state !== "weak") {
    187    return {
    188      [ERROR_LABEL]: securityInfo.errorMessage || NOT_AVAILABLE,
    189    };
    190  }
    191 
    192  const HOST_HEADER_LABEL = L10N.getFormatStr(
    193    "netmonitor.security.hostHeader",
    194    getUrlHost(url)
    195  );
    196  const formattedSecurityInfo = formatSecurityInfo(securityInfo);
    197 
    198  return {
    199    [CONNECTION_LABEL]: getConnectionLabel(formattedSecurityInfo),
    200    [HOST_HEADER_LABEL]: getHostHeaderLabel(formattedSecurityInfo),
    201    [CERTIFICATE_LABEL]: getCertificateLabel(formattedSecurityInfo),
    202  };
    203 };
    204 
    205 /*
    206 * Security panel component
    207 * If the site is being served over HTTPS, you get an extra tab labeled "Security".
    208 * This contains details about the secure connection used including the protocol,
    209 * the cipher suite, and certificate details
    210 */
    211 class SecurityPanel extends Component {
    212  static get propTypes() {
    213    return {
    214      connector: PropTypes.object.isRequired,
    215      openLink: PropTypes.func,
    216      request: PropTypes.object.isRequired,
    217    };
    218  }
    219 
    220  componentDidMount() {
    221    const { request, connector } = this.props;
    222    fetchNetworkUpdatePacket(connector.requestData, request, ["securityInfo"]);
    223  }
    224 
    225  // FIXME: https://bugzilla.mozilla.org/show_bug.cgi?id=1774507
    226  UNSAFE_componentWillReceiveProps(nextProps) {
    227    const { request, connector } = nextProps;
    228    fetchNetworkUpdatePacket(connector.requestData, request, ["securityInfo"]);
    229  }
    230 
    231  renderValue(props, weaknessReasons = []) {
    232    const { member, value } = props;
    233 
    234    // Hide object summary
    235    if (typeof member.value === "object") {
    236      return null;
    237    }
    238 
    239    return span(
    240      { className: "security-info-value" },
    241      member.name === ERROR_LABEL
    242        ? // Display multiline text for security error for a label using a rep.
    243          value
    244        : Rep(
    245            Object.assign(props, {
    246              // FIXME: A workaround for the issue in StringRep
    247              // Force StringRep to crop the text everytime
    248              member: Object.assign({}, member, { open: false }),
    249              mode: MODE.TINY,
    250              cropLimit: 60,
    251              noGrip: true,
    252            })
    253          ),
    254      weaknessReasons.includes("cipher") && member.name === CIPHER_SUITE_LABEL
    255        ? // Display an extra warning icon after the cipher suite
    256          div({
    257            id: "security-warning-cipher",
    258            className: "security-warning-icon",
    259            title: WARNING_CIPHER_LABEL,
    260          })
    261        : null
    262    );
    263  }
    264 
    265  render() {
    266    const { request } = this.props;
    267    const { securityInfo, url } = request;
    268 
    269    if (!securityInfo || !url) {
    270      return null;
    271    }
    272 
    273    const object = getObject({ securityInfo, url });
    274    return div(
    275      { className: "panel-container security-panel" },
    276      PropertiesView({
    277        object,
    278        renderValue: props =>
    279          this.renderValue(props, securityInfo.weaknessReasons),
    280        enableFilter: false,
    281        expandedNodes: TreeViewClass.getExpandedNodes(object),
    282      })
    283    );
    284  }
    285 }
    286 
    287 module.exports = SecurityPanel;