summaryrefslogtreecommitdiffstats
path: root/devtools/shared/gcli/commands/security.js
blob: eeed03c61c95af4dedb81e2577624165aa8332d9 (plain)
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
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
/* 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/. */

/**
 * The Security devtool supports the following arguments:
 * * Security CSP
 *   Provides feedback about the current CSP
 *
 *  * Security referrer
 *    Provides information about the current referrer policy
 */

"use strict";

const { Cc, Ci, Cu, CC } = require("chrome");
const l10n = require("gcli/l10n");
const CSP = Cc["@mozilla.org/cspcontext;1"].getService(Ci.nsIContentSecurityPolicy);

const GOOD_IMG_SRC = "chrome://browser/content/gcli_sec_good.svg";
const MOD_IMG_SRC = "chrome://browser/content/gcli_sec_moderate.svg";
const BAD_IMG_SRC = "chrome://browser/content/gcli_sec_bad.svg";


// special handling within policy
const POLICY_REPORT_ONLY = "report-only"

// special handling of directives
const DIR_UPGRADE_INSECURE = "upgrade-insecure-requests";
const DIR_BLOCK_ALL_MIXED_CONTENT = "block-all-mixed-content";

// special handling of sources
const SRC_UNSAFE_INLINE = "'unsafe-inline'";
const SRC_UNSAFE_EVAL = "'unsafe-eval'";

const WILDCARD_MSG = l10n.lookup("securityCSPRemWildCard");
const XSS_WARNING_MSG = l10n.lookup("securityCSPPotentialXSS");
const NO_CSP_ON_PAGE_MSG = l10n.lookup("securityCSPNoCSPOnPage");
const CONTENT_SECURITY_POLICY_MSG = l10n.lookup("securityCSPHeaderOnPage");
const CONTENT_SECURITY_POLICY_REPORT_ONLY_MSG = l10n.lookup("securityCSPROHeaderOnPage");

const NEXT_URI_HEADER = l10n.lookup("securityReferrerNextURI");
const CALCULATED_REFERRER_HEADER = l10n.lookup("securityReferrerCalculatedReferrer");
/* The official names from the W3C Referrer Policy Draft http://www.w3.org/TR/referrer-policy/ */
const REFERRER_POLICY_NAMES = [ "None When Downgrade (default)", "None", "Origin Only", "Origin When Cross-Origin", "Unsafe URL" ];

