/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set ts=8 sts=2 et sw=2 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/. */ #ifndef nsCSPUtils_h___ #define nsCSPUtils_h___ #include "nsCOMPtr.h" #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, uint32_t aColumnNumber, uint32_t aFlags, const char* aCategory, uint32_t aInnerWindowID); void CSP_GetLocalizedStr(const char16_t* aName, const char16_t** aParams, uint32_t aLength, char16_t** outResult); void CSP_LogStrMessage(const nsAString& aMsg); void CSP_LogMessage(const nsAString& aMessage, const nsAString& aSourceName, const nsAString& aSourceLine, uint32_t aLineNumber, uint32_t aColumnNumber, uint32_t aFlags, const char* aCategory, uint32_t aInnerWindowID); /* =============== Constant and Type Definitions ================== */ #define INLINE_STYLE_VIOLATION_OBSERVER_TOPIC "violated base restriction: Inline Stylesheets will not apply" #define INLINE_SCRIPT_VIOLATION_OBSERVER_TOPIC "violated base restriction: Inline Scripts will not execute" #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 // and also create an entry for the new directive in // nsCSPDirective::toDomCSPStruct() and add it to CSPDictionaries.webidl. // Order of elements below important! Make sure it matches the order as in // nsIContentSecurityPolicy.idl 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 "frame-src", // FRAME_SRC_DIRECTIVE "font-src", // FONT_SRC_DIRECTIVE "connect-src", // CONNECT_SRC_DIRECTIVE "report-uri", // REPORT_URI_DIRECTIVE "frame-ancestors", // FRAME_ANCESTORS_DIRECTIVE "reflected-xss", // REFLECTED_XSS_DIRECTIVE "base-uri", // BASE_URI_DIRECTIVE "form-action", // FORM_ACTION_DIRECTIVE "referrer", // REFERRER_DIRECTIVE "manifest-src" // MANIFEST_SRC_DIRECTIVE }; inline const char* CSP_CSPDirectiveToString(CSPDirective aDir) { return CSPStrDirectives[static_cast(aDir)]; } inline CSPDirective CSP_StringToCSPDirective(const nsAString& aDir) { nsString lowerDir = PromiseFlatString(aDir); ToLowerCase(lowerDir); uint32_t numDirs = (sizeof(CSPStrDirectives) / sizeof(CSPStrDirectives[0])); for (uint32_t i = 1; i < numDirs; i++) { if (lowerDir.EqualsASCII(CSPStrDirectives[i])) { return static_cast(i); } } NS_ASSERTION(false, "Can not convert unknown Directive to Integer"); return nsIContentSecurityPolicy::NO_DIRECTIVE; } // Please add any new enum items not only to CSPKeyword, but also add // a string version for every enum >> using the same index << to // CSPStrKeywords underneath. enum CSPKeyword { CSP_SELF = 0, CSP_UNSAFE_INLINE, CSP_UNSAFE_EVAL, CSP_NONE, CSP_NONCE, // CSP_LAST_KEYWORD_VALUE always needs to be the last element in the enum // because we use it to calculate the size for the char* array. CSP_LAST_KEYWORD_VALUE, // Putting CSP_HASH after the delimitor, because CSP_HASH is not a valid // keyword (hash uses e.g. sha256, sha512) but we use CSP_HASH internally // to identify allowed hashes in ::allows. CSP_HASH }; static const char* CSPStrKeywords[] = { "'self'", // CSP_SELF = 0 "'unsafe-inline'", // CSP_UNSAFE_INLINE "'unsafe-eval'", // CSP_UNSAFE_EVAL "'none'", // CSP_NONE "'nonce-", // CSP_NONCE // Remember: CSP_HASH is not supposed to be used }; inline const char* CSP_EnumToKeyword(enum CSPKeyword aKey) { // Make sure all elements in enum CSPKeyword got added to CSPStrKeywords. static_assert((sizeof(CSPStrKeywords) / sizeof(CSPStrKeywords[0]) == static_cast(CSP_LAST_KEYWORD_VALUE)), "CSP_LAST_KEYWORD_VALUE does not match length of CSPStrKeywords"); return CSPStrKeywords[static_cast(aKey)]; } inline CSPKeyword CSP_KeywordToEnum(const nsAString& aKey) { nsString lowerKey = PromiseFlatString(aKey); ToLowerCase(lowerKey); static_assert(CSP_LAST_KEYWORD_VALUE == (sizeof(CSPStrKeywords) / sizeof(CSPStrKeywords[0])), "CSP_LAST_KEYWORD_VALUE does not match length of CSPStrKeywords"); for (uint32_t i = 0; i < CSP_LAST_KEYWORD_VALUE; i++) { if (lowerKey.EqualsASCII(CSPStrKeywords[i])) { return static_cast(i); } } NS_ASSERTION(false, "Can not convert unknown Keyword to Enum"); return CSP_LAST_KEYWORD_VALUE; } /* =============== Helpers ================== */ class nsCSPHostSrc; nsCSPHostSrc* CSP_CreateHostSrcFromURI(nsIURI* aURI); bool CSP_IsValidDirective(const nsAString& aDir); bool CSP_IsDirective(const nsAString& aValue, CSPDirective aDir); bool CSP_IsKeyword(const nsAString& aValue, enum CSPKeyword aKey); bool CSP_IsQuotelessKeyword(const nsAString& aKey); CSPDirective CSP_ContentTypeToDirective(nsContentPolicyType aType); /* =============== nsCSPSrc ================== */ class nsCSPBaseSrc { public: nsCSPBaseSrc(); virtual ~nsCSPBaseSrc(); virtual bool permits(nsIURI* aUri, const nsAString& aNonce, bool aWasRedirected) const; virtual bool allows(enum CSPKeyword aKeyword, const nsAString& aHashOrNonce) const; virtual void toString(nsAString& outStr) const = 0; }; /* =============== nsCSPSchemeSrc ============ */ class nsCSPSchemeSrc : public nsCSPBaseSrc { public: explicit nsCSPSchemeSrc(const nsAString& aScheme); virtual ~nsCSPSchemeSrc(); bool permits(nsIURI* aUri, const nsAString& aNonce, bool aWasRedirected) const; void toString(nsAString& outStr) const; private: nsString mScheme; }; /* =============== nsCSPHostSrc ============== */ class nsCSPHostSrc : public nsCSPBaseSrc { public: explicit nsCSPHostSrc(const nsAString& aHost); virtual ~nsCSPHostSrc(); bool permits(nsIURI* aUri, const nsAString& aNonce, bool aWasRedirected) const; void toString(nsAString& outStr) const; void setScheme(const nsAString& aScheme, bool aAllowHttps = false); void setPort(const nsAString& aPort); void appendPath(const nsAString &aPath); private: nsString mScheme; nsString mHost; nsString mPort; nsString mPath; bool mAllowHttps; }; /* =============== nsCSPKeywordSrc ============ */ class nsCSPKeywordSrc : public nsCSPBaseSrc { public: explicit nsCSPKeywordSrc(CSPKeyword aKeyword); virtual ~nsCSPKeywordSrc(); bool allows(enum CSPKeyword aKeyword, const nsAString& aHashOrNonce) const; void toString(nsAString& outStr) const; void invalidate(); private: CSPKeyword mKeyword; // invalidate 'unsafe-inline' if nonce- or hash-source specified bool mInvalidated; }; /* =============== nsCSPNonceSource =========== */ class nsCSPNonceSrc : public nsCSPBaseSrc { public: explicit nsCSPNonceSrc(const nsAString& aNonce); virtual ~nsCSPNonceSrc(); bool permits(nsIURI* aUri, const nsAString& aNonce, bool aWasRedirected) const; bool allows(enum CSPKeyword aKeyword, const nsAString& aHashOrNonce) const; void toString(nsAString& outStr) const; private: nsString mNonce; }; /* =============== nsCSPHashSource ============ */ class nsCSPHashSrc : public nsCSPBaseSrc { public: nsCSPHashSrc(const nsAString& algo, const nsAString& hash); virtual ~nsCSPHashSrc(); bool allows(enum CSPKeyword aKeyword, const nsAString& aHashOrNonce) const; void toString(nsAString& outStr) const; private: nsString mAlgorithm; nsString mHash; }; /* =============== nsCSPReportURI ============ */ class nsCSPReportURI : public nsCSPBaseSrc { public: explicit nsCSPReportURI(nsIURI* aURI); virtual ~nsCSPReportURI(); void toString(nsAString& outStr) const; private: nsCOMPtr mReportURI; }; /* =============== nsCSPDirective ============= */ class nsCSPDirective { public: 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& aSrcs) { mSrcs = aSrcs; } bool restrictsContentType(nsContentPolicyType aContentType) const; inline bool isDefaultDirective() const { return mDirective == nsIContentSecurityPolicy::DEFAULT_SRC_DIRECTIVE; } inline bool equals(CSPDirective aDirective) const { return (mDirective == aDirective); } void getReportURIs(nsTArray &outReportURIs) const; private: CSPDirective mDirective; nsTArray mSrcs; }; /* =============== nsCSPPolicy ================== */ class nsCSPPolicy { public: nsCSPPolicy(); virtual ~nsCSPPolicy(); bool permits(CSPDirective aDirective, nsIURI* aUri, const nsAString& aNonce, bool aWasRedirected, bool aSpecific, nsAString& outViolatedDirective) const; bool permits(CSPDirective aDir, 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; } inline bool getReportOnlyFlag() const { return mReportOnly; } inline void setReferrerPolicy(const nsAString* aValue) { mReferrerPolicy = *aValue; } inline void getReferrerPolicy(nsAString& outPolicy) const { outPolicy.Assign(mReferrerPolicy); } void getReportURIs(nsTArray &outReportURIs) const; void getDirectiveStringForContentType(nsContentPolicyType aContentType, nsAString& outDirective) const; void getDirectiveAsString(CSPDirective aDir, nsAString& outDirective) const; inline uint32_t getNumDirectives() const { return mDirectives.Length(); } private: nsTArray mDirectives; bool mReportOnly; nsString mReferrerPolicy; }; #endif /* nsCSPUtils_h___ */