/* -*- 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 "nsIContentSecurityPolicy.h" #include "nsIURI.h" #include "nsLiteralString.h" #include "nsString.h" #include "nsTArray.h" #include "nsUnicharUtils.h" #include "mozilla/Logging.h" class nsIChannel; namespace mozilla { namespace dom { struct CSP; class Document; } // namespace dom } // namespace mozilla /* =============== Logging =================== */ void CSP_LogLocalizedStr(const char* aName, const nsTArray& aParams, const nsAString& aSourceName, const nsAString& aSourceLine, uint32_t aLineNumber, uint32_t aColumnNumber, uint32_t aFlags, const nsACString& aCategory, uint64_t aInnerWindowID, bool aFromPrivateWindow); void CSP_GetLocalizedStr(const char* aName, const nsTArray& aParams, nsAString& 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 nsACString& aCategory, uint64_t aInnerWindowID, bool aFromPrivateWindow); /* =============== 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 "manifest-src", // MANIFEST_SRC_DIRECTIVE "upgrade-insecure-requests", // UPGRADE_IF_INSECURE_DIRECTIVE "child-src", // CHILD_SRC_DIRECTIVE "block-all-mixed-content", // BLOCK_ALL_MIXED_CONTENT "sandbox", // SANDBOX_DIRECTIVE "worker-src", // WORKER_SRC_DIRECTIVE "navigate-to" // NAVIGATE_TO_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; } #define FOR_EACH_CSP_KEYWORD(MACRO) \ MACRO(CSP_SELF, "'self'") \ MACRO(CSP_UNSAFE_INLINE, "'unsafe-inline'") \ MACRO(CSP_UNSAFE_EVAL, "'unsafe-eval'") \ MACRO(CSP_NONE, "'none'") \ MACRO(CSP_NONCE, "'nonce-") \ MACRO(CSP_REPORT_SAMPLE, "'report-sample'") \ MACRO(CSP_STRICT_DYNAMIC, "'strict-dynamic'") \ MACRO(CSP_UNSAFE_ALLOW_REDIRECTS, "'unsafe-allow-redirects'") enum CSPKeyword { #define KEYWORD_ENUM(id_, string_) id_, FOR_EACH_CSP_KEYWORD(KEYWORD_ENUM) #undef KEYWORD_ENUM // 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 }; // The keywords, in UTF-8 form. static const char* gCSPUTF8Keywords[] = { #define KEYWORD_UTF8_LITERAL(id_, string_) string_, FOR_EACH_CSP_KEYWORD(KEYWORD_UTF8_LITERAL) #undef KEYWORD_UTF8_LITERAL }; // The keywords, in UTF-16 form. static const char16_t* gCSPUTF16Keywords[] = { #define KEYWORD_UTF16_LITERAL(id_, string_) u"" string_, FOR_EACH_CSP_KEYWORD(KEYWORD_UTF16_LITERAL) #undef KEYWORD_UTF16_LITERAL }; #undef FOR_EACH_CSP_KEYWORD inline const char* CSP_EnumToUTF8Keyword(enum CSPKeyword aKey) { // Make sure all elements in enum CSPKeyword got added to gCSPUTF8Keywords. static_assert((sizeof(gCSPUTF8Keywords) / sizeof(gCSPUTF8Keywords[0]) == CSP_LAST_KEYWORD_VALUE), "CSP_LAST_KEYWORD_VALUE != length(gCSPUTF8Keywords)"); if (static_cast(aKey) < static_cast(CSP_LAST_KEYWORD_VALUE)) { return gCSPUTF8Keywords[static_cast(aKey)]; } return "error: invalid keyword in CSP_EnumToUTF8Keyword"; } inline const char16_t* CSP_EnumToUTF16Keyword(enum CSPKeyword aKey) { // Make sure all elements in enum CSPKeyword got added to gCSPUTF16Keywords. static_assert((sizeof(gCSPUTF16Keywords) / sizeof(gCSPUTF16Keywords[0]) == CSP_LAST_KEYWORD_VALUE), "CSP_LAST_KEYWORD_VALUE != length(gCSPUTF16Keywords)"); if (static_cast(aKey) < static_cast(CSP_LAST_KEYWORD_VALUE)) { return gCSPUTF16Keywords[static_cast(aKey)]; } return u"error: invalid keyword in CSP_EnumToUTF16Keyword"; } inline CSPKeyword CSP_UTF16KeywordToEnum(const nsAString& aKey) { nsString lowerKey = PromiseFlatString(aKey); ToLowerCase(lowerKey); for (uint32_t i = 0; i < CSP_LAST_KEYWORD_VALUE; i++) { if (lowerKey.Equals(gCSPUTF16Keywords[i])) { return static_cast(i); } } NS_ASSERTION(false, "Can not convert unknown Keyword to Enum"); return CSP_LAST_KEYWORD_VALUE; } nsresult CSP_AppendCSPFromHeader(nsIContentSecurityPolicy* aCsp, const nsAString& aHeaderValue, bool aReportOnly); /* =============== Helpers ================== */ class nsCSPHostSrc; nsCSPHostSrc* CSP_CreateHostSrcFromSelfURI(nsIURI* aSelfURI); bool CSP_IsEmptyDirective(const nsAString& aValue, const nsAString& aDir); 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); class nsCSPSrcVisitor; void CSP_PercentDecodeStr(const nsAString& aEncStr, nsAString& outDecStr); bool CSP_ShouldResponseInheritCSP(nsIChannel* aChannel); void CSP_ApplyMetaCSPToDoc(mozilla::dom::Document& aDoc, const nsAString& aPolicyStr); /* =============== nsCSPSrc ================== */ class nsCSPBaseSrc { public: nsCSPBaseSrc(); virtual ~nsCSPBaseSrc(); virtual bool permits(nsIURI* aUri, const nsAString& aNonce, bool aWasRedirected, bool aReportOnly, bool aUpgradeInsecure, bool aParserCreated) const; virtual bool allows(enum CSPKeyword aKeyword, const nsAString& aHashOrNonce, bool aParserCreated) const; virtual bool visit(nsCSPSrcVisitor* aVisitor) const = 0; virtual void toString(nsAString& outStr) const = 0; virtual void invalidate() const { mInvalidated = true; } virtual bool isReportSample() const { return false; } protected: // invalidate srcs if 'script-dynamic' is present or also invalidate // unsafe-inline' if nonce- or hash-source specified mutable bool mInvalidated; }; /* =============== nsCSPSchemeSrc ============ */ class nsCSPSchemeSrc : public nsCSPBaseSrc { public: explicit nsCSPSchemeSrc(const nsAString& aScheme); virtual ~nsCSPSchemeSrc(); bool permits(nsIURI* aUri, const nsAString& aNonce, bool aWasRedirected, bool aReportOnly, bool aUpgradeInsecure, bool aParserCreated) const override; bool visit(nsCSPSrcVisitor* aVisitor) const override; void toString(nsAString& outStr) const override; inline void getScheme(nsAString& outStr) const { outStr.Assign(mScheme); }; 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, bool aReportOnly, bool aUpgradeInsecure, bool aParserCreated) const override; bool visit(nsCSPSrcVisitor* aVisitor) const override; void toString(nsAString& outStr) const override; void setScheme(const nsAString& aScheme); void setPort(const nsAString& aPort); void appendPath(const nsAString& aPath); inline void setGeneratedFromSelfKeyword() const { mGeneratedFromSelfKeyword = true; } inline void setIsUniqueOrigin() const { mIsUniqueOrigin = true; } inline void setWithinFrameAncestorsDir(bool aValue) const { mWithinFrameAncstorsDir = aValue; } inline void getScheme(nsAString& outStr) const { outStr.Assign(mScheme); }; inline void getHost(nsAString& outStr) const { outStr.Assign(mHost); }; inline void getPort(nsAString& outStr) const { outStr.Assign(mPort); }; inline void getPath(nsAString& outStr) const { outStr.Assign(mPath); }; private: nsString mScheme; nsString mHost; nsString mPort; nsString mPath; mutable bool mGeneratedFromSelfKeyword; mutable bool mIsUniqueOrigin; mutable bool mWithinFrameAncstorsDir; }; /* =============== nsCSPKeywordSrc ============ */ class nsCSPKeywordSrc : public nsCSPBaseSrc { public: explicit nsCSPKeywordSrc(CSPKeyword aKeyword); virtual ~nsCSPKeywordSrc(); bool allows(enum CSPKeyword aKeyword, const nsAString& aHashOrNonce, bool aParserCreated) const override; bool permits(nsIURI* aUri, const nsAString& aNonce, bool aWasRedirected, bool aReportOnly, bool aUpgradeInsecure, bool aParserCreated) const override; bool visit(nsCSPSrcVisitor* aVisitor) const override; void toString(nsAString& outStr) const override; inline CSPKeyword getKeyword() const { return mKeyword; }; inline void invalidate() const override { // keywords that need to invalidated if (mKeyword == CSP_SELF || mKeyword == CSP_UNSAFE_INLINE || mKeyword == CSP_REPORT_SAMPLE) { mInvalidated = true; } } bool isReportSample() const override { return mKeyword == CSP_REPORT_SAMPLE; } private: CSPKeyword mKeyword; }; /* =============== nsCSPNonceSource =========== */ class nsCSPNonceSrc : public nsCSPBaseSrc { public: explicit nsCSPNonceSrc(const nsAString& aNonce); virtual ~nsCSPNonceSrc(); bool permits(nsIURI* aUri, const nsAString& aNonce, bool aWasRedirected, bool aReportOnly, bool aUpgradeInsecure, bool aParserCreated) const override; bool allows(enum CSPKeyword aKeyword, const nsAString& aHashOrNonce, bool aParserCreated) const override; bool visit(nsCSPSrcVisitor* aVisitor) const override; void toString(nsAString& outStr) const override; inline void getNonce(nsAString& outStr) const { outStr.Assign(mNonce); }; inline void invalidate() const override { // overwrite nsCSPBaseSRC::invalidate() and explicitily // do *not* invalidate, because 'strict-dynamic' should // not invalidate nonces. } 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, bool aParserCreated) const override; void toString(nsAString& outStr) const override; bool visit(nsCSPSrcVisitor* aVisitor) const override; inline void getAlgorithm(nsAString& outStr) const { outStr.Assign(mAlgorithm); }; inline void getHash(nsAString& outStr) const { outStr.Assign(mHash); }; inline void invalidate() const override { // overwrite nsCSPBaseSRC::invalidate() and explicitily // do *not* invalidate, because 'strict-dynamic' should // not invalidate hashes. } private: nsString mAlgorithm; nsString mHash; }; /* =============== nsCSPReportURI ============ */ class nsCSPReportURI : public nsCSPBaseSrc { public: explicit nsCSPReportURI(nsIURI* aURI); virtual ~nsCSPReportURI(); bool visit(nsCSPSrcVisitor* aVisitor) const override; void toString(nsAString& outStr) const override; private: nsCOMPtr mReportURI; }; /* =============== nsCSPSandboxFlags ================== */ class nsCSPSandboxFlags : public nsCSPBaseSrc { public: explicit nsCSPSandboxFlags(const nsAString& aFlags); virtual ~nsCSPSandboxFlags(); bool visit(nsCSPSrcVisitor* aVisitor) const override; void toString(nsAString& outStr) const override; private: nsString mFlags; }; /* =============== nsCSPSrcVisitor ================== */ class nsCSPSrcVisitor { public: virtual bool visitSchemeSrc(const nsCSPSchemeSrc& src) = 0; virtual bool visitHostSrc(const nsCSPHostSrc& src) = 0; virtual bool visitKeywordSrc(const nsCSPKeywordSrc& src) = 0; virtual bool visitNonceSrc(const nsCSPNonceSrc& src) = 0; virtual bool visitHashSrc(const nsCSPHashSrc& src) = 0; protected: explicit nsCSPSrcVisitor() = default; virtual ~nsCSPSrcVisitor() = default; }; /* =============== nsCSPDirective ============= */ class nsCSPDirective { public: explicit nsCSPDirective(CSPDirective aDirective); virtual ~nsCSPDirective(); virtual bool permits(nsIURI* aUri, const nsAString& aNonce, bool aWasRedirected, bool aReportOnly, bool aUpgradeInsecure, bool aParserCreated) const; virtual bool allows(enum CSPKeyword aKeyword, const nsAString& aHashOrNonce, bool aParserCreated) const; virtual void toString(nsAString& outStr) const; void toDomCSPStruct(mozilla::dom::CSP& outCSP) const; virtual void addSrcs(const nsTArray& aSrcs) { mSrcs = aSrcs.Clone(); } virtual bool restrictsContentType(nsContentPolicyType aContentType) const; inline bool isDefaultDirective() const { return mDirective == nsIContentSecurityPolicy::DEFAULT_SRC_DIRECTIVE; } virtual bool equals(CSPDirective aDirective) const; void getReportURIs(nsTArray& outReportURIs) const; bool visitSrcs(nsCSPSrcVisitor* aVisitor) const; virtual void getDirName(nsAString& outStr) const; bool hasReportSampleKeyword() const; protected: CSPDirective mDirective; nsTArray mSrcs; }; /* =============== nsCSPChildSrcDirective ============= */ /* * In CSP 3 child-src is deprecated. For backwards compatibility * child-src needs to restrict: * (*) frames, in case frame-src is not expicitly specified * (*) workers, in case worker-src is not expicitly specified */ class nsCSPChildSrcDirective : public nsCSPDirective { public: explicit nsCSPChildSrcDirective(CSPDirective aDirective); virtual ~nsCSPChildSrcDirective(); void setRestrictFrames() { mRestrictFrames = true; } void setRestrictWorkers() { mRestrictWorkers = true; } virtual bool restrictsContentType( nsContentPolicyType aContentType) const override; virtual bool equals(CSPDirective aDirective) const override; private: bool mRestrictFrames; bool mRestrictWorkers; }; /* =============== nsCSPScriptSrcDirective ============= */ /* * In CSP 3 worker-src restricts workers, for backwards compatibily * script-src has to restrict workers as the ultimate fallback if * neither worker-src nor child-src is present in a CSP. */ class nsCSPScriptSrcDirective : public nsCSPDirective { public: explicit nsCSPScriptSrcDirective(CSPDirective aDirective); virtual ~nsCSPScriptSrcDirective(); void setRestrictWorkers() { mRestrictWorkers = true; } virtual bool restrictsContentType( nsContentPolicyType aContentType) const override; virtual bool equals(CSPDirective aDirective) const override; private: bool mRestrictWorkers; }; /* =============== nsBlockAllMixedContentDirective === */ class nsBlockAllMixedContentDirective : public nsCSPDirective { public: explicit nsBlockAllMixedContentDirective(CSPDirective aDirective); ~nsBlockAllMixedContentDirective(); bool permits(nsIURI* aUri, const nsAString& aNonce, bool aWasRedirected, bool aReportOnly, bool aUpgradeInsecure, bool aParserCreated) const override { return false; } bool permits(nsIURI* aUri) const { return false; } bool allows(enum CSPKeyword aKeyword, const nsAString& aHashOrNonce, bool aParserCreated) const override { return false; } void toString(nsAString& outStr) const override; void addSrcs(const nsTArray& aSrcs) override { MOZ_ASSERT(false, "block-all-mixed-content does not hold any srcs"); } void getDirName(nsAString& outStr) const override; }; /* =============== nsUpgradeInsecureDirective === */ /* * Upgrading insecure requests includes the following actors: * (1) CSP: * The CSP implementation allowlists the http-request * in case the policy is executed in enforcement mode. * The CSP implementation however does not allow http * requests to succeed if executed in report-only mode. * In such a case the CSP implementation reports the * error back to the page. * * (2) MixedContent: * The evalution of MixedContent allowlists all http * requests with the promise that the http requests * gets upgraded to https before any data is fetched * from the network. * * (3) CORS: * Does not consider the http request to be of a * different origin in case the scheme is the only * difference in otherwise matching URIs. * * (4) nsHttpChannel: * Before connecting, the channel gets redirected * to use https. * * (5) WebSocketChannel: * Similar to the httpChannel, the websocketchannel * gets upgraded from ws to wss. */ class nsUpgradeInsecureDirective : public nsCSPDirective { public: explicit nsUpgradeInsecureDirective(CSPDirective aDirective); ~nsUpgradeInsecureDirective(); bool permits(nsIURI* aUri, const nsAString& aNonce, bool aWasRedirected, bool aReportOnly, bool aUpgradeInsecure, bool aParserCreated) const override { return false; } bool permits(nsIURI* aUri) const { return false; } bool allows(enum CSPKeyword aKeyword, const nsAString& aHashOrNonce, bool aParserCreated) const override { return false; } void toString(nsAString& outStr) const override; void addSrcs(const nsTArray& aSrcs) override { MOZ_ASSERT(false, "upgrade-insecure-requests does not hold any srcs"); } void getDirName(nsAString& outStr) const override; }; /* =============== nsCSPPolicy ================== */ class nsCSPPolicy { public: nsCSPPolicy(); virtual ~nsCSPPolicy(); bool permits(CSPDirective aDirective, nsIURI* aUri, const nsAString& aNonce, bool aWasRedirected, bool aSpecific, bool aParserCreated, nsAString& outViolatedDirective) const; bool permits(CSPDirective aDir, nsIURI* aUri, bool aSpecific) const; bool allows(nsContentPolicyType aContentType, enum CSPKeyword aKeyword, const nsAString& aHashOrNonce, bool aParserCreated) 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); } inline void addUpgradeInsecDir(nsUpgradeInsecureDirective* aDir) { mUpgradeInsecDir = aDir; addDirective(aDir); } bool hasDirective(CSPDirective aDir) const; inline void setDeliveredViaMetaTagFlag(bool aFlag) { mDeliveredViaMetaTag = aFlag; } inline bool getDeliveredViaMetaTagFlag() const { return mDeliveredViaMetaTag; } inline void setReportOnlyFlag(bool aFlag) { mReportOnly = aFlag; } inline bool getReportOnlyFlag() const { return mReportOnly; } void getReportURIs(nsTArray& outReportURIs) const; void getDirectiveStringAndReportSampleForContentType( nsContentPolicyType aContentType, nsAString& outDirective, bool* aReportSample) const; void getDirectiveAsString(CSPDirective aDir, nsAString& outDirective) const; uint32_t getSandboxFlags() const; inline uint32_t getNumDirectives() const { return mDirectives.Length(); } bool visitDirectiveSrcs(CSPDirective aDir, nsCSPSrcVisitor* aVisitor) const; bool allowsNavigateTo(nsIURI* aURI, bool aWasRedirected, bool aEnforceAllowlist) const; private: nsUpgradeInsecureDirective* mUpgradeInsecDir; nsTArray mDirectives; bool mReportOnly; bool mDeliveredViaMetaTag; }; #endif /* nsCSPUtils_h___ */