Bug 883975 - CSP 1.1 hash-source. r=sstamm, r=dholbert, r=mrbkap
authorGarrett Robinson <grobinson@mozilla.com>
Thu, 02 Jan 2014 11:14:06 -0800
changeset 179338 d570802145c998d4002af2e186f9860c46eb3144
parent 179337 0d816dd5e310e4b84c690e6b43b85168ced9d25a
child 179339 64712081790e4f1fcb2362b6a32a513e32470987
push id3343
push userffxbld
push dateMon, 17 Mar 2014 21:55:32 +0000
treeherdermozilla-beta@2f7d3415f79f [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerssstamm, dholbert, mrbkap
bugs883975
milestone29.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 883975 - CSP 1.1 hash-source. r=sstamm, r=dholbert, r=mrbkap
caps/src/nsScriptSecurityManager.cpp
content/base/public/nsIContentSecurityPolicy.idl
content/base/src/CSPUtils.jsm
content/base/src/contentSecurityPolicy.js
content/base/src/nsScriptLoader.cpp
content/base/test/csp/file_hash_source.html
content/base/test/csp/file_hash_source.html^headers^
content/base/test/csp/mochitest.ini
content/base/test/csp/test_hash_source.html
content/events/src/nsEventListenerManager.cpp
dom/base/nsJSTimeoutHandler.cpp
dom/src/jsurl/nsJSProtocolHandler.cpp
dom/workers/RuntimeService.cpp
layout/style/nsStyleUtil.cpp
security/manager/ssl/src/nsCrypto.cpp
testing/mochitest/b2g.json
--- a/caps/src/nsScriptSecurityManager.cpp
+++ b/caps/src/nsScriptSecurityManager.cpp
@@ -401,16 +401,17 @@ nsScriptSecurityManager::ContentSecurity
             if (const char *file = JS_GetScriptFilename(cx, script)) {
                 CopyUTF8toUTF16(nsDependentCString(file), fileName);
             }
         }
         csp->LogViolationDetails(nsIContentSecurityPolicy::VIOLATION_TYPE_EVAL,
                                  fileName,
                                  scriptSample,
                                  lineNum,
+                                 EmptyString(),
                                  EmptyString());
     }
 
     return evalOK;
 }
 
 bool
 nsScriptSecurityManager::CheckObjectAccess(JSContext *cx, JS::Handle<JSObject*> obj,
--- a/content/base/public/nsIContentSecurityPolicy.idl
+++ b/content/base/public/nsIContentSecurityPolicy.idl
@@ -10,17 +10,17 @@ interface nsIDocShell;
 
 /**
  * nsIContentSecurityPolicy
  * Describes an XPCOM component used to model and enforce CSPs.  Instances of
  * this class may have multiple policies within them, but there should only be
  * one of these per document/principal.
  */
 
-[scriptable, uuid(781b6511-f1fa-4e2c-8eff-1739d091eb2f)]
+[scriptable, uuid(ff46c14e-5b2d-4aca-8961-d0b0d987cb81)]
 interface nsIContentSecurityPolicy : nsISupports
 {
 
   /**
    * Set to true when the CSP has been read in and parsed and is ready to
    * enforce.  This is a barrier for the nsDocument so it doesn't load any
    * sub-content until either it knows that a CSP is ready or will not be used.
    */
@@ -117,16 +117,35 @@ interface nsIContentSecurityPolicy : nsI
    *     well.
    * @return
    *     Whether or not this nonce is valid
    */
    boolean getAllowsNonce(in AString aNonce,
                           in unsigned long aContentType,
                           out boolean shouldReportViolation);
 
+   /**
+    * Whether this policy accepts the given inline resource based on the hash
+    * of its content.
+    * @param aContent
+    *     The content of the inline resource to hash (and compare to the
+    *     hashes listed in the policy)
+    * @param aContentType
+    *     The type of inline element (script or style)
+    * @param shouldReportViolation
+    *     Whether this inline resource should be reported as a hash-source
+    *     violation. If there are no hash-sources in the policy, this is
+    *     always false.
+    * @return
+    *     Whether or not this inline resource is whitelisted by a hash-source
+    */
+   boolean getAllowsHash(in AString aContent,
+                         in unsigned short aContentType,
+                         out boolean shouldReportViolation);
+
   /**
    * For each violated policy (of type violationType), log policy violation on
    * the Error Console and send a report to report-uris present in the violated
    * policies.
    *
    * @param violationType
    *     one of the VIOLATION_TYPE_* constants, e.g. inline-script or eval
    * @param sourceFile
@@ -134,28 +153,36 @@ interface nsIContentSecurityPolicy : nsI
    * @param contentSample
    *     sample of the violating content (to aid debugging)
    * @param lineNum
    *     source line number of the violation (if available)
    * @param aNonce
    *     (optional) If this is a nonce violation, include the nonce so we can
    *     recheck to determine which policies were violated and send the
    *     appropriate reports.
+   * @param aContent
+   *     (optional) If this is a hash violation, include contents of the inline
+   *     resource in the question so we can recheck the hash in order to
+   *     determine which policies were violated and send the appropriate
+   *     reports.
    */
   void logViolationDetails(in unsigned short violationType,
                            in AString sourceFile,
                            in AString scriptSample,
                            in int32_t lineNum,
-                           [optional] in AString nonce);
+                           [optional] in AString nonce,
+                           [optional] in AString content);
 
   const unsigned short VIOLATION_TYPE_INLINE_SCRIPT = 1;
   const unsigned short VIOLATION_TYPE_EVAL          = 2;
   const unsigned short VIOLATION_TYPE_INLINE_STYLE  = 3;
   const unsigned short VIOLATION_TYPE_NONCE_SCRIPT  = 4;
   const unsigned short VIOLATION_TYPE_NONCE_STYLE   = 5;
+  const unsigned short VIOLATION_TYPE_HASH_SCRIPT   = 6;
+  const unsigned short VIOLATION_TYPE_HASH_STYLE    = 7;
 
   /**
    * Called after the CSP object is created to fill in the appropriate request
    * and request header information needed in case a report needs to be sent.
    */
   void scanRequestData(in nsIHttpChannel aChannel);
 
   /**
--- a/content/base/src/CSPUtils.jsm
+++ b/content/base/src/CSPUtils.jsm
@@ -61,25 +61,35 @@ const R_HOSTSRC    = new RegExp ("^((" +
 
 // ext-host-source = host-source "/" *( <VCHAR except ";" and ","> )
 //                 ; ext-host-source is reserved for future use.
 const R_EXTHOSTSRC = new RegExp ("^" + R_HOSTSRC.source + "\\/[:print:]+$", 'i');
 
 // keyword-source  = "'self'" / "'unsafe-inline'" / "'unsafe-eval'"
 const R_KEYWORDSRC = new RegExp ("^('self'|'unsafe-inline'|'unsafe-eval')$", 'i');
 
+const R_BASE64     = new RegExp ("([a-zA-Z0-9+/]+={0,2})");
+
 // nonce-source      = "'nonce-" nonce-value "'"
 // nonce-value       = 1*( ALPHA / DIGIT / "+" / "/" )
-const R_NONCESRC = new RegExp ("^'nonce-([a-zA-Z0-9\+\/]+)'$", 'i');
+const R_NONCESRC = new RegExp ("^'nonce-" + R_BASE64.source + "'$");
+
+// hash-source       = "'" hash-algo "-" hash-value "'"
+// hash-algo         = "sha256" / "sha384" / "sha512"
+// hash-value        = 1*( ALPHA / DIGIT / "+" / "/" / "=" )
+// Each algo must be a valid argument to nsICryptoHash.init
+const R_HASH_ALGOS = new RegExp ("(sha256|sha384|sha512)");
+const R_HASHSRC    = new RegExp ("^'" + R_HASH_ALGOS.source + "-" + R_BASE64.source + "'$");
 
 // source-exp      = scheme-source / host-source / keyword-source
 const R_SOURCEEXP  = new RegExp (R_SCHEMESRC.source + "|" +
                                    R_HOSTSRC.source + "|" +
                                 R_KEYWORDSRC.source + "|" +
-                                  R_NONCESRC.source,  'i');
+                                  R_NONCESRC.source + "|" +
+                                   R_HASHSRC.source,  'i');
 
 
 this.CSPPrefObserver = {
   get debugEnabled () {
     if (!this._branch)
       this._initialize();
     return this._debugEnabled;
   },
@@ -801,57 +811,61 @@ CSPRep.prototype = {
     for (var i in this._directives) {
       if (this._directives[i]) {
         dirs.push(i + " " + this._directives[i].toString());
       }
     }
     return dirs.join("; ");
   },
 
+  permitsNonce:
+  function csp_permitsNonce(aNonce, aDirective) {
+    if (!this._directives.hasOwnProperty(aDirective)) return false;
+    return this._directives[aDirective]._sources.some(function (source) {
+      return source instanceof CSPNonceSource && source.permits(aNonce);
+    });
+  },
+
+  permitsHash:
+  function csp_permitsHash(aContent, aDirective) {
+    if (!this._directives.hasOwnProperty(aDirective)) return false;
+    return this._directives[aDirective]._sources.some(function (source) {
+      return source instanceof CSPHashSource && source.permits(aContent);
+    });
+  },
+
   /**
    * Determines if this policy accepts a URI.
    * @param aURI
    *        URI of the requested resource
    * @param aDirective
    *        one of the SRC_DIRECTIVES defined above
-   * @param aContext
-   *        Context of the resource being requested. This is a type inheriting
-   *        from nsIDOMHTMLElement if this is called from shouldLoad to check
-   *        an external resource load, and refers to the HTML element that is
-   *        causing the resource load. Otherwise, it is a string containing
-   *        a nonce from a nonce="" attribute if it is called from
-   *        getAllowsNonce.
    * @returns
    *        true if the policy permits the URI in given context.
    */
   permits:
-  function csp_permits(aURI, aDirective, aContext) {
-    // In the case where permits is called from getAllowsNonce (for an inline
-    // element), aURI is null and aContext has a specific value. Otherwise,
-    // calling permits without aURI is invalid.
-    let checking_nonce = aContext instanceof Ci.nsIDOMHTMLElement ||
-                         typeof aContext === 'string';
-    if (!aURI && !checking_nonce) return false;
+  function csp_permits(aURI, aDirective) {
+    if (!aURI) return false;
 
     // GLOBALLY ALLOW "about:" SCHEME
     if (aURI instanceof String && aURI.substring(0,6) === "about:")
       return true;
     if (aURI instanceof Ci.nsIURI && aURI.scheme === "about")
       return true;
 
     // make sure the right directive set is used
     let DIRS = this._specCompliant ? CSPRep.SRC_DIRECTIVES_NEW : CSPRep.SRC_DIRECTIVES_OLD;
 
     let directiveInPolicy = false;
     for (var i in DIRS) {
       if (DIRS[i] === aDirective) {
         // for catching calls with invalid contexts (below)
         directiveInPolicy = true;
         if (this._directives.hasOwnProperty(aDirective)) {
-          return this._directives[aDirective].permits(aURI, aContext);
+          return this._directives[aDirective].permits(aURI);
         }
         //found matching dir, can stop looking
         break;
       }
     }
 
     // frame-ancestors is a special case; it doesn't fall back to default-src.
     if (aDirective === DIRS.FRAME_ANCESTORS)
@@ -866,17 +880,17 @@ CSPRep.prototype = {
       return false;
     }
 
     // no directives specifically matched, fall back to default-src.
     // (default-src may not be present for CSP 1.0-compliant policies, and
     // indicates no relevant directives were present and the load should be
     // permitted).
     if (this._directives.hasOwnProperty(DIRS.DEFAULT_SRC)) {
-      return this._directives[DIRS.DEFAULT_SRC].permits(aURI, aContext);
+      return this._directives[DIRS.DEFAULT_SRC].permits(aURI);
     }
 
     // no relevant directives present -- this means for CSP 1.0 that the load
     // should be permitted (and for the old CSP, to block it).
     return this._specCompliant;
   },
 
   /**
@@ -944,16 +958,22 @@ this.CSPSourceList = function CSPSourceL
   this._sources = [];
   this._permitAllSources = false;
 
   // When this is true, the source list contains 'unsafe-inline'.
   this._allowUnsafeInline = false;
 
   // When this is true, the source list contains 'unsafe-eval'.
   this._allowUnsafeEval = false;
+
+  // When this is true, the source list contains at least one nonce-source
+  this._hasNonceSource = false;
+
+  // When this is true, the source list contains at least one hash-source
+  this._hasHashSource = false;
 }
 
 /**
  * Factory to create a new CSPSourceList, parsed from a string.
  *
  * @param aStr
  *        string rep of a CSP Source List
  * @param aCSPRep
@@ -1005,16 +1025,22 @@ CSPSourceList.fromString = function(aStr
     // if a source allows unsafe-inline, set our flag to indicate this.
     if (src._allowUnsafeInline)
       slObj._allowUnsafeInline = true;
 
     // if a source allows unsafe-eval, set our flag to indicate this.
     if (src._allowUnsafeEval)
       slObj._allowUnsafeEval = true;
 
+    if (src instanceof CSPNonceSource)
+      slObj._hasNonceSource = true;
+
+    if (src instanceof CSPHashSource)
+      slObj._hasHashSource = true;
+
     // if a source is a *, then we can permit all sources
     if (src.permitAll) {
       slObj._permitAllSources = true;
     } else {
       slObj._sources.push(src);
     }
   }
 
@@ -1105,38 +1131,37 @@ CSPSourceList.prototype = {
   /**
    * Determines if this directive accepts a URI.
    * @param aURI
    *        the URI in question
    * @returns
    *        true if the URI matches a source in this source list.
    */
   permits:
-  function cspsd_permits(aURI, aContext) {
+  function cspsd_permits(aURI) {
     if (this.isNone())    return false;
     if (this.isAll())     return true;
 
     for (var i in this._sources) {
-      if (this._sources[i].permits(aURI, aContext)) {
+      if (this._sources[i].permits(aURI)) {
         return true;
       }
     }
     return false;
   }
 }
 
 //////////////////////////////////////////////////////////////////////
 /**
  * Class to model a source (scheme, host, port)
  */
 this.CSPSource = function CSPSource() {
   this._scheme = undefined;
   this._port = undefined;
   this._host = undefined;
-  this._nonce = undefined;
 
   //when set to true, this allows all source
   this._permitAll = false;
 
   // when set to true, this source represents 'self'
   this._isSelf = false;
 
   // when set to true, this source allows inline scripts or styles
@@ -1372,27 +1397,22 @@ CSPSource.fromString = function(aStr, aC
     else {
       // strip the ':' from the port
       sObj._port = portMatch[0].substr(1);
     }
     return sObj;
   }
 
   // check for a nonce-source match
-  if (R_NONCESRC.test(aStr)) {
-    // We can't put this check outside of the regex test because R_NONCESRC is
-    // included in R_SOURCEEXP, which is const. By testing here, we can
-    // explicitly return null for nonces if experimental is not enabled,
-    // instead of letting it fall through and assuming it won't accidentally
-    // match something later in this function.
-    if (!CSPPrefObserver.experimentalEnabled) return null;
-    var nonceSrcMatch = R_NONCESRC.exec(aStr);
-    sObj._nonce = nonceSrcMatch[1];
-    return sObj;
-  }
+  if (R_NONCESRC.test(aStr))
+    return CSPNonceSource.fromString(aStr, aCSPRep);
+
+  // check for a hash-source match
+  if (R_HASHSRC.test(aStr))
+    return CSPHashSource.fromString(aStr, aCSPRep);
 
   // check for 'self' (case insensitive)
   if (aStr.toUpperCase() === "'SELF'") {
     if (!self) {
       cspError(aCSPRep, CSPLocalizer.getStr("selfKeywordNoSelfData"));
       return null;
     }
     sObj._self = self.clone();
@@ -1488,61 +1508,45 @@ CSPSource.prototype = {
 
     var s = "";
     if (this.scheme)
       s = s + this.scheme + "://";
     if (this._host)
       s = s + this._host;
     if (this.port)
       s = s + ":" + this.port;
-    if (this._nonce)
-      s = s + "'nonce-" + this._nonce + "'";
     return s;
   },
 
   /**
    * Makes a new deep copy of this object.
    * @returns
    *      a new CSPSource
    */
   clone:
   function() {
     var aClone = new CSPSource();
     aClone._self = this._self ? this._self.clone() : undefined;
     aClone._scheme = this._scheme;
     aClone._port = this._port;
     aClone._host = this._host ? this._host.clone() : undefined;
-    aClone._nonce = this._nonce;
     aClone._isSelf = this._isSelf;
     aClone._CSPRep = this._CSPRep;
     return aClone;
   },
 
   /**
    * Determines if this Source accepts a URI.
    * @param aSource
    *        the URI, or CSPSource in question
-   * @param aContext
-   *        the context of the resource being loaded
    * @returns
    *        true if the URI matches a source in this source list.
    */
   permits:
-  function(aSource, aContext) {
-    if (this._nonce && CSPPrefObserver.experimentalEnabled) {
-      if (aContext instanceof Ci.nsIDOMHTMLElement) {
-        return this._nonce === aContext.getAttribute('nonce');
-      } else if (typeof aContext === 'string') {
-        return this._nonce === aContext;
-      }
-    }
-    // We only use aContext for nonce checks. If it's otherwise provided,
-    // ignore it.
-    if (!CSPPrefObserver.experimentalEnabled && aContext) return false;
-
+  function(aSource) {
     if (!aSource) return false;
 
     if (!(aSource instanceof CSPSource))
       aSource = CSPSource.create(aSource, this._CSPRep);
 
     // verify scheme
     if (this.scheme != aSource.scheme)
       return false;
@@ -1727,16 +1731,137 @@ CSPHost.prototype = {
     for (var i=0; i<this._segments.length; i++) {
       if (this._segments[i] != that._segments[i])
         return false;
     }
     return true;
   }
 };
 
+this.CSPNonceSource = function CSPNonceSource() {
+  this._nonce = undefined;
+}
+
+CSPNonceSource.fromString = function(aStr, aCSPRep) {
+  if (!CSPPrefObserver.experimentalEnabled)
+    return null;
+
+  let nonce = R_NONCESRC.exec(aStr)[1];
+  if (!nonce) {
+    cspError(aCSPRep, "Error in parsing nonce-source from string: nonce was empty");
+    return null;
+  }
+
+  let nonceSourceObj = new CSPNonceSource();
+  nonceSourceObj._nonce = nonce;
+  return nonceSourceObj;
+};
+
+CSPNonceSource.prototype = {
+
+  permits: function(aContext) {
+    if (!CSPPrefObserver.experimentalEnabled) return false;
+
+    if (aContext instanceof Ci.nsIDOMHTMLElement) {
+      return this._nonce === aContext.getAttribute('nonce');
+    } else if (typeof aContext === 'string') {
+      return this._nonce === aContext;
+    }
+    CSPdebug("permits called on nonce-source, but aContext was not nsIDOMHTMLElement or string (was " + typeof(aContext) + ")");
+    return false;
+  },
+
+  toString: function() {
+    return "'nonce-" + this._nonce + "'";
+  },
+
+  clone: function() {
+    let clone = new CSPNonceSource();
+    clone._nonce = this._nonce;
+    return clone;
+  },
+
+  equals: function(that) {
+    return this._nonce === that._nonce;
+  }
+
+};
+
+this.CSPHashSource = function CSPHashSource() {
+  this._algo = undefined;
+  this._hash = undefined;
+}
+
+CSPHashSource.fromString = function(aStr, aCSPRep) {
+  if (!CSPPrefObserver.experimentalEnabled)
+    return null;
+
+  let hashSrcMatch = R_HASHSRC.exec(aStr);
+  let algo = hashSrcMatch[1];
+  let hash = hashSrcMatch[2];
+  if (!algo) {
+    cspError(aCSPRep, "Error parsing hash-source from string: algo was empty");
+    return null;
+  }
+  if (!hash) {
+    cspError(aCSPRep, "Error parsing hash-source from string: hash was empty");
+    return null;
+  }
+
+  let hashSourceObj = new CSPHashSource();
+  hashSourceObj._algo = algo;
+  hashSourceObj._hash = hash;
+  return hashSourceObj;
+};
+
+CSPHashSource.prototype = {
+
+  permits: function(aContext) {
+    if (!CSPPrefObserver.experimentalEnabled) return false;
+
+    let ScriptableUnicodeConverter =
+      Components.Constructor("@mozilla.org/intl/scriptableunicodeconverter",
+                             "nsIScriptableUnicodeConverter");
+    let converter = new ScriptableUnicodeConverter();
+    converter.charset = 'utf8';
+    let utf8InnerHTML = converter.convertToByteArray(aContext);
+
+    let CryptoHash =
+      Components.Constructor("@mozilla.org/security/hash;1",
+                             "nsICryptoHash",
+                             "initWithString");
+    let hash = new CryptoHash(this._algo);
+    hash.update(utf8InnerHTML, utf8InnerHTML.length);
+    // passing true causes a base64-encoded hash to be returned
+    let contentHash = hash.finish(true);
+
+    // The NSS Base64 encoder automatically adds linebreaks "\r\n" every 64
+    // characters. We need to remove these so we can properly validate longer
+    // (SHA-512) base64-encoded hashes
+    contentHash = contentHash.replace('\r\n', '');
+
+    return contentHash === this._hash;
+  },
+
+  toString: function() {
+    return "'" + this._algo + '-' + this._hash + "'";
+  },
+
+  clone: function() {
+    let clone = new CSPHashSource();
+    clone._algo = this._algo;
+    clone._hash = this._hash;
+    return clone;
+  },
+
+  equals: function(that) {
+    return this._algo === that._algo && this._hash === that._hash;
+  }
+
+};
 
 //////////////////////////////////////////////////////////////////////
 /**
  * Class that listens to violation report transmission and logs errors.
  */
 this.CSPViolationReportListener = function CSPViolationReportListener(reportURI) {
   this._reportURI = reportURI;
 }
--- a/content/base/src/contentSecurityPolicy.js
+++ b/content/base/src/contentSecurityPolicy.js
@@ -28,16 +28,18 @@ const CSP_TYPE_WEBSOCKET_SPEC_COMPLIANT 
 const WARN_FLAG = Ci.nsIScriptError.warningFlag;
 const ERROR_FLAG = Ci.nsIScriptError.ERROR_FLAG;
 
 const INLINE_STYLE_VIOLATION_OBSERVER_SUBJECT = 'violated base restriction: Inline Stylesheets will not apply';
 const INLINE_SCRIPT_VIOLATION_OBSERVER_SUBJECT = 'violated base restriction: Inline Scripts will not execute';
 const EVAL_VIOLATION_OBSERVER_SUBJECT = 'violated base restriction: Code will not be created from strings';
 const SCRIPT_NONCE_VIOLATION_OBSERVER_SUBJECT = 'Inline Script had invalid nonce'
 const STYLE_NONCE_VIOLATION_OBSERVER_SUBJECT = 'Inline Style had invalid nonce'
+const SCRIPT_HASH_VIOLATION_OBSERVER_SUBJECT = 'Inline Script had invalid hash';
+const STYLE_HASH_VIOLATION_OBSERVER_SUBJECT = 'Inline Style had invalid hash';
 
 // The cutoff length of content location in creating CSP cache key.
 const CSP_CACHE_URI_CUTOFF_SIZE = 512;
 
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/CSPUtils.jsm");
 
@@ -195,48 +197,81 @@ ContentSecurityPolicy.prototype = {
     if (!CSPPrefObserver.experimentalEnabled)
       return false;
 
     if (!(aContentType == Ci.nsIContentPolicy.TYPE_SCRIPT ||
           aContentType == Ci.nsIContentPolicy.TYPE_STYLESHEET)) {
       CSPdebug("Nonce check requested for an invalid content type (not script or style): " + aContentType);
       return false;
     }
-    let ct = ContentSecurityPolicy._MAPPINGS[aContentType];
+
+    let directive = ContentSecurityPolicy._MAPPINGS[aContentType];
+    let policyAllowsNonce = [ policy.permitsNonce(aNonce, directive)
+                              for (policy of this._policies) ];
 
-    // allow it to execute?
-    let policyAllowsNonce = [ policy.permits(null, ct, aNonce) for (policy of this._policies) ];
-
-    shouldReportViolation.value = policyAllowsNonce.some(function(a) { return !a; });
+    shouldReportViolation.value = this._policies.some(function(policy, i) {
+      // Don't report a violation if the policy didn't use nonce-source
+      return policy._directives[directive]._hasNonceSource && !policyAllowsNonce[i];
+    });
 
     // allow it to execute?  (Do all the policies allow it to execute)?
     return this._policies.every(function(policy, i) {
       return policy._reportOnlyMode || policyAllowsNonce[i];
     });
   },
 
+  getAllowsHash: function(aContent, aContentType, shouldReportViolation) {
+    if (!CSPPrefObserver.experimentalEnabled)
+      return false;
+
+    if (!(aContentType == Ci.nsIContentPolicy.TYPE_SCRIPT ||
+          aContentType == Ci.nsIContentPolicy.TYPE_STYLESHEET)) {
+      CSPdebug("Hash check requested for an invalid content type (not script or style): " + aContentType);
+      return false;
+    }
+
+    let directive = ContentSecurityPolicy._MAPPINGS[aContentType];
+    let policyAllowsHash = [ policy.permitsHash(aContent, directive)
+                             for (policy of this._policies) ];
+
+    shouldReportViolation.value = this._policies.some(function(policy, i) {
+      // Don't report a violation if the policy didn't use hash-source
+      return policy._directives[directive]._hasHashSource && !policyAllowsHash[i];
+    });
+
+    // allow it to execute?  (Do all the policies allow it to execute)?
+    return this._policies.every(function(policy, i) {
+      return policy._reportOnlyMode || policyAllowsHash[i];
+    });
+  },
+
   /**
    * For each policy, log any violation on the Error Console and send a report
    * if a report-uri is present in the policy
    *
    * @param aViolationType
    *     one of the VIOLATION_TYPE_* constants, e.g. inline-script or eval
    * @param aSourceFile
    *     name of the source file containing the violation (if available)
    * @param aContentSample
    *     sample of the violating content (to aid debugging)
    * @param aLineNum
    *     source line number of the violation (if available)
    * @param aNonce
-   *     (optional) If this is a nonce violation, include the nonce should we
-   *     can recheck to determine which policies were violated and send the
+   *     (optional) If this is a nonce violation, include the nonce so we can
+   *     recheck to determine which policies were violated and send the
    *     appropriate reports.
+   * @param aContent
+   *     (optional) If this is a hash violation, include contents of the inline
+   *     resource in the question so we can recheck the hash in order to
+   *     determine which policies were violated and send the appropriate
+   *     reports.
    */
   logViolationDetails:
-  function(aViolationType, aSourceFile, aScriptSample, aLineNum, aNonce) {
+  function(aViolationType, aSourceFile, aScriptSample, aLineNum, aNonce, aContent) {
     for (let policyIndex=0; policyIndex < this._policies.length; policyIndex++) {
       let policy = this._policies[policyIndex];
 
       // call-sites to the eval/inline checks recieve two return values: allows
       // and violates.  Policies that are report-only allow the
       // loads/compilations but violations should still be reported.  Not all
       // policies in this nsIContentSecurityPolicy instance will be violated,
       // which is why we must check again here.
@@ -261,33 +296,51 @@ ContentSecurityPolicy.prototype = {
         if (!policy.allowsEvalInScripts) {
           var violatedDirective = this._buildViolatedDirectiveString('SCRIPT_SRC', policy);
           this._asyncReportViolation('self', null, violatedDirective, policyIndex,
                                     EVAL_VIOLATION_OBSERVER_SUBJECT,
                                     aSourceFile, aScriptSample, aLineNum);
         }
         break;
       case Ci.nsIContentSecurityPolicy.VIOLATION_TYPE_NONCE_SCRIPT:
-        let scriptType = ContentSecurityPolicy._MAPPINGS[Ci.nsIContentPolicy.TYPE_SCRIPT];
-        if (!policy.permits(null, scriptType, aNonce)) {
+        var scriptType = ContentSecurityPolicy._MAPPINGS[Ci.nsIContentPolicy.TYPE_SCRIPT];
+        if (!policy.permitsNonce(aNonce, scriptType)) {
           var violatedDirective = this._buildViolatedDirectiveString('SCRIPT_SRC', policy);
           this._asyncReportViolation('self', null, violatedDirective, policyIndex,
                                      SCRIPT_NONCE_VIOLATION_OBSERVER_SUBJECT,
                                      aSourceFile, aScriptSample, aLineNum);
         }
         break;
       case Ci.nsIContentSecurityPolicy.VIOLATION_TYPE_NONCE_STYLE:
-        let styleType = ContentSecurityPolicy._MAPPINGS[Ci.nsIContentPolicy.TYPE_STYLE];
-        if (!policy.permits(null, styleType, aNonce)) {
+        var styleType = ContentSecurityPolicy._MAPPINGS[Ci.nsIContentPolicy.TYPE_STYLE];
+        if (!policy.permitsNonce(aNonce, styleType)) {
           var violatedDirective = this._buildViolatedDirectiveString('STYLE_SRC', policy);
           this._asyncReportViolation('self', null, violatedDirective, policyIndex,
                                      STYLE_NONCE_VIOLATION_OBSERVER_SUBJECT,
                                      aSourceFile, aScriptSample, aLineNum);
         }
         break;
+      case Ci.nsIContentSecurityPolicy.VIOLATION_TYPE_HASH_SCRIPT:
+        var scriptType = ContentSecurityPolicy._MAPPINGS[Ci.nsIContentPolicy.TYPE_SCRIPT];
+        if (!policy.permitsHash(aContent, scriptType)) {
+          var violatedDirective = this._buildViolatedDirectiveString('SCRIPT_SRC', policy);
+          this._asyncReportViolation('self', null, violatedDirective, policyIndex,
+                                     SCRIPT_HASH_VIOLATION_OBSERVER_SUBJECT,
+                                     aSourceFile, aScriptSample, aLineNum);
+        }
+        break;
+      case Ci.nsIContentSecurityPolicy.VIOLATION_TYPE_HASH_STYLE:
+        var styleType = ContentSecurityPolicy._MAPPINGS[Ci.nsIContentPolicy.TYPE_STYLE];
+        if (!policy.permitsHash(aContent, styleType)) {
+          var violatedDirective = this._buildViolatedDirectiveString('STYLE_SRC', policy);
+          this._asyncReportViolation('self', null, violatedDirective, policyIndex,
+                                     STYLE_HASH_VIOLATION_OBSERVER_SUBJECT,
+                                     aSourceFile, aScriptSample, aLineNum);
+        }
+        break;
       }
     }
   },
 
   /**
    * Given an nsIHttpChannel, fill out the appropriate data.
    */
   scanRequestData:
@@ -674,19 +727,20 @@ ContentSecurityPolicy.prototype = {
     var cspContext;
 
     let cp = Ci.nsIContentPolicy;
 
     // Infer if this is a preload for elements that use nonce-source. Since,
     // for preloads, aContext is the document and not the element associated
     // with the resource, we cannot determine the nonce. See Bug 612921 and
     // Bug 855326.
-    var possiblePreloadNonceConflict =
-      (aContentType == cp.TYPE_SCRIPT || aContentType == cp.TYPE_STYLESHEET) &&
-      aContext instanceof Ci.nsIDOMHTMLDocument;
+    let nonceSourceValid = aContentType == cp.TYPE_SCRIPT ||
+                           aContentType == cp.TYPE_STYLESHEET;
+    var possiblePreloadNonceConflict = nonceSourceValid &&
+                                       aContext instanceof Ci.nsIDOMHTMLDocument;
 
     // iterate through all the _policies and send reports where a policy is
     // violated.  After the check, determine the overall effect (blocked or
     // loaded?) and cache it.
     let policyAllowsLoadArray = [];
     for (let policyIndex=0; policyIndex < this._policies.length; policyIndex++) {
       let policy = this._policies[policyIndex];
 
@@ -709,30 +763,37 @@ ContentSecurityPolicy.prototype = {
       CSPdebug("shouldLoad cspContext = " + cspContext);
 #endif
 
       // if the mapping is null, there's no policy, let it through.
       if (!cspContext) {
         return Ci.nsIContentPolicy.ACCEPT;
       }
 
-      // otherwise, honor the translation
-      // var source = aContentLocation.scheme + "://" + aContentLocation.hostPort;
-      let context = CSPPrefObserver.experimentalEnabled ? aContext : null;
-      var res = policy.permits(aContentLocation, cspContext, context) ?
-                cp.ACCEPT : cp.REJECT_SERVER;
+      // check if location is permitted
+      let permitted = policy.permits(aContentLocation, cspContext);
+
+      // check any valid content type for nonce if location is not permitted
+      if (!permitted && nonceSourceValid &&
+          aContext instanceof Ci.nsIDOMHTMLElement &&
+          aContext.hasAttribute('nonce')) {
+        permitted = policy.permitsNonce(aContext.getAttribute('nonce'),
+                                        cspContext);
+      }
+
       // record whether the thing should be blocked or just reported.
-      policyAllowsLoadArray.push(res == cp.ACCEPT || policy._reportOnlyMode);
+      policyAllowsLoadArray.push(permitted || policy._reportOnlyMode);
+      let res = permitted ? cp.ACCEPT : cp.REJECT_SERVER;
 
       // frame-ancestors is taken care of early on (as this document is loaded)
 
       // If the result is *NOT* ACCEPT, then send report
       // Do not send report if this is a nonce-source preload - the decision may
       // be wrong and will incorrectly fail the unit tests.
-      if (res != Ci.nsIContentPolicy.ACCEPT && !possiblePreloadNonceConflict) {
+      if (res != cp.ACCEPT && !possiblePreloadNonceConflict) {
         CSPdebug("blocking request for " + aContentLocation.asciiSpec);
         try {
           let directive = "unknown directive",
               violatedPolicy = "unknown policy";
 
           // The policy might not explicitly declare each source directive (so
           // the cspContext may be implicit).  If so, we have to report
           // violations as appropriate: specific or the default-src directive.
@@ -743,18 +804,20 @@ ContentSecurityPolicy.prototype = {
             directive = policy._directives["default-src"];
             violatedPolicy = "default-src " + directive.toString();
           } else {
             violatedPolicy = "unknown directive";
             CSPdebug('ERROR in blocking content: ' +
                     'CSP is not sure which part of the policy caused this block');
           }
 
-          this._asyncReportViolation(aContentLocation, aOriginalUri,
-                                     violatedPolicy, policyIndex);
+          this._asyncReportViolation(aContentLocation,
+                                     aOriginalUri,
+                                     violatedPolicy,
+                                     policyIndex);
         } catch(e) {
           CSPdebug('---------------- ERROR: ' + e);
         }
       }
     } // end for-each loop over policies
 
     // the ultimate decision is based on whether any policies want to reject
     // the load.  The array keeps track of whether the policies allowed the
--- a/content/base/src/nsScriptLoader.cpp
+++ b/content/base/src/nsScriptLoader.cpp
@@ -419,64 +419,105 @@ CSPAllowsInlineScript(nsIScriptElement *
   nsresult rv = aDocument->NodePrincipal()->GetCsp(getter_AddRefs(csp));
   NS_ENSURE_SUCCESS(rv, false);
 
   if (!csp) {
     // no CSP --> allow
     return true;
   }
 
-  bool reportViolation = false;
+  // An inline script can be allowed because all inline scripts are allowed,
+  // or else because it is whitelisted by a nonce-source or hash-source. This
+  // is a logical OR between whitelisting methods, so the allowInlineScript
+  // outparam can be reused for each check as long as we stop checking as soon
+  // as it is set to true. This also optimizes performance by avoiding the
+  // overhead of unnecessary checks.
   bool allowInlineScript = true;
-  rv = csp->GetAllowsInlineScript(&reportViolation, &allowInlineScript);
+  nsAutoTArray<unsigned short, 3> violations;
+
+  bool reportInlineViolation = false;
+  rv = csp->GetAllowsInlineScript(&reportInlineViolation, &allowInlineScript);
   NS_ENSURE_SUCCESS(rv, false);
+  if (reportInlineViolation) {
+    violations.AppendElement(static_cast<unsigned short>(
+          nsIContentSecurityPolicy::VIOLATION_TYPE_INLINE_SCRIPT));
+  }
 
-  bool foundNonce = false;
   nsAutoString nonce;
   if (!allowInlineScript) {
     nsCOMPtr<nsIContent> scriptContent = do_QueryInterface(aElement);
-    foundNonce = scriptContent->GetAttr(kNameSpaceID_None, nsGkAtoms::nonce, nonce);
+    bool foundNonce = scriptContent->GetAttr(kNameSpaceID_None,
+                                             nsGkAtoms::nonce, nonce);
     if (foundNonce) {
-      // We can overwrite the outparams from GetAllowsInlineScript because
-      // if the nonce is correct, then we don't want to report the original
-      // inline violation (it has been whitelisted by the nonce), and if
-      // the nonce is incorrect, then we want to return just the specific
-      // "nonce violation" rather than both a "nonce violation" and
-      // a generic "inline violation".
+      bool reportNonceViolation;
       rv = csp->GetAllowsNonce(nonce, nsIContentPolicy::TYPE_SCRIPT,
-                               &reportViolation, &allowInlineScript);
+                               &reportNonceViolation, &allowInlineScript);
       NS_ENSURE_SUCCESS(rv, false);
+      if (reportNonceViolation) {
+        violations.AppendElement(static_cast<unsigned short>(
+              nsIContentSecurityPolicy::VIOLATION_TYPE_NONCE_SCRIPT));
+      }
     }
   }
 
-  if (reportViolation) {
+  if (!allowInlineScript) {
+    bool reportHashViolation;
+    nsAutoString scriptText;
+    aElement->GetScriptText(scriptText);
+    rv = csp->GetAllowsHash(scriptText, nsIContentPolicy::TYPE_SCRIPT,
+                            &reportHashViolation, &allowInlineScript);
+    NS_ENSURE_SUCCESS(rv, false);
+    if (reportHashViolation) {
+      violations.AppendElement(static_cast<unsigned short>(
+            nsIContentSecurityPolicy::VIOLATION_TYPE_HASH_SCRIPT));
+    }
+  }
+
+  // What violation(s) should be reported?
+  //
+  // 1. If the script tag has a nonce attribute, and the nonce does not match
+  // the policy, report VIOLATION_TYPE_NONCE_SCRIPT.
+  // 2. If the policy has at least one hash-source, and the hashed contents of
+  // the script tag did not match any of them, report VIOLATION_TYPE_HASH_SCRIPT
+  // 3. Otherwise, report VIOLATION_TYPE_INLINE_SCRIPT if appropriate.
+  //
+  // 1 and 2 may occur together, 3 should only occur by itself. Naturally,
+  // every VIOLATION_TYPE_NONCE_SCRIPT and VIOLATION_TYPE_HASH_SCRIPT are also
+  // VIOLATION_TYPE_INLINE_SCRIPT, but reporting the
+  // VIOLATION_TYPE_INLINE_SCRIPT is redundant and does not help the developer.
+  if (!violations.IsEmpty()) {
+    MOZ_ASSERT(violations[0] == nsIContentSecurityPolicy::VIOLATION_TYPE_INLINE_SCRIPT,
+               "How did we get any violations without an initial inline script violation?");
     // gather information to log with violation report
     nsIURI* uri = aDocument->GetDocumentURI();
     nsAutoCString asciiSpec;
     uri->GetAsciiSpec(asciiSpec);
     nsAutoString scriptText;
     aElement->GetScriptText(scriptText);
+    nsAutoString scriptSample(scriptText);
 
     // cap the length of the script sample at 40 chars
-    if (scriptText.Length() > 40) {
-      scriptText.Truncate(40);
-      scriptText.AppendLiteral("...");
+    if (scriptSample.Length() > 40) {
+      scriptSample.Truncate(40);
+      scriptSample.AppendLiteral("...");
     }
 
-    // The type of violation to report is determined by whether there was
-    // a nonce present.
-    unsigned short violationType = foundNonce ?
-      nsIContentSecurityPolicy::VIOLATION_TYPE_NONCE_SCRIPT :
-      nsIContentSecurityPolicy::VIOLATION_TYPE_INLINE_SCRIPT;
-    csp->LogViolationDetails(violationType, NS_ConvertUTF8toUTF16(asciiSpec),
-                             scriptText, aElement->GetScriptLineNumber(), nonce);
+    for (uint32_t i = 0; i < violations.Length(); i++) {
+      // Skip reporting the redundant inline script violation if there are
+      // other (nonce and/or hash violations) as well.
+      if (i > 0 || violations.Length() == 1) {
+        csp->LogViolationDetails(violations[i], NS_ConvertUTF8toUTF16(asciiSpec),
+                                 scriptSample, aElement->GetScriptLineNumber(),
+                                 nonce, scriptText);
+      }
+    }
   }
 
   if (!allowInlineScript) {
-    NS_ASSERTION(reportViolation,
+    NS_ASSERTION(!violations.IsEmpty(),
         "CSP blocked inline script but is not reporting a violation");
    return false;
   }
   return true;
 }
 
 bool
 nsScriptLoader::ProcessScriptElement(nsIScriptElement *aElement)
new file mode 100644
--- /dev/null
+++ b/content/base/test/csp/file_hash_source.html
@@ -0,0 +1,65 @@
+<!doctype html>
+<html>
+  <body>
+    <!-- inline scripts -->
+    <p id="inline-script-valid-hash">blocked</p>
+    <p id="inline-script-invalid-hash">blocked</p>
+    <p id="inline-script-invalid-hash-valid-nonce">blocked</p>
+    <p id="inline-script-valid-hash-invalid-nonce">blocked</p>
+    <p id="inline-script-invalid-hash-invalid-nonce">blocked</p>
+    <p id="inline-script-valid-sha512-hash">blocked</p>
+    <p id="inline-script-valid-sha384-hash">blocked</p>
+    <p id="inline-script-valid-sha1-hash">blocked</p>
+    <p id="inline-script-valid-md5-hash">blocked</p>
+
+    <!-- 'sha256-siVR8vAcqP06h2ppeNwqgjr0yZ6yned4X2VF84j4GmI=' (in policy) -->
+    <script>document.getElementById("inline-script-valid-hash").innerHTML = "allowed";</script>
+    <!-- 'sha256-cYPTF2pm0QeyDtbmJ3+xi00o2Rxrw7vphBoHgOg9EnQ=' (not in policy) -->
+    <script>document.getElementById("inline-script-invalid-hash").innerHTML = "allowed";</script>
+    <!-- 'sha256-SKtBKyfeMjBpOujES0etR9t/cklbouJu/3T4PXnjbIo=' (not in policy) -->
+    <script nonce="jPRxvuRHbiQnCWVuoCMAvQ==">document.getElementById("inline-script-invalid-hash-valid-nonce").innerHTML = "allowed";</script>
+    <!-- 'sha256-z7rzCkbOJqi08lga3CVQ3b+3948ZbJWaSxsBs8zPliE=' -->
+    <script nonce="foobar">document.getElementById("inline-script-valid-hash-invalid-nonce").innerHTML = "allowed";</script>
+    <!-- 'sha256-E5TX2PmYZ4YQOK/F3XR1wFcvFjbO7QHMmxHTT/18LbE=' (not in policy) -->
+    <script nonce="foobar">document.getElementById("inline-script-invalid-hash-invalid-nonce").innerHTML = "allowed";</script>
+    <!-- 'sha512-tMLuv22jJ5RHkvLNlv0otvA2fgw6PF16HKu6wy0ZDQ3M7UKzoygs1uxIMSfjMttgWrB5WRvIr35zrTZppMYBVw==' (in policy) -->
+    <script>document.getElementById("inline-script-valid-sha512-hash").innerHTML = "allowed";</script>
+    <!-- 'sha384-XjAD+FxZfipkxna4id1JrR2QP6OYUZfAxpn9+yHOmT1VSLVa9SQR/dz7CEb7jw7w' (in policy) -->
+    <script>document.getElementById("inline-script-valid-sha384-hash").innerHTML = "allowed";</script>
+    <!-- 'sha1-LHErkMxKGcSpa/znpzmKYkKnI30=' (in policy) -->
+    <script>document.getElementById("inline-script-valid-sha1-hash").innerHTML = "allowed";</script>
+    <!-- 'md5-/m4wX3YU+IHs158KwKOBWg==' (in policy) -->
+    <script>document.getElementById("inline-script-valid-md5-hash").innerHTML = "allowed";</script>
+
+    <!-- inline styles -->
+    <p id="inline-style-valid-hash"></p>
+    <p id="inline-style-invalid-hash"></p>
+    <p id="inline-style-invalid-hash-valid-nonce"></p>
+    <p id="inline-style-valid-hash-invalid-nonce"></p>
+    <p id="inline-style-invalid-hash-invalid-nonce"></p>
+    <p id="inline-style-valid-sha512-hash"></p>
+    <p id="inline-style-valid-sha384-hash"></p>
+    <p id="inline-style-valid-sha1-hash"></p>
+    <p id="inline-style-valid-md5-hash"></p>
+
+    <!-- 'sha256-UpNH6x+Ux99QTW1fJikQsVbBERJruIC98et0YDVKKHQ=' (in policy) -->
+    <style>p#inline-style-valid-hash { color: green; }</style>
+    <!-- 'sha256-+TYxTx+bsfTDdivWLZUwScEYyxuv6lknMbNjrgGBRZo=' (not in policy) -->
+    <style>p#inline-style-invalid-hash { color: red; }</style>
+    <!-- 'sha256-U+9UPC/CFzz3QuOrl5q3KCVNngOYWuIkE2jK6Ir0Mbs=' (not in policy) -->
+    <style nonce="ftL2UbGHlSEaZTLWMwtA5Q==">p#inline-style-invalid-hash-valid-nonce { color: green; }</style>
+    <!-- 'sha256-0IPbWW5IDJ/juvETq60oTnhC+XzOqdYp5/UBsBKCaOY=' (in policy) -->
+    <style nonce="foobar">p#inline-style-valid-hash-invalid-nonce { color: green; }</style>
+    <!-- 'sha256-KaHZgPd4nC4S8BVLT/9WjzdPDtunGWojR83C2whbd50=' (not in policy) -->
+    <style nonce="foobar">p#inline-style-invalid-hash-invalid-nonce { color: red; }</style>
+    <!-- 'sha512-EpcDbSuvFv0HIyKtU5tQMN7UtBMeEbljz1dWPfy7PNCa1RYdHKwdJWT1tie41evq/ZUL1rzadSVdEzq3jl6Twg==' (in policy) -->
+    <style>p#inline-style-valid-sha512-hash { color: green; }</style>
+    <!-- 'sha384-c5W8ON4WyeA2zEOGdrOGhRmRYI8+2UzUUmhGQFjUFP6yiPZx9FGEV3UOiQ+tIshF' (in policy) -->
+    <style>p#inline-style-valid-sha384-hash { color: green; }</style>
+    <!-- 'sha1-T/+b4sxCIiJxDr6XS9dAEyHKt2M=' (in policy) -->
+    <style>p#inline-style-valid-sha1-hash { color: red; }</style>
+    <!-- 'md5-oNrgrtzOZduwDYYi1yo12g==' (in policy) -->
+    <style>p#inline-style-valid-md5-hash { color: red; }</style>
+
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/content/base/test/csp/file_hash_source.html^headers^
@@ -0,0 +1,2 @@
+Content-Security-Policy: script-src 'sha256-siVR8vAcqP06h2ppeNwqgjr0yZ6yned4X2VF84j4GmI=' 'nonce-jPRxvuRHbiQnCWVuoCMAvQ==' 'sha256-z7rzCkbOJqi08lga3CVQ3b+3948ZbJWaSxsBs8zPliE=' 'sha512-tMLuv22jJ5RHkvLNlv0otvA2fgw6PF16HKu6wy0ZDQ3M7UKzoygs1uxIMSfjMttgWrB5WRvIr35zrTZppMYBVw==' 'sha384-XjAD+FxZfipkxna4id1JrR2QP6OYUZfAxpn9+yHOmT1VSLVa9SQR/dz7CEb7jw7w' 'sha1-LHErkMxKGcSpa/znpzmKYkKnI30=' 'md5-/m4wX3YU+IHs158KwKOBWg=='; style-src 'sha256-UpNH6x+Ux99QTW1fJikQsVbBERJruIC98et0YDVKKHQ=' 'nonce-ftL2UbGHlSEaZTLWMwtA5Q==' 'sha256-0IPbWW5IDJ/juvETq60oTnhC+XzOqdYp5/UBsBKCaOY=' 'sha512-EpcDbSuvFv0HIyKtU5tQMN7UtBMeEbljz1dWPfy7PNCa1RYdHKwdJWT1tie41evq/ZUL1rzadSVdEzq3jl6Twg==' 'sha384-c5W8ON4WyeA2zEOGdrOGhRmRYI8+2UzUUmhGQFjUFP6yiPZx9FGEV3UOiQ+tIshF' 'sha1-T/+b4sxCIiJxDr6XS9dAEyHKt2M=' 'md5-oNrgrtzOZduwDYYi1yo12g==';
+Cache-Control: no-cache
--- a/content/base/test/csp/mochitest.ini
+++ b/content/base/test/csp/mochitest.ini
@@ -97,16 +97,18 @@ support-files =
   file_policyuri_regression_from_multipolicy.html
   file_policyuri_regression_from_multipolicy.html^headers^
   file_policyuri_regression_from_multipolicy_policy
   file_nonce_source.html
   file_nonce_source.html^headers^
   file_CSP_bug941404.html
   file_CSP_bug941404_xhr.html
   file_CSP_bug941404_xhr.html^headers^
+  file_hash_source.html
+  file_hash_source.html^headers^
 
 [test_CSP.html]
 [test_CSP_bug663567.html]
 [test_CSP_bug802872.html]
 [test_CSP_bug885433.html]
 [test_CSP_bug888172.html]
 [test_CSP_bug916446.html]
 [test_CSP_evalscript.html]
@@ -118,8 +120,9 @@ support-files =
 [test_bug836922_npolicies.html]
 [test_bug886164.html]
 [test_csp_redirects.html]
 [test_CSP_bug910139.html]
 [test_CSP_bug909029.html]
 [test_policyuri_regression_from_multipolicy.html]
 [test_nonce_source.html]
 [test_CSP_bug941404.html]
+[test_hash_source.html]
new file mode 100644
--- /dev/null
+++ b/content/base/test/csp/test_hash_source.html
@@ -0,0 +1,140 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <title>Test CSP 1.1 hash-source for inline scripts and styles</title>
+  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <script type="application/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<p id="display"></p>
+<div id="content" style="visibility:hidden">
+  <iframe style="width:100%;" id='cspframe'></iframe>
+</div>
+<script class="testbody" type="text/javascript">
+
+function cleanup() {
+  // finish the tests
+  SimpleTest.finish();
+}
+
+function checkInline () {
+  var cspframe = document.getElementById('cspframe').contentDocument;
+
+  var inlineScriptTests = {
+    'inline-script-valid-hash': {
+      shouldBe: 'allowed',
+      message:  'Inline script with valid hash should be allowed'
+    },
+    'inline-script-invalid-hash': {
+      shouldBe: 'blocked',
+      message: 'Inline script with invalid hash should be blocked'
+    },
+    'inline-script-invalid-hash-valid-nonce': {
+      shouldBe: 'allowed',
+      message: 'Inline script with invalid hash and valid nonce should be allowed'
+    },
+    'inline-script-valid-hash-invalid-nonce': {
+      shouldBe: 'allowed',
+      message: 'Inline script with valid hash and invalid nonce should be allowed'
+    },
+    'inline-script-invalid-hash-invalid-nonce': {
+      shouldBe: 'blocked',
+      message: 'Inline script with invalid hash and invalid nonce should be blocked'
+    },
+    'inline-script-valid-sha512-hash': {
+      shouldBe: 'allowed',
+      message: 'Inline script with a valid sha512 hash should be allowed'
+    },
+    'inline-script-valid-sha384-hash': {
+      shouldBe: 'allowed',
+      message: 'Inline script with a valid sha384 hash should be allowed'
+    },
+    'inline-script-valid-sha1-hash': {
+      shouldBe: 'blocked',
+      message: 'Inline script with a valid sha1 hash should be blocked, because sha1 is not a valid hash function'
+    },
+    'inline-script-valid-md5-hash': {
+      shouldBe: 'blocked',
+      message: 'Inline script with a valid md5 hash should be blocked, because md5 is not a valid hash function'
+    }
+  }
+
+  for (testId in inlineScriptTests) {
+    var test = inlineScriptTests[testId];
+    is(cspframe.getElementById(testId).innerHTML, test.shouldBe, test.message);
+  }
+
+  // Inline style tries to change an element's color to green. If blocked, the
+  // element's color will be the default black.
+  var green = "rgb(0, 128, 0)";
+  var black = "rgb(0, 0, 0)";
+
+  var getElementColorById = function (id) {
+    return window.getComputedStyle(cspframe.getElementById(id), null).color;
+  };
+
+  var inlineStyleTests = {
+    'inline-style-valid-hash': {
+      shouldBe: green,
+      message: 'Inline style with valid hash should be allowed'
+    },
+    'inline-style-invalid-hash': {
+      shouldBe: black,
+      message: 'Inline style with invalid hash should be blocked'
+    },
+    'inline-style-invalid-hash-valid-nonce': {
+      shouldBe: green,
+      message: 'Inline style with invalid hash and valid nonce should be allowed'
+    },
+    'inline-style-valid-hash-invalid-nonce': {
+      shouldBe: green,
+      message: 'Inline style with valid hash and invalid nonce should be allowed'
+    },
+    'inline-style-invalid-hash-invalid-nonce' : {
+      shouldBe: black,
+      message: 'Inline style with invalid hash and invalid nonce should be blocked'
+    },
+    'inline-style-valid-sha512-hash': {
+      shouldBe: green,
+      message: 'Inline style with a valid sha512 hash should be allowed'
+    },
+    'inline-style-valid-sha384-hash': {
+      shouldBe: green,
+      message: 'Inline style with a valid sha384 hash should be allowed'
+    },
+    'inline-style-valid-sha1-hash': {
+      shouldBe: black,
+      message: 'Inline style with a valid sha1 hash should be blocked, because sha1 is not a valid hash function'
+    },
+    'inline-style-valid-md5-hash': {
+      shouldBe: black,
+      message: 'Inline style with a valid md5 hash should be blocked, because md5 is not a valid hash function'
+    }
+  }
+
+  for (testId in inlineStyleTests) {
+    var test = inlineStyleTests[testId];
+    is(getElementColorById(testId), test.shouldBe, test.message);
+  }
+
+  cleanup();
+}
+
+//////////////////////////////////////////////////////////////////////
+// set up and go
+SimpleTest.waitForExplicitFinish();
+
+SpecialPowers.pushPrefEnv(
+  {'set':[["security.csp.speccompliant", true],
+          ["security.csp.experimentalEnabled", true]]},
+  function() {
+    // save this for last so that our listeners are registered.
+    // ... this loads the testbed of good and bad requests.
+    document.getElementById('cspframe').src = 'file_hash_source.html';
+    document.getElementById('cspframe').addEventListener('load', checkInline, false);
+  });
+</script>
+</pre>
+</body>
+</html>
--- a/content/events/src/nsEventListenerManager.cpp
+++ b/content/events/src/nsEventListenerManager.cpp
@@ -695,16 +695,17 @@ nsEventListenerManager::SetEventHandler(
         scriptSample.Assign(attr);
         scriptSample.AppendLiteral(" attribute on ");
         scriptSample.Append(tagName);
         scriptSample.AppendLiteral(" element");
         csp->LogViolationDetails(nsIContentSecurityPolicy::VIOLATION_TYPE_INLINE_SCRIPT,
                                  NS_ConvertUTF8toUTF16(asciiSpec),
                                  scriptSample,
                                  0,
+                                 EmptyString(),
                                  EmptyString());
       }
 
       // return early if CSP wants us to block inline scripts
       if (!inlineOK) {
         return NS_OK;
       }
     }
--- a/dom/base/nsJSTimeoutHandler.cpp
+++ b/dom/base/nsJSTimeoutHandler.cpp
@@ -176,17 +176,18 @@ CheckCSPForEval(JSContext* aCx, nsGlobal
     nsAutoString fileNameString;
     if (nsJSUtils::GetCallingLocation(aCx, &fileName, &lineNum)) {
       AppendUTF8toUTF16(fileName, fileNameString);
     } else {
       fileNameString.AssignLiteral("unknown");
     }
 
     csp->LogViolationDetails(nsIContentSecurityPolicy::VIOLATION_TYPE_EVAL,
-                             fileNameString, scriptSample, lineNum, EmptyString());
+                             fileNameString, scriptSample, lineNum,
+                             EmptyString(), EmptyString());
   }
 
   return allowsEval;
 }
 
 nsJSScriptTimeoutHandler::nsJSScriptTimeoutHandler() :
   mLineNo(0)
 {
--- a/dom/src/jsurl/nsJSProtocolHandler.cpp
+++ b/dom/src/jsurl/nsJSProtocolHandler.cpp
@@ -175,16 +175,17 @@ nsresult nsJSThunk::EvaluateScript(nsICh
             nsCOMPtr<nsIURI> uri;
             principal->GetURI(getter_AddRefs(uri));
             nsAutoCString asciiSpec;
             uri->GetAsciiSpec(asciiSpec);
             csp->LogViolationDetails(nsIContentSecurityPolicy::VIOLATION_TYPE_INLINE_SCRIPT,
                                      NS_ConvertUTF8toUTF16(asciiSpec),
                                      NS_ConvertUTF8toUTF16(mURL),
                                      0,
+                                     EmptyString(),
                                      EmptyString());
         }
 
         //return early if inline scripts are not allowed
         if (!allowsInline) {
           return NS_ERROR_DOM_RETVAL_UNDEFINED;
         }
     }
--- a/dom/workers/RuntimeService.cpp
+++ b/dom/workers/RuntimeService.cpp
@@ -2518,17 +2518,17 @@ LogViolationDetailsRunnable::Run()
 
   nsIContentSecurityPolicy* csp = mWorkerPrivate->GetCSP();
   if (csp) {
     NS_NAMED_LITERAL_STRING(scriptSample,
         "Call to eval() or related function blocked by CSP.");
     if (mWorkerPrivate->GetReportCSPViolations()) {
       csp->LogViolationDetails(nsIContentSecurityPolicy::VIOLATION_TYPE_EVAL,
                                mFileName, scriptSample, mLineNum,
-                               EmptyString());
+                               EmptyString(), EmptyString());
     }
   }
 
   nsRefPtr<MainThreadStopSyncLoopRunnable> response =
     new MainThreadStopSyncLoopRunnable(mWorkerPrivate, mSyncLoopTarget.forget(),
                                        true);
   MOZ_ALWAYS_TRUE(response->Dispatch(nullptr));
 
--- a/layout/style/nsStyleUtil.cpp
+++ b/layout/style/nsStyleUtil.cpp
@@ -463,72 +463,111 @@ nsStyleUtil::CSPAllowsInlineStyle(nsICon
     return false;
   }
 
   if (!csp) {
     // No CSP --> the style is allowed
     return true;
   }
 
-  bool reportViolation;
+  // An inline style can be allowed because all inline styles are allowed,
+  // or else because it is whitelisted by a nonce-source or hash-source. This
+  // is a logical OR between whitelisting methods, so the allowInlineStyle
+  // outparam can be reused for each check as long as we stop checking as soon
+  // as it is set to true. This also optimizes performance by avoiding the
+  // overhead of unnecessary checks.
   bool allowInlineStyle = true;
-  rv = csp->GetAllowsInlineStyle(&reportViolation, &allowInlineStyle);
+  nsAutoTArray<unsigned short, 3> violations;
+
+  bool reportInlineViolation;
+  rv = csp->GetAllowsInlineStyle(&reportInlineViolation, &allowInlineStyle);
   if (NS_FAILED(rv)) {
     if (aRv)
       *aRv = rv;
     return false;
   }
+  if (reportInlineViolation) {
+    violations.AppendElement(static_cast<unsigned short>(
+          nsIContentSecurityPolicy::VIOLATION_TYPE_INLINE_STYLE));
+  }
 
-  bool foundNonce = false;
   nsAutoString nonce;
-  // If inline styles are allowed ('unsafe-inline'), skip the (irrelevant)
-  // nonce check
   if (!allowInlineStyle) {
     // We can only find a nonce if aContent is provided
-    foundNonce = !!aContent &&
+    bool foundNonce = !!aContent &&
       aContent->GetAttr(kNameSpaceID_None, nsGkAtoms::nonce, nonce);
     if (foundNonce) {
-      // We can overwrite the outparams from GetAllowsInlineStyle because
-      // if the nonce is correct, then we don't want to report the original
-      // inline violation (it has been whitelisted by the nonce), and if
-      // the nonce is incorrect, then we want to return just the specific
-      // "nonce violation" rather than both a "nonce violation" and
-      // a generic "inline violation".
+      bool reportNonceViolation;
       rv = csp->GetAllowsNonce(nonce, nsIContentPolicy::TYPE_STYLESHEET,
-                               &reportViolation, &allowInlineStyle);
+                               &reportNonceViolation, &allowInlineStyle);
       if (NS_FAILED(rv)) {
         if (aRv)
           *aRv = rv;
         return false;
       }
+
+      if (reportNonceViolation) {
+        violations.AppendElement(static_cast<unsigned short>(
+              nsIContentSecurityPolicy::VIOLATION_TYPE_NONCE_STYLE));
+      }
     }
   }
 
-  if (reportViolation) {
+  if (!allowInlineStyle) {
+    bool reportHashViolation;
+    rv = csp->GetAllowsHash(aStyleText, nsIContentPolicy::TYPE_STYLESHEET,
+                            &reportHashViolation, &allowInlineStyle);
+    if (NS_FAILED(rv)) {
+      if (aRv)
+        *aRv = rv;
+      return false;
+    }
+    if (reportHashViolation) {
+      violations.AppendElement(static_cast<unsigned short>(
+            nsIContentSecurityPolicy::VIOLATION_TYPE_HASH_STYLE));
+    }
+  }
+
+  // What violation(s) should be reported?
+  //
+  // 1. If the style tag has a nonce attribute, and the nonce does not match
+  // the policy, report VIOLATION_TYPE_NONCE_STYLE.
+  // 2. If the policy has at least one hash-source, and the hashed contents of
+  // the style tag did not match any of them, report VIOLATION_TYPE_HASH_STYLE
+  // 3. Otherwise, report VIOLATION_TYPE_INLINE_STYLE if appropriate.
+  //
+  // 1 and 2 may occur together, 3 should only occur by itself. Naturally,
+  // every VIOLATION_TYPE_NONCE_STYLE and VIOLATION_TYPE_HASH_STYLE are also
+  // VIOLATION_TYPE_INLINE_STYLE, but reporting the
+  // VIOLATION_TYPE_INLINE_STYLE is redundant and does not help the developer.
+  if (!violations.IsEmpty()) {
+    MOZ_ASSERT(violations[0] == nsIContentSecurityPolicy::VIOLATION_TYPE_INLINE_STYLE,
+               "How did we get any violations without an initial inline style violation?");
     // This inline style is not allowed by CSP, so report the violation
     nsAutoCString asciiSpec;
     aSourceURI->GetAsciiSpec(asciiSpec);
-    nsAutoString styleText(aStyleText);
+    nsAutoString styleSample(aStyleText);
 
     // cap the length of the style sample at 40 chars.
-    if (styleText.Length() > 40) {
-      styleText.Truncate(40);
-      styleText.AppendLiteral("...");
+    if (styleSample.Length() > 40) {
+      styleSample.Truncate(40);
+      styleSample.AppendLiteral("...");
     }
 
-    // The type of violation to report is determined by whether there was
-    // a nonce present.
-    unsigned short violationType = foundNonce ?
-      nsIContentSecurityPolicy::VIOLATION_TYPE_NONCE_STYLE :
-      nsIContentSecurityPolicy::VIOLATION_TYPE_INLINE_STYLE;
-    csp->LogViolationDetails(violationType, NS_ConvertUTF8toUTF16(asciiSpec),
-                             styleText, aLineNumber, nonce);
+    for (uint32_t i = 0; i < violations.Length(); i++) {
+      // Skip reporting the redundant inline style violation if there are
+      // other (nonce and/or hash violations) as well.
+      if (i > 0 || violations.Length() == 1) {
+        csp->LogViolationDetails(violations[i], NS_ConvertUTF8toUTF16(asciiSpec),
+                                 styleSample, aLineNumber, nonce, aStyleText);
+      }
+    }
   }
 
   if (!allowInlineStyle) {
-    NS_ASSERTION(reportViolation,
+    NS_ASSERTION(!violations.IsEmpty(),
         "CSP blocked inline style but is not reporting a violation");
     // The inline style should be blocked.
     return false;
   }
   // CSP allows inline styles.
   return true;
 }
--- a/security/manager/ssl/src/nsCrypto.cpp
+++ b/security/manager/ssl/src/nsCrypto.cpp
@@ -1920,16 +1920,17 @@ nsCrypto::GenerateCRMFRequest(JSContext*
 
     const char *fileName;
     uint32_t lineNum;
     nsJSUtils::GetCallingLocation(aContext, &fileName, &lineNum);
     csp->LogViolationDetails(nsIContentSecurityPolicy::VIOLATION_TYPE_EVAL,
                              NS_ConvertASCIItoUTF16(fileName),
                              scriptSample,
                              lineNum,
+                             EmptyString(),
                              EmptyString());
   }
 
   if (!evalAllowed) {
     NS_WARNING("eval() not allowed by Content Security Policy");
     aRv.Throw(NS_ERROR_FAILURE);
     return nullptr;
   }
--- a/testing/mochitest/b2g.json
+++ b/testing/mochitest/b2g.json
@@ -445,10 +445,11 @@
     "layout/style/test/test_visited_lying.html" : "bug 870262, :visited support",
     "layout/style/test/test_visited_pref.html" : "bug 870262, :visited support",
     "layout/style/test/test_visited_reftests.html":"bug 870262, :visited support",
 
     "Harness_sanity/test_sanityEventUtils.html": "bug 688052",
     "Harness_sanity/test_sanitySimpletest.html": "bug 688052",
 
     "content/base/test/csp/test_CSP_evalscript_getCRMFRequest.html":"no window.crypto support in multiprocess"
+    "content/base/test/csp/test_hash_source.html":"can't use nsICryptoHash in CSPUtils.jsm (child process)",
   }
 }