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 7const EXPORTED_SYMBOLS = ["SecurityInfo"]; 8 9const { XPCOMUtils } = ChromeUtils.import( 10 "resource://gre/modules/XPCOMUtils.jsm" 11); 12 13const wpl = Ci.nsIWebProgressListener; 14XPCOMUtils.defineLazyServiceGetter( 15 this, 16 "NSSErrorsService", 17 "@mozilla.org/nss_errors_service;1", 18 "nsINSSErrorsService" 19); 20XPCOMUtils.defineLazyServiceGetter( 21 this, 22 "sss", 23 "@mozilla.org/ssservice;1", 24 "nsISiteSecurityService" 25); 26XPCOMUtils.defineLazyServiceGetter( 27 this, 28 "pkps", 29 "@mozilla.org/security/publickeypinningservice;1", 30 "nsIPublicKeyPinningService" 31); 32 33// NOTE: SecurityInfo is largely reworked from the devtools NetworkHelper with changes 34// to better support the WebRequest api. The objects returned are formatted specifically 35// to pass through as part of a response to webRequest listeners. 36 37const SecurityInfo = { 38 /** 39 * Extracts security information from nsIChannel.securityInfo. 40 * 41 * @param {nsIChannel} channel 42 * If null channel is assumed to be insecure. 43 * @param {Object} options 44 * 45 * @returns {Object} 46 * Returns an object containing following members: 47 * - state: The security of the connection used to fetch this 48 * request. Has one of following string values: 49 * * "insecure": the connection was not secure (only http) 50 * * "weak": the connection has minor security issues 51 * * "broken": secure connection failed (e.g. expired cert) 52 * * "secure": the connection was properly secured. 53 * If state == broken: 54 * - errorMessage: full error message from 55 * nsITransportSecurityInfo. 56 * If state == secure: 57 * - protocolVersion: one of TLSv1, TLSv1.1, TLSv1.2, TLSv1.3. 58 * - cipherSuite: the cipher suite used in this connection. 59 * - cert: information about certificate used in this connection. 60 * See parseCertificateInfo for the contents. 61 * - hsts: true if host uses Strict Transport Security, 62 * false otherwise 63 * - hpkp: true if host uses Public Key Pinning, false otherwise 64 * If state == weak: Same as state == secure and 65 * - weaknessReasons: list of reasons that cause the request to be 66 * considered weak. See getReasonsForWeakness. 67 */ 68 getSecurityInfo(channel, options = {}) { 69 const info = { 70 state: "insecure", 71 }; 72 73 /** 74 * Different scenarios to consider here and how they are handled: 75 * - request is HTTP, the connection is not secure 76 * => securityInfo is null 77 * => state === "insecure" 78 * 79 * - request is HTTPS, the connection is secure 80 * => .securityState has STATE_IS_SECURE flag 81 * => state === "secure" 82 * 83 * - request is HTTPS, the connection has security issues 84 * => .securityState has STATE_IS_INSECURE flag 85 * => .errorCode is an NSS error code. 86 * => state === "broken" 87 * 88 * - request is HTTPS, the connection was terminated before the security 89 * could be validated 90 * => .securityState has STATE_IS_INSECURE flag 91 * => .errorCode is NOT an NSS error code. 92 * => .errorMessage is not available. 93 * => state === "insecure" 94 * 95 * - request is HTTPS but it uses a weak cipher or old protocol, see 96 * https://hg.mozilla.org/mozilla-central/annotate/def6ed9d1c1a/ 97 * security/manager/ssl/nsNSSCallbacks.cpp#l1233 98 * - request is mixed content (which makes no sense whatsoever) 99 * => .securityState has STATE_IS_BROKEN flag 100 * => .errorCode is NOT an NSS error code 101 * => .errorMessage is not available 102 * => state === "weak" 103 */ 104 105 let securityInfo = channel.securityInfo; 106 if (!securityInfo) { 107 return info; 108 } 109 110 securityInfo.QueryInterface(Ci.nsITransportSecurityInfo); 111 112 if (NSSErrorsService.isNSSErrorCode(securityInfo.errorCode)) { 113 // The connection failed. 114 info.state = "broken"; 115 info.errorMessage = securityInfo.errorMessage; 116 if (options.certificateChain && securityInfo.failedCertChain) { 117 info.certificates = this.getCertificateChain( 118 securityInfo.failedCertChain, 119 options 120 ); 121 } 122 return info; 123 } 124 125 const state = securityInfo.securityState; 126 127 let uri = channel.URI; 128 if (uri && !uri.schemeIs("https") && !uri.schemeIs("wss")) { 129 // it is not enough to look at the transport security info - 130 // schemes other than https and wss are subject to 131 // downgrade/etc at the scheme level and should always be 132 // considered insecure. 133 // Leave info.state = "insecure"; 134 } else if (state & wpl.STATE_IS_SECURE) { 135 // The connection is secure if the scheme is sufficient 136 info.state = "secure"; 137 } else if (state & wpl.STATE_IS_BROKEN) { 138 // The connection is not secure, there was no error but there's some 139 // minor security issues. 140 info.state = "weak"; 141 info.weaknessReasons = this.getReasonsForWeakness(state); 142 } else if (state & wpl.STATE_IS_INSECURE) { 143 // This was most likely an https request that was aborted before 144 // validation. Return info as info.state = insecure. 145 return info; 146 } else { 147 // No known STATE_IS_* flags. 148 return info; 149 } 150 151 // Cipher suite. 152 info.cipherSuite = securityInfo.cipherName; 153 154 // Key exchange group name. 155 if (securityInfo.keaGroupName !== "none") { 156 info.keaGroupName = securityInfo.keaGroupName; 157 } 158 159 // Certificate signature scheme. 160 if (securityInfo.signatureSchemeName !== "none") { 161 info.signatureSchemeName = securityInfo.signatureSchemeName; 162 } 163 164 info.isDomainMismatch = securityInfo.isDomainMismatch; 165 info.isExtendedValidation = securityInfo.isExtendedValidation; 166 info.isNotValidAtThisTime = securityInfo.isNotValidAtThisTime; 167 info.isUntrusted = securityInfo.isUntrusted; 168 169 info.certificateTransparencyStatus = this.getTransparencyStatus( 170 securityInfo.certificateTransparencyStatus 171 ); 172 173 // Protocol version. 174 info.protocolVersion = this.formatSecurityProtocol( 175 securityInfo.protocolVersion 176 ); 177 178 if (options.certificateChain && securityInfo.succeededCertChain) { 179 info.certificates = this.getCertificateChain( 180 securityInfo.succeededCertChain, 181 options 182 ); 183 } else { 184 info.certificates = [ 185 this.parseCertificateInfo(securityInfo.serverCert, options), 186 ]; 187 } 188 189 // HSTS and static pinning if available. 190 if (uri && uri.host) { 191 // SiteSecurityService uses different storage if the channel is 192 // private. Thus we must give isSecureURI correct flags or we 193 // might get incorrect results. 194 let flags = 0; 195 if ( 196 channel instanceof Ci.nsIPrivateBrowsingChannel && 197 channel.isChannelPrivate 198 ) { 199 flags = Ci.nsISocketProvider.NO_PERMANENT_STORAGE; 200 } 201 202 info.hsts = sss.isSecureURI(uri, flags); 203 info.hpkp = pkps.hostHasPins(uri); 204 } else { 205 info.hsts = false; 206 info.hpkp = false; 207 } 208 209 return info; 210 }, 211 212 getCertificateChain(certChain, options = {}) { 213 let certificates = []; 214 for (let cert of certChain) { 215 certificates.push(this.parseCertificateInfo(cert, options)); 216 } 217 return certificates; 218 }, 219 220 /** 221 * Takes an nsIX509Cert and returns an object with certificate information. 222 * 223 * @param {nsIX509Cert} cert 224 * The certificate to extract the information from. 225 * @param {Object} options 226 * @returns {Object} 227 * An object with following format: 228 * { 229 * subject: subjectName, 230 * issuer: issuerName, 231 * validity: { start, end }, 232 * fingerprint: { sha1, sha256 } 233 * } 234 */ 235 parseCertificateInfo(cert, options = {}) { 236 if (!cert) { 237 return {}; 238 } 239 240 let certData = { 241 subject: cert.subjectName, 242 issuer: cert.issuerName, 243 validity: { 244 start: cert.validity.notBefore 245 ? Math.trunc(cert.validity.notBefore / 1000) 246 : 0, 247 end: cert.validity.notAfter 248 ? Math.trunc(cert.validity.notAfter / 1000) 249 : 0, 250 }, 251 fingerprint: { 252 sha1: cert.sha1Fingerprint, 253 sha256: cert.sha256Fingerprint, 254 }, 255 serialNumber: cert.serialNumber, 256 isBuiltInRoot: cert.isBuiltInRoot, 257 subjectPublicKeyInfoDigest: { 258 sha256: cert.sha256SubjectPublicKeyInfoDigest, 259 }, 260 }; 261 if (options.rawDER) { 262 certData.rawDER = cert.getRawDER(); 263 } 264 return certData; 265 }, 266 267 // Bug 1355903 Transparency is currently disabled using security.pki.certificate_transparency.mode 268 getTransparencyStatus(status) { 269 switch (status) { 270 case Ci.nsITransportSecurityInfo.CERTIFICATE_TRANSPARENCY_NOT_APPLICABLE: 271 return "not_applicable"; 272 case Ci.nsITransportSecurityInfo 273 .CERTIFICATE_TRANSPARENCY_POLICY_COMPLIANT: 274 return "policy_compliant"; 275 case Ci.nsITransportSecurityInfo 276 .CERTIFICATE_TRANSPARENCY_POLICY_NOT_ENOUGH_SCTS: 277 return "policy_not_enough_scts"; 278 case Ci.nsITransportSecurityInfo 279 .CERTIFICATE_TRANSPARENCY_POLICY_NOT_DIVERSE_SCTS: 280 return "policy_not_diverse_scts"; 281 } 282 return "unknown"; 283 }, 284 285 /** 286 * Takes protocolVersion of TransportSecurityInfo object and returns human readable 287 * description. 288 * 289 * @param {number} version 290 * One of nsITransportSecurityInfo version constants. 291 * @returns {string} 292 * One of TLSv1, TLSv1.1, TLSv1.2, TLSv1.3 if version 293 * is valid, Unknown otherwise. 294 */ 295 formatSecurityProtocol(version) { 296 switch (version) { 297 case Ci.nsITransportSecurityInfo.TLS_VERSION_1: 298 return "TLSv1"; 299 case Ci.nsITransportSecurityInfo.TLS_VERSION_1_1: 300 return "TLSv1.1"; 301 case Ci.nsITransportSecurityInfo.TLS_VERSION_1_2: 302 return "TLSv1.2"; 303 case Ci.nsITransportSecurityInfo.TLS_VERSION_1_3: 304 return "TLSv1.3"; 305 } 306 return "unknown"; 307 }, 308 309 /** 310 * Takes the securityState bitfield and returns reasons for weak connection 311 * as an array of strings. 312 * 313 * @param {number} state 314 * nsITransportSecurityInfo.securityState. 315 * 316 * @returns {array<string>} 317 * List of weakness reasons. A subset of { cipher } where 318 * * cipher: The cipher suite is consireded to be weak (RC4). 319 */ 320 getReasonsForWeakness(state) { 321 // If there's non-fatal security issues the request has STATE_IS_BROKEN 322 // flag set. See https://hg.mozilla.org/mozilla-central/file/44344099d119 323 // /security/manager/ssl/nsNSSCallbacks.cpp#l1233 324 let reasons = []; 325 326 if (state & wpl.STATE_IS_BROKEN) { 327 if (state & wpl.STATE_USES_WEAK_CRYPTO) { 328 reasons.push("cipher"); 329 } 330 } 331 332 return reasons; 333 }, 334}; 335