DXR is a code search and navigation tool aimed at making sense of large projects. It supports full-text and regex searches as well as structural queries.

Mercurial (02b89c29412b)

VCS Links

Line Code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300
/* 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 EXPORTED_SYMBOLS = ["SecurityInfo"];

const {XPCOMUtils} = ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");

const wpl = Ci.nsIWebProgressListener;
XPCOMUtils.defineLazyServiceGetter(this, "NSSErrorsService",
                                   "@mozilla.org/nss_errors_service;1",
                                   "nsINSSErrorsService");
XPCOMUtils.defineLazyServiceGetter(this, "sss",
                                   "@mozilla.org/ssservice;1",
                                   "nsISiteSecurityService");

// NOTE: SecurityInfo is largely reworked from the devtools NetworkHelper with changes
// to better support the WebRequest api.  The objects returned are formatted specifically
// to pass through as part of a response to webRequest listeners.

const SecurityInfo = {
  /**
   * Extracts security information from nsIChannel.securityInfo.
   *
   * @param {nsIChannel} channel
   *        If null channel is assumed to be insecure.
   * @param {Object} options
   *
   * @returns {Object}
   *         Returns an object containing following members:
   *          - state: The security of the connection used to fetch this
   *                   request. Has one of following string values:
   *                    * "insecure": the connection was not secure (only http)
   *                    * "weak": the connection has minor security issues
   *                    * "broken": secure connection failed (e.g. expired cert)
   *                    * "secure": the connection was properly secured.
   *          If state == broken:
   *            - errorMessage: full error message from
   *                            nsITransportSecurityInfo.
   *          If state == secure:
   *            - protocolVersion: one of TLSv1, TLSv1.1, TLSv1.2, TLSv1.3.
   *            - cipherSuite: the cipher suite used in this connection.
   *            - cert: information about certificate used in this connection.
   *                    See parseCertificateInfo for the contents.
   *            - hsts: true if host uses Strict Transport Security,
   *                    false otherwise
   *            - hpkp: true if host uses Public Key Pinning, false otherwise
   *          If state == weak: Same as state == secure and
   *            - weaknessReasons: list of reasons that cause the request to be
   *                               considered weak. See getReasonsForWeakness.
   */
  getSecurityInfo(channel, options = {}) {
    const info = {
      state: "insecure",
    };

    /**
     * Different scenarios to consider here and how they are handled:
     * - request is HTTP, the connection is not secure
     *   => securityInfo is null
     *      => state === "insecure"
     *
     * - request is HTTPS, the connection is secure
     *   => .securityState has STATE_IS_SECURE flag
     *      => state === "secure"
     *
     * - request is HTTPS, the connection has security issues
     *   => .securityState has STATE_IS_INSECURE flag
     *   => .errorCode is an NSS error code.
     *      => state === "broken"
     *
     * - request is HTTPS, the connection was terminated before the security
     *   could be validated
     *   => .securityState has STATE_IS_INSECURE flag
     *   => .errorCode is NOT an NSS error code.
     *   => .errorMessage is not available.
     *      => state === "insecure"
     *
     * - request is HTTPS but it uses a weak cipher or old protocol, see
     *   https://hg.mozilla.org/mozilla-central/annotate/def6ed9d1c1a/
     *   security/manager/ssl/nsNSSCallbacks.cpp#l1233
     * - request is mixed content (which makes no sense whatsoever)
     *   => .securityState has STATE_IS_BROKEN flag
     *   => .errorCode is NOT an NSS error code
     *   => .errorMessage is not available
     *      => state === "weak"
     */

    let securityInfo = channel.securityInfo;
    if (!securityInfo) {
      return info;
    }

    securityInfo.QueryInterface(Ci.nsITransportSecurityInfo);

    if (NSSErrorsService.isNSSErrorCode(securityInfo.errorCode)) {
      // The connection failed.
      info.state = "broken";
      info.errorMessage = securityInfo.errorMessage;
      if (options.certificateChain && securityInfo.failedCertChain) {
        info.certificates = this.getCertificateChain(
          securityInfo.failedCertChain, options);
      }
      return info;
    }

    const state = securityInfo.securityState;

    let uri = channel.URI;
    if (uri && !uri.schemeIs("https") && !uri.schemeIs("wss")) {
      // it is not enough to look at the transport security info -
      // schemes other than https and wss are subject to
      // downgrade/etc at the scheme level and should always be
      // considered insecure.
      // Leave info.state = "insecure";
    } else if (state & wpl.STATE_IS_SECURE) {
      // The connection is secure if the scheme is sufficient
      info.state = "secure";
    } else if (state & wpl.STATE_IS_BROKEN) {
      // The connection is not secure, there was no error but there's some
      // minor security issues.
      info.state = "weak";
      info.weaknessReasons = this.getReasonsForWeakness(state);
    } else if (state & wpl.STATE_IS_INSECURE) {
      // This was most likely an https request that was aborted before
      // validation. Return info as info.state = insecure.
      return info;
    } else {
      // No known STATE_IS_* flags.
      return info;
    }

    // Cipher suite.
    info.cipherSuite = securityInfo.cipherName;

    // Key exchange group name.
    if (securityInfo.keaGroupName !== "none") {
      info.keaGroupName = securityInfo.keaGroupName;
    }

    // Certificate signature scheme.
    if (securityInfo.signatureSchemeName !== "none") {
      info.signatureSchemeName = securityInfo.signatureSchemeName;
    }

    info.isDomainMismatch = securityInfo.isDomainMismatch;
    info.isExtendedValidation = securityInfo.isExtendedValidation;
    info.isNotValidAtThisTime = securityInfo.isNotValidAtThisTime;
    info.isUntrusted = securityInfo.isUntrusted;

    info.certificateTransparencyStatus = this.getTransparencyStatus(
      securityInfo.certificateTransparencyStatus);

    // Protocol version.
    info.protocolVersion = this.formatSecurityProtocol(securityInfo.protocolVersion);

    if (options.certificateChain && securityInfo.succeededCertChain) {
      info.certificates = this.getCertificateChain(securityInfo.succeededCertChain, options);
    } else {
      info.certificates = [this.parseCertificateInfo(securityInfo.serverCert, options)];
    }

    // HSTS and HPKP if available.
    if (uri && uri.host) {
      // SiteSecurityService uses different storage if the channel is
      // private. Thus we must give isSecureURI correct flags or we
      // might get incorrect results.
      let flags = 0;
      if (channel instanceof Ci.nsIPrivateBrowsingChannel && channel.isChannelPrivate) {
        flags = Ci.nsISocketProvider.NO_PERMANENT_STORAGE;
      }

      info.hsts = sss.isSecureURI(sss.HEADER_HSTS, uri, flags);
      info.hpkp = sss.isSecureURI(sss.HEADER_HPKP, uri, flags);
    } else {
      info.hsts = false;
      info.hpkp = false;
    }

    return info;
  },

  getCertificateChain(certChain, options = {}) {
    let certificates = [];
    for (let cert of certChain.getEnumerator()) {
      certificates.push(this.parseCertificateInfo(cert, options));
    }
    return certificates;
  },

  /**
   * Takes an nsIX509Cert and returns an object with certificate information.
   *
   * @param {nsIX509Cert} cert
   *        The certificate to extract the information from.
   * @param {Object} options
   * @returns {Object}
   *         An object with following format:
   *           {
   *             subject: subjectName,
   *             issuer: issuerName,
   *             validity: { start, end },
   *             fingerprint: { sha1, sha256 }
   *           }
   */
  parseCertificateInfo(cert, options = {}) {
    if (!cert) {
      return {};
    }

    let certData = {
      subject: cert.subjectName,
      issuer: cert.issuerName,
      validity: {
        start: cert.validity.notBefore ? Math.trunc(cert.validity.notBefore / 1000) : 0,
        end: cert.validity.notAfter ? Math.trunc(cert.validity.notAfter / 1000) : 0,
      },
      fingerprint: {
        sha1: cert.sha1Fingerprint,
        sha256: cert.sha256Fingerprint,
      },
      serialNumber: cert.serialNumber,
      isBuiltInRoot: cert.isBuiltInRoot,
      subjectPublicKeyInfoDigest: {
        sha256: cert.sha256SubjectPublicKeyInfoDigest,
      },
    };
    if (options.rawDER) {
      certData.rawDER = cert.getRawDER({});
    }
    return certData;
  },

  // Bug 1355903 Transparency is currently disabled using security.pki.certificate_transparency.mode
  getTransparencyStatus(status) {
    switch (status) {
      case Ci.nsITransportSecurityInfo.CERTIFICATE_TRANSPARENCY_NOT_APPLICABLE:
        return "not_applicable";
      case Ci.nsITransportSecurityInfo.CERTIFICATE_TRANSPARENCY_POLICY_COMPLIANT:
        return "policy_compliant";
      case Ci.nsITransportSecurityInfo.CERTIFICATE_TRANSPARENCY_POLICY_NOT_ENOUGH_SCTS:
        return "policy_not_enough_scts";
      case Ci.nsITransportSecurityInfo.CERTIFICATE_TRANSPARENCY_POLICY_NOT_DIVERSE_SCTS:
        return "policy_not_diverse_scts";
    }
    return "unknown";
  },

  /**
   * Takes protocolVersion of TransportSecurityInfo object and returns human readable
   * description.
   *
   * @param {number} version
   *        One of nsITransportSecurityInfo version constants.
   * @returns {string}
   *         One of TLSv1, TLSv1.1, TLSv1.2, TLSv1.3 if version
   *         is valid, Unknown otherwise.
   */
  formatSecurityProtocol(version) {
    switch (version) {
      case Ci.nsITransportSecurityInfo.TLS_VERSION_1:
        return "TLSv1";
      case Ci.nsITransportSecurityInfo.TLS_VERSION_1_1:
        return "TLSv1.1";
      case Ci.nsITransportSecurityInfo.TLS_VERSION_1_2:
        return "TLSv1.2";
      case Ci.nsITransportSecurityInfo.TLS_VERSION_1_3:
        return "TLSv1.3";
    }
    return "unknown";
  },

  /**
   * Takes the securityState bitfield and returns reasons for weak connection
   * as an array of strings.
   *
   * @param {number} state
   *        nsITransportSecurityInfo.securityState.
   *
   * @returns {array<string>}
   *         List of weakness reasons. A subset of { cipher } where
   *         * cipher: The cipher suite is consireded to be weak (RC4).
   */
  getReasonsForWeakness(state) {
    // If there's non-fatal security issues the request has STATE_IS_BROKEN
    // flag set. See https://hg.mozilla.org/mozilla-central/file/44344099d119
    // /security/manager/ssl/nsNSSCallbacks.cpp#l1233
    let reasons = [];

    if (state & wpl.STATE_IS_BROKEN) {
      if (state & wpl.STATE_USES_WEAK_CRYPTO) {
        reasons.push("cipher");
      }
    }

    return reasons;
  },
};