exports.items = [
  {
    // --- General Security information
    name: "security",
    description: l10n.lookup("securityPrivacyDesc"),
    manual: l10n.lookup("securityManual")
  },
  {
    // --- CSP specific Security information
    item: "command",
    runAt: "server",
    name: "security csp",
    description: l10n.lookup("securityCSPDesc"),
    manual: l10n.lookup("securityCSPManual"),
    returnType: "securityCSPInfo",
    exec: function(args, context) {

      var cspJSON = context.environment.document.nodePrincipal.cspJSON;
      var cspOBJ = JSON.parse(cspJSON);

      var outPolicies = [];

      var policies = cspOBJ["csp-policies"];

      // loop over all the different policies
      for (var csp in policies) {
        var curPolicy = policies[csp];

        // loop over all the directive-values within that policy
        var outDirectives = [];
        var outHeader = CONTENT_SECURITY_POLICY_MSG;
        for (var dir in curPolicy) {
          var curDir = curPolicy[dir];

          // when iterating properties within the obj we might also
          // encounter the 'report-only' flag, which is not a csp directive.
          if (dir === POLICY_REPORT_ONLY) {
            outHeader = curPolicy[POLICY_REPORT_ONLY] === true ?
                          CONTENT_SECURITY_POLICY_REPORT_ONLY_MSG :
                          CONTENT_SECURITY_POLICY_MSG;
            continue;
          }

          // loop over all the directive-sources within that directive
          var outSrcs = [];

          // special case handling for the directives
          // upgrade-insecure-requests and block-all-mixed-content
          // which do not include any srcs
          if (dir === DIR_UPGRADE_INSECURE ||
              dir === DIR_BLOCK_ALL_MIXED_CONTENT) {
            outSrcs.push({
              icon: GOOD_IMG_SRC,
              src: "", // no src
              desc: "" // no description
            });
          }

          for (var src in curDir) {
            var curSrc = curDir[src];

            // the default icon and descritpion of the directive-src
            var outIcon = GOOD_IMG_SRC;
            var outDesc = "";

            if (curSrc.indexOf("*") > -1) {
              outIcon = MOD_IMG_SRC;
              outDesc = WILDCARD_MSG;
            }
            if (curSrc == SRC_UNSAFE_INLINE || curSrc == SRC_UNSAFE_EVAL) {
              outIcon = BAD_IMG_SRC;
              outDesc = XSS_WARNING_MSG;
            }
            outSrcs.push({
              icon: outIcon,
              src: curSrc,
              desc: outDesc
            });
          }
          // append info about that directive to the directives array
          outDirectives.push({
            dirValue: dir,
            dirSrc: outSrcs
          });
        }
        // append info about the policy to the policies array
        outPolicies.push({
          header: outHeader,
          directives: outDirectives
        });
      }
      return outPolicies;
    }
  },
  {
    item: "converter",
    from: "securityCSPInfo",
    to: "view",
    exec: function(cspInfo, context) {
      var url = context.environment.target.url;

      if (cspInfo.length == 0) {
        return context.createView({
          html:
            "<table class='gcli-csp-detail' cellspacing='10' valign='top'>" +
            "  <tr>" +
            "    <td> <img src='chrome://browser/content/gcli_sec_bad.svg' width='20px' /> </td> " +
            "    <td>" + NO_CSP_ON_PAGE_MSG + " <b>" + url + "</b></td>" +
            "  </tr>" +
            "</table>"});
      }

      return context.createView({
        html:
          "<table class='gcli-csp-detail' cellspacing='10' valign='top'>" +
          // iterate all policies
          "  <tr foreach='csp in ${cspinfo}' >" +
          "    <td> ${csp.header} <b>" + url + "</b><br/><br/>" +
          "      <table class='gcli-csp-dir-detail' valign='top'>" +
          // >> iterate all directives
          "        <tr foreach='dir in ${csp.directives}' >" +
          "          <td valign='top'> ${dir.dirValue} </td>" +
          "          <td valign='top'>" +
          "            <table class='gcli-csp-src-detail' valign='top'>" +
          // >> >> iterate all srs
          "              <tr foreach='src in ${dir.dirSrc}' >" +
          "                <td valign='center' width='20px'> <img src= \"${src.icon}\" width='20px' /> </td> " +
          "                <td valign='center' width='200px'> ${src.src} </td>" +
          "                <td valign='center'> ${src.desc} </td>" +
          "              </tr>" +
          "            </table>" +
          "          </td>" +
          "        </tr>" +
          "      </table>" +
          "    </td>" +
          "  </tr>" +
          "</table>",
          data: {
            cspinfo: cspInfo,
          }
        });
    }
  },
  {
    // --- Referrer Policy specific Security information
    item: "command",
    runAt: "server",
    name: "security referrer",
    description: l10n.lookup("securityReferrerPolicyDesc"),
    manual: l10n.lookup("securityReferrerPolicyManual"),
    returnType: "securityReferrerPolicyInfo",
    exec: function(args, context) {
      var doc = context.environment.document;

      var referrerPolicy = doc.referrerPolicy;

      var pageURI = doc.documentURIObject;
      var sameDomainReferrer = "";
      var otherDomainReferrer = "";
      var downgradeReferrer = "";
      var otherDowngradeReferrer = "";
      var origin = pageURI.prePath;

      switch (referrerPolicy) {
        case Ci.nsIHttpChannel.REFERRER_POLICY_NO_REFERRER:
          // sends no referrer
          sameDomainReferrer
            = otherDomainReferrer
            = downgradeReferrer
            = otherDowngradeReferrer
            = "(no referrer)";
          break;
        case Ci.nsIHttpChannel.REFERRER_POLICY_ORIGIN:
          // only sends the origin of the referring URL
          sameDomainReferrer
            = otherDomainReferrer
            = downgradeReferrer
            = otherDowngradeReferrer
            = origin;
          break;
        case Ci.nsIHttpChannel.REFERRER_POLICY_ORIGIN_WHEN_XORIGIN:
          // same as default, but reduced to ORIGIN when cross-origin.
          sameDomainReferrer = pageURI.spec;
          otherDomainReferrer
            = downgradeReferrer
            = otherDowngradeReferrer
            = origin;
          break;
        case Ci.nsIHttpChannel.REFERRER_POLICY_UNSAFE_URL:
          // always sends the referrer, even on downgrade.
          sameDomainReferrer
            = otherDomainReferrer
            = downgradeReferrer
            = otherDowngradeReferrer
            = pageURI.spec;
          break;
        case Ci.nsIHttpChannel.REFERRER_POLICY_NO_REFERRER_WHEN_DOWNGRADE:
          // default state, doesn't send referrer from https->http
          sameDomainReferrer = otherDomainReferrer = pageURI.spec;
          downgradeReferrer = otherDowngradeReferrer = "(no referrer)";
          break;
        default:
          // this is a new referrer policy which we do not know about
          sameDomainReferrer
            = otherDomainReferrer
            = downgradeReferrer
            = otherDowngradeReferrer
            = "(unknown Referrer Policy)";
          break;
      }

      var sameDomainUri = origin + "/*";

      var referrerUrls = [
        // add the referrer uri 'referrer' we would send when visiting 'uri'
        {
          uri: pageURI.scheme+'://example.com/',
          referrer: otherDomainReferrer,
          description: l10n.lookup('securityReferrerPolicyOtherDomain')},
        {
          uri: sameDomainUri,
          referrer: sameDomainReferrer,
          description: l10n.lookup('securityReferrerPolicySameDomain')}
      ];

      if (pageURI.schemeIs('https')) {
        // add the referrer we would send on downgrading http->https
        if (sameDomainReferrer != downgradeReferrer) {
          referrerUrls.push({
            uri: "http://"+pageURI.hostPort+"/*",
            referrer: downgradeReferrer,
            description:
              l10n.lookup('securityReferrerPolicySameDomainDowngrade')
          });
        }
        if (otherDomainReferrer != otherDowngradeReferrer) {
          referrerUrls.push({
            uri: "http://example.com/",
            referrer: otherDowngradeReferrer,
            description:
              l10n.lookup('securityReferrerPolicyOtherDomainDowngrade')
          });
        }
      }

      return {
        header: l10n.lookupFormat("securityReferrerPolicyReportHeader",
                                  [pageURI.spec]),
        policyName: REFERRER_POLICY_NAMES[referrerPolicy],
        urls: referrerUrls
      }
    }
  },
  {
    item: "converter",
    from: "securityReferrerPolicyInfo",
    to: "view",
    exec: function(referrerPolicyInfo, context) {
      return context.createView({
          html:
          "<div class='gcli-referrer-policy'>" +
          "  <strong> ${rpi.header} </strong> <br />" +
          "  ${rpi.policyName} <br />" +
          "  <table class='gcli-referrer-policy-detail' cellspacing='10' >" +
          "    <tr>" +
          "      <th> " + NEXT_URI_HEADER + " </th>" +
          "      <th> " + CALCULATED_REFERRER_HEADER + " </th>" +
          "    </tr>" +
          // iterate all policies
          "    <tr foreach='nextURI in ${rpi.urls}' >" +
          "      <td> ${nextURI.description} (e.g., ${nextURI.uri}) </td>" +
          "      <td> ${nextURI.referrer} </td>" +
          "    </tr>" +
          "  </table>" +
          "</div>",
          data: {
            rpi: referrerPolicyInfo,
          }
        });
     }
  }
];