Bug 1129999 - Implement CSP devtool using GCLI; CSP to JSON (r=sstamm,bholley)
authorChristoph Kerschbaumer <mozilla@christophkerschbaumer.com>
Thu, 21 May 2015 11:16:04 -0700
changeset 266202 4d157cf63a5e0319d19ed45237c5df8e907d3463
parent 266201 5924130fdaa5708fd0efc49728e04feffb876407
child 266203 c6983ac381939949add7c474d4fdd1d3e9f95124
push id2231
push usermichael.l.comella@gmail.com
push dateFri, 22 May 2015 20:04:59 +0000
reviewerssstamm, bholley
bugs1129999
milestone41.0a1
Bug 1129999 - Implement CSP devtool using GCLI; CSP to JSON (r=sstamm,bholley)
caps/BasePrincipal.cpp
caps/BasePrincipal.h
caps/nsIPrincipal.idl
dom/interfaces/security/nsIContentSecurityPolicy.idl
dom/security/nsCSPContext.cpp
dom/security/nsCSPUtils.cpp
dom/security/nsCSPUtils.h
dom/webidl/CSPDictionaries.webidl
dom/webidl/moz.build
--- a/caps/BasePrincipal.cpp
+++ b/caps/BasePrincipal.cpp
@@ -1,24 +1,26 @@
 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim: set ts=2 sw=2 et tw=80: */
 /* 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/. */
 
 #include "mozilla/BasePrincipal.h"
 
+#include "nsIContentSecurityPolicy.h"
 #include "nsIObjectInputStream.h"
 #include "nsIObjectOutputStream.h"
 
 #include "nsPrincipal.h"
 #include "nsNetUtil.h"
 #include "nsNullPrincipal.h"
 #include "nsScriptSecurityManager.h"
 
