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;