+#include "mozilla/dom/CSPDictionariesBinding.h"
 #include "mozilla/dom/ToJSValue.h"
 
 namespace mozilla {
 
 void
 OriginAttributes::CreateSuffix(nsACString& aStr)
 {
   aStr.Truncate();
@@ -124,16 +126,29 @@ BasePrincipal::SetCsp(nsIContentSecurity
   if (mCSP)
     return NS_ERROR_ALREADY_INITIALIZED;
 
   mCSP = aCsp;
   return NS_OK;
 }
 
 NS_IMETHODIMP
+BasePrincipal::GetCspJSON(nsAString& outCSPinJSON)
+{
+  outCSPinJSON.Truncate();
+  dom::CSPPolicies jsonPolicies;
+
+  if (!mCSP) {
+    jsonPolicies.ToJSON(outCSPinJSON);
+    return NS_OK;
+  }
+  return mCSP->ToJSON(outCSPinJSON);
+}
+
+NS_IMETHODIMP
 BasePrincipal::GetIsNullPrincipal(bool* aIsNullPrincipal)
 {
   *aIsNullPrincipal = false;
   return NS_OK;
 }
 
 NS_IMETHODIMP
 BasePrincipal::GetJarPrefix(nsACString& aJarPrefix)
--- a/caps/BasePrincipal.h
+++ b/caps/BasePrincipal.h
@@ -65,16 +65,17 @@ public:
   NS_IMETHOD GetOrigin(nsACString& aOrigin) final;
   NS_IMETHOD GetOriginNoSuffix(nsACString& aOrigin) final;
   NS_IMETHOD Equals(nsIPrincipal* other, bool* _retval) final;
   NS_IMETHOD EqualsConsideringDomain(nsIPrincipal* other, bool* _retval) final;
   NS_IMETHOD Subsumes(nsIPrincipal* other, bool* _retval) final;
   NS_IMETHOD SubsumesConsideringDomain(nsIPrincipal* other, bool* _retval) final;
   NS_IMETHOD GetCsp(nsIContentSecurityPolicy** aCsp) override;
   NS_IMETHOD SetCsp(nsIContentSecurityPolicy* aCsp) override;
+  NS_IMETHOD GetCspJSON(nsAString& outCSPinJSON) override;
   NS_IMETHOD GetIsNullPrincipal(bool* aIsNullPrincipal) override;
   NS_IMETHOD GetJarPrefix(nsACString& aJarPrefix) final;
   NS_IMETHOD GetOriginAttributes(JSContext* aCx, JS::MutableHandle<JS::Value> aVal) final;
   NS_IMETHOD GetOriginSuffix(nsACString& aOriginSuffix) final;
   NS_IMETHOD GetCookieJar(nsACString& aCookieJar) final;
   NS_IMETHOD GetAppStatus(uint16_t* aAppStatus) final;
   NS_IMETHOD GetAppId(uint32_t* aAppStatus) final;
   NS_IMETHOD GetIsInBrowserElement(bool* aIsInBrowserElement) final;
--- a/caps/nsIPrincipal.idl
+++ b/caps/nsIPrincipal.idl
@@ -15,17 +15,17 @@ struct JSPrincipals;
 
 interface nsIURI;
 interface nsIContentSecurityPolicy;
 
 [ptr] native JSContext(JSContext);
 [ptr] native JSPrincipals(JSPrincipals);
 [ptr] native PrincipalArray(nsTArray<nsCOMPtr<nsIPrincipal> >);
 
-[scriptable, builtinclass, uuid(749f21f5-8ade-4d0b-a590-2b1d18e890d5)]
+[scriptable, builtinclass, uuid(49c2faf0-b6de-4640-8d0f-e0217baa8627)]
 interface nsIPrincipal : nsISerializable
 {
     /**
      * Returns whether the other principal is equivalent to this principal.
      * Principals are considered equal if they are the same principal, or
      * they have the same origin.
      */
     boolean equals(in nsIPrincipal other);
@@ -129,16 +129,23 @@ interface nsIPrincipal : nsISerializable
                       in boolean allowIfInheritsPrincipal);
 
     /**
      * A Content Security Policy associated with this principal.
      */
     [noscript] attribute nsIContentSecurityPolicy csp;
 
     /**
+     * The CSP of the principal in JSON notation.
+     * Note, that the CSP itself is not exposed to JS, but script
+     * should be able to obtain a JSON representation of the CSP.
+     */
+    readonly attribute AString cspJSON;
+
+    /**
      * Returns the jar prefix of the principal.
      * The jar prefix is a string that can be used to isolate data or
      * permissions between different principals while taking into account
      * parameters like the app id or the fact that the principal is embedded in
      * a mozbrowser.
      * Some principals will return an empty string.
      * Some principals will assert if you try to access the jarPrefix.
      *
--- a/dom/interfaces/security/nsIContentSecurityPolicy.idl
+++ b/dom/interfaces/security/nsIContentSecurityPolicy.idl
@@ -15,17 +15,17 @@ interface nsIURI;
  * 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.
  */
 
 typedef unsigned short CSPDirective;
 
-[scriptable, uuid(68434447-b816-4473-a731-efc4f6d59902)]
+[scriptable, uuid(459fe61a-203e-4460-9ced-352a9bd3aa71)]
 interface nsIContentSecurityPolicy : nsISerializable
 {
   /**
    * Directives supported by Content Security Policy.  These are enums for
    * the CSPDirective type.
    * The NO_DIRECTIVE entry is  used for checking default permissions and
    * returning failure when asking CSP which directive to check.
    *
@@ -277,9 +277,14 @@ interface nsIContentSecurityPolicy : nsI
                    in ACString        aMimeTypeGuess,
                    in nsISupports     aExtra);
 
 %{ C++
 // nsIObserver topic to fire when the policy encounters a violation.
 #define CSP_VIOLATION_TOPIC "csp-on-violate-policy"
 %}
 
+  /**
+   * Returns the CSP in JSON notation.
+   */
+  AString toJSON();
+
 };
--- a/dom/security/nsCSPContext.cpp
+++ b/dom/security/nsCSPContext.cpp
@@ -32,16 +32,17 @@
 #include "nsNetUtil.h"
 #include "nsNullPrincipal.h"
 #include "nsIContentPolicy.h"
 #include "nsSupportsPrimitives.h"
 #include "nsThreadUtils.h"
 #include "nsString.h"
 #include "mozilla/Logging.h"
 #include "mozilla/dom/CSPReportBinding.h"
+#include "mozilla/dom/CSPDictionariesBinding.h"
 #include "mozilla/net/ReferrerPolicy.h"
 #include "nsINetworkInterceptController.h"
 
 using namespace mozilla;
 
 static PRLogModuleInfo *
 GetCspContextLog()
 {
@@ -1181,16 +1182,36 @@ nsCSPContext::Permits(nsIURI* aURI,
       CSPCONTEXTLOG(("nsCSPContext::Permits, aUri: %s, aDir: %d, isAllowed: %s",
                     spec.get(), aDir,
                     *outPermits ? "allow" : "deny"));
   }
 
   return NS_OK;
 }
 
+NS_IMETHODIMP
+nsCSPContext::ToJSON(nsAString& outCSPinJSON)
+{
+  outCSPinJSON.Truncate();
+  dom::CSPPolicies jsonPolicies;
+  jsonPolicies.mCsp_policies.Construct();
+
+  for (uint32_t p = 0; p < mPolicies.Length(); p++) {
+    dom::CSP jsonCSP;
+    mPolicies[p]->toDomCSPStruct(jsonCSP);
+    jsonPolicies.mCsp_policies.Value().AppendElement(jsonCSP);
+  }
+
+  // convert the gathered information to JSON
+  if (!jsonPolicies.ToJSON(outCSPinJSON)) {
+    return NS_ERROR_FAILURE;
+  }
+  return NS_OK;
+}
+
 /* ========== CSPViolationReportListener implementation ========== */
 
 NS_IMPL_ISUPPORTS(CSPViolationReportListener, nsIStreamListener, nsIRequestObserver, nsISupports);
 
 CSPViolationReportListener::CSPViolationReportListener()
 {
 }
 
--- a/dom/security/nsCSPUtils.cpp
+++ b/dom/security/nsCSPUtils.cpp
@@ -764,16 +764,103 @@ nsCSPDirective::toString(nsAString& outS
   for (uint32_t i = 0; i < length; i++) {
     mSrcs[i]->toString(outStr);
     if (i != (length - 1)) {
       outStr.AppendASCII(" ");
     }
   }
 }
 
+void
+nsCSPDirective::toDomCSPStruct(mozilla::dom::CSP& outCSP) const
+{
+  mozilla::dom::Sequence<nsString> srcs;
+  nsString src;
+  for (uint32_t i = 0; i < mSrcs.Length(); i++) {
+    src.Truncate();
+    mSrcs[i]->toString(src);
+    srcs.AppendElement(src);
+  }
+
+  switch(mDirective) {
+    case nsIContentSecurityPolicy::DEFAULT_SRC_DIRECTIVE:
+      outCSP.mDefault_src.Construct();
+      outCSP.mDefault_src.Value() = srcs;
+      return;
+
+    case nsIContentSecurityPolicy::SCRIPT_SRC_DIRECTIVE:
+      outCSP.mScript_src.Construct();
+      outCSP.mScript_src.Value() = srcs;
+      return;
+
+    case nsIContentSecurityPolicy::OBJECT_SRC_DIRECTIVE:
+      outCSP.mObject_src.Construct();
+      outCSP.mObject_src.Value() = srcs;
+      return;
+
+    case nsIContentSecurityPolicy::STYLE_SRC_DIRECTIVE:
+      outCSP.mStyle_src.Construct();
+      outCSP.mStyle_src.Value() = srcs;
+      return;
+
+    case nsIContentSecurityPolicy::IMG_SRC_DIRECTIVE:
+      outCSP.mImg_src.Construct();
+      outCSP.mImg_src.Value() = srcs;
+      return;
+
+    case nsIContentSecurityPolicy::MEDIA_SRC_DIRECTIVE:
+      outCSP.mMedia_src.Construct();
+      outCSP.mMedia_src.Value() = srcs;
+      return;
+
+    case nsIContentSecurityPolicy::FRAME_SRC_DIRECTIVE:
+      outCSP.mFrame_src.Construct();
+      outCSP.mFrame_src.Value() = srcs;
+      return;
+
+    case nsIContentSecurityPolicy::FONT_SRC_DIRECTIVE:
+      outCSP.mFont_src.Construct();
+      outCSP.mFont_src.Value() = srcs;
+      return;
+
+    case nsIContentSecurityPolicy::CONNECT_SRC_DIRECTIVE:
+      outCSP.mConnect_src.Construct();
+      outCSP.mConnect_src.Value() = srcs;
+      return;
+
+    case nsIContentSecurityPolicy::REPORT_URI_DIRECTIVE:
+      outCSP.mReport_uri.Construct();
+      outCSP.mReport_uri.Value() = srcs;
+      return;
+
+    case nsIContentSecurityPolicy::FRAME_ANCESTORS_DIRECTIVE:
+      outCSP.mFrame_ancestors.Construct();
+      outCSP.mFrame_ancestors.Value() = srcs;
+      return;
+
+    // not supporting REFLECTED_XSS_DIRECTIVE
+
+    case nsIContentSecurityPolicy::BASE_URI_DIRECTIVE:
+      outCSP.mBase_uri.Construct();
+      outCSP.mBase_uri.Value() = srcs;
+      return;
+
+    case nsIContentSecurityPolicy::FORM_ACTION_DIRECTIVE:
+      outCSP.mForm_action.Construct();
+      outCSP.mForm_action.Value() = srcs;
+      return;
+
+    // REFERRER_DIRECTIVE is handled in nsCSPPolicy::toDomCSPStruct()
+
+    default:
+      NS_ASSERTION(false, "cannot find directive to convert CSP to JSON");
+  }
+}
+
+
 bool
 nsCSPDirective::restrictsContentType(nsContentPolicyType aContentType) const
 {
   // make sure we do not check for the default src before any other sources
   if (isDefaultDirective()) {
     return false;
   }
   return mDirective == CSP_ContentTypeToDirective(aContentType);
@@ -931,16 +1018,33 @@ nsCSPPolicy::toString(nsAString& outStr)
       mDirectives[i]->toString(outStr);
     }
     if (i != (length - 1)) {
       outStr.AppendASCII("; ");
     }
   }
 }
 
+void
+nsCSPPolicy::toDomCSPStruct(mozilla::dom::CSP& outCSP) const
+{
+  outCSP.mReport_only = mReportOnly;
+
+  for (uint32_t i = 0; i < mDirectives.Length(); ++i) {
+    if (mDirectives[i]->equals(nsIContentSecurityPolicy::REFERRER_DIRECTIVE)) {
+      mozilla::dom::Sequence<nsString> srcs;
+      srcs.AppendElement(mReferrerPolicy);
+      outCSP.mReferrer.Construct();
+      outCSP.mReferrer.Value() = srcs;
+    } else {
+      mDirectives[i]->toDomCSPStruct(outCSP);
+    }
+  }
+}
+
 bool
 nsCSPPolicy::hasDirective(CSPDirective aDir) const
 {
   for (uint32_t i = 0; i < mDirectives.Length(); i++) {
     if (mDirectives[i]->equals(aDir)) {
       return true;
     }
   }
--- a/dom/security/nsCSPUtils.h
+++ b/dom/security/nsCSPUtils.h
@@ -11,16 +11,22 @@
 #include "nsIContentPolicy.h"
 #include "nsIContentSecurityPolicy.h"
 #include "nsIURI.h"
 #include "nsString.h"
 #include "nsTArray.h"
 #include "nsUnicharUtils.h"
 #include "mozilla/Logging.h"
 
+namespace mozilla {
+namespace dom {
+  struct CSP;
+}
+}
+
 /* =============== Logging =================== */
 
 void CSP_LogLocalizedStr(const char16_t* aName,
                          const char16_t** aParams,
                          uint32_t aLength,
                          const nsAString& aSourceName,
                          const nsAString& aSourceLine,
                          uint32_t aLineNumber,
@@ -53,17 +59,18 @@ void CSP_LogMessage(const nsAString& aMe
 #define EVAL_VIOLATION_OBSERVER_TOPIC           "violated base restriction: Code will not be created from strings"
 #define SCRIPT_NONCE_VIOLATION_OBSERVER_TOPIC   "Inline Script had invalid nonce"
 #define STYLE_NONCE_VIOLATION_OBSERVER_TOPIC    "Inline Style had invalid nonce"
 #define SCRIPT_HASH_VIOLATION_OBSERVER_TOPIC    "Inline Script had invalid hash"
 #define STYLE_HASH_VIOLATION_OBSERVER_TOPIC     "Inline Style had invalid hash"
 
 // these strings map to the CSPDirectives in nsIContentSecurityPolicy
 // NOTE: When implementing a new directive, you will need to add it here but also
-// add a corresponding entry to the constants in nsIContentSecurityPolicy.idl
+// add a corresponding entry to the constants in nsIContentSecurityPolicy.idl and
+// also create an entry for the new directive in nsCSPDirective::toDomCSPStruct().
 static const char* CSPStrDirectives[] = {
   "-error-",    // NO_DIRECTIVE
   "default-src",     // DEFAULT_SRC_DIRECTIVE
   "script-src",      // SCRIPT_SRC_DIRECTIVE
   "object-src",      // OBJECT_SRC_DIRECTIVE
   "style-src",       // STYLE_SRC_DIRECTIVE
   "img-src",         // IMG_SRC_DIRECTIVE
   "media-src",       // MEDIA_SRC_DIRECTIVE
@@ -279,16 +286,17 @@ class nsCSPDirective {
     nsCSPDirective();
     explicit nsCSPDirective(CSPDirective aDirective);
     virtual ~nsCSPDirective();
 
     bool permits(nsIURI* aUri, const nsAString& aNonce, bool aWasRedirected) const;
     bool permits(nsIURI* aUri) const;
     bool allows(enum CSPKeyword aKeyword, const nsAString& aHashOrNonce) const;
     void toString(nsAString& outStr) const;
+    void toDomCSPStruct(mozilla::dom::CSP& outCSP) const;
 
     inline void addSrcs(const nsTArray<nsCSPBaseSrc*>& aSrcs)
       { mSrcs = aSrcs; }
 
     bool restrictsContentType(nsContentPolicyType aContentType) const;
 
     inline bool isDefaultDirective() const
      { return mDirective == nsIContentSecurityPolicy::DEFAULT_SRC_DIRECTIVE; }
@@ -320,16 +328,17 @@ class nsCSPPolicy {
                  nsIURI* aUri,
                  bool aSpecific) const;
     bool allows(nsContentPolicyType aContentType,
                 enum CSPKeyword aKeyword,
                 const nsAString& aHashOrNonce) const;
     bool allows(nsContentPolicyType aContentType,
                 enum CSPKeyword aKeyword) const;
     void toString(nsAString& outStr) const;
+    void toDomCSPStruct(mozilla::dom::CSP& outCSP) const;
 
     inline void addDirective(nsCSPDirective* aDir)
       { mDirectives.AppendElement(aDir); }
 
     bool hasDirective(CSPDirective aDir) const;
 
     inline void setReportOnlyFlag(bool aFlag)
       { mReportOnly = aFlag; }
new file mode 100644
--- /dev/null
+++ b/dom/webidl/CSPDictionaries.webidl
@@ -0,0 +1,31 @@
+/* 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/. */
+
+/**
+  * Dictionary used to display CSP info.
+  */
+
+dictionary CSP {
+  boolean report-only = false;
+
+  sequence<DOMString> default-src;
+  sequence<DOMString> script-src;
+  sequence<DOMString> object-src;
+  sequence<DOMString> style-src;
+  sequence<DOMString> img-src;
+  sequence<DOMString> media-src;
+  sequence<DOMString> frame-src;
+  sequence<DOMString> font-src;
+  sequence<DOMString> connect-src;
+  sequence<DOMString> report-uri;
+  sequence<DOMString> frame-ancestors;
+  // sequence<DOMString> reflected-xss; // not suppored in Firefox
+  sequence<DOMString> base-uri;
+  sequence<DOMString> form-action;
+  sequence<DOMString> referrer;
+};
+
+dictionary CSPPolicies {
+  sequence<CSP> csp-policies;
+};
--- a/dom/webidl/moz.build
+++ b/dom/webidl/moz.build
@@ -82,16 +82,17 @@ WEBIDL_FILES = [
     'CompositionEvent.webidl',
     'Console.webidl',
     'Constraints.webidl',
     'Contacts.webidl',
     'ContainerBoxObject.webidl',
     'ConvolverNode.webidl',
     'Coordinates.webidl',
     'Crypto.webidl',
+    'CSPDictionaries.webidl',
     'CSPReport.webidl',
     'CSS.webidl',
     'CSSLexer.webidl',
     'CSSPrimitiveValue.webidl',
     'CSSRuleList.webidl',
     'CSSStyleDeclaration.webidl',
     'CSSStyleSheet.webidl',
     'CSSValue.webidl',