From eb50fdee9b13260cdcc8bb892b7c0e87401dba3c Mon Sep 17 00:00:00 2001 From: "fabien.menager" Date: Tue, 14 Nov 2023 13:29:25 +0100 Subject: [PATCH] Add support for script-src-elem, script-src-attr, style-src-elem and style-src-attr, (https://w3c.github.io/webappsec-csp/#directive-style-src-elem) --- .../Csp/Builder/CspBuilder.cs | 20 +++ .../Csp/Builder/CspScriptAttributesBuilder.cs | 12 ++ .../Csp/Builder/CspScriptElementsBuilder.cs | 12 ++ .../Csp/Builder/CspScriptsBuilder.cs | 133 +--------------- .../Csp/Builder/CspScriptsBuilderBase.cs | 142 ++++++++++++++++++ .../Csp/Builder/CspStyleAttributesBuilder.cs | 12 ++ .../Csp/Builder/CspStyleElementsBuilder.cs | 12 ++ .../Csp/Builder/CspStylesBuilder.cs | 106 +------------ .../Csp/Builder/CspStylesBuilderBase.cs | 115 ++++++++++++++ .../Options/CspScriptSrcAttributeOptions.cs | 9 ++ .../Csp/Options/CspScriptSrcElementOptions.cs | 9 ++ .../Csp/Options/CspScriptSrcOptions.cs | 71 +-------- .../Csp/Options/CspScriptSrcOptionsBase.cs | 74 +++++++++ .../Options/CspStyleSrcAttributeOptions.cs | 9 ++ .../Csp/Options/CspStyleSrcElementOptions.cs | 9 ++ .../Csp/Options/CspStyleSrcOptions.cs | 49 +----- .../Csp/Options/CspStyleSrcOptionsBase.cs | 52 +++++++ .../CspOptions.cs | 24 +++ .../CspOptionsTests.cs | 25 +++ 19 files changed, 546 insertions(+), 349 deletions(-) create mode 100644 src/Joonasw.AspNetCore.SecurityHeaders/Csp/Builder/CspScriptAttributesBuilder.cs create mode 100644 src/Joonasw.AspNetCore.SecurityHeaders/Csp/Builder/CspScriptElementsBuilder.cs create mode 100644 src/Joonasw.AspNetCore.SecurityHeaders/Csp/Builder/CspScriptsBuilderBase.cs create mode 100644 src/Joonasw.AspNetCore.SecurityHeaders/Csp/Builder/CspStyleAttributesBuilder.cs create mode 100644 src/Joonasw.AspNetCore.SecurityHeaders/Csp/Builder/CspStyleElementsBuilder.cs create mode 100644 src/Joonasw.AspNetCore.SecurityHeaders/Csp/Builder/CspStylesBuilderBase.cs create mode 100644 src/Joonasw.AspNetCore.SecurityHeaders/Csp/Options/CspScriptSrcAttributeOptions.cs create mode 100644 src/Joonasw.AspNetCore.SecurityHeaders/Csp/Options/CspScriptSrcElementOptions.cs create mode 100644 src/Joonasw.AspNetCore.SecurityHeaders/Csp/Options/CspScriptSrcOptionsBase.cs create mode 100644 src/Joonasw.AspNetCore.SecurityHeaders/Csp/Options/CspStyleSrcAttributeOptions.cs create mode 100644 src/Joonasw.AspNetCore.SecurityHeaders/Csp/Options/CspStyleSrcElementOptions.cs create mode 100644 src/Joonasw.AspNetCore.SecurityHeaders/Csp/Options/CspStyleSrcOptionsBase.cs diff --git a/src/Joonasw.AspNetCore.SecurityHeaders/Csp/Builder/CspBuilder.cs b/src/Joonasw.AspNetCore.SecurityHeaders/Csp/Builder/CspBuilder.cs index f858a5f..ab7a13b 100644 --- a/src/Joonasw.AspNetCore.SecurityHeaders/Csp/Builder/CspBuilder.cs +++ b/src/Joonasw.AspNetCore.SecurityHeaders/Csp/Builder/CspBuilder.cs @@ -13,10 +13,26 @@ public class CspBuilder /// public CspScriptsBuilder AllowScripts { get; } = new CspScriptsBuilder(); /// + /// Set up rules for JavaScript attributes. + /// + public CspScriptAttributesBuilder AllowScriptAttributes { get; } = new CspScriptAttributesBuilder(); + /// + /// Set up rules for JavaScript elements. + /// + public CspScriptElementsBuilder AllowScriptElements { get; } = new CspScriptElementsBuilder(); + /// /// Set up rules for CSS. /// public CspStylesBuilder AllowStyles { get; } = new CspStylesBuilder(); /// + /// Set up rules for CSS attributes. + /// + public CspStyleAttributesBuilder AllowStyleAttributes { get; } = new CspStyleAttributesBuilder(); + /// + /// Set up rules for CSS elements. + /// + public CspStyleElementsBuilder AllowStyleElements { get; } = new CspStyleElementsBuilder(); + /// /// Set up default rules for resources for which no rules exist. /// public CspDefaultBuilder ByDefaultAllow { get; } = new CspDefaultBuilder(); @@ -129,7 +145,11 @@ public void SetBlockAllMixedContent() public CspOptions BuildCspOptions() { _options.Script = AllowScripts.BuildOptions(); + _options.ScriptAttribute = AllowScriptAttributes.BuildOptions(); + _options.ScriptElement = AllowScriptElements.BuildOptions(); _options.Style = AllowStyles.BuildOptions(); + _options.StyleAttribute = AllowStyleAttributes.BuildOptions(); + _options.StyleElement = AllowStyleElements.BuildOptions(); #pragma warning disable CS0618 // Type or member is obsolete _options.Child = AllowChildren.BuildOptions(); #pragma warning restore CS0618 // Type or member is obsolete diff --git a/src/Joonasw.AspNetCore.SecurityHeaders/Csp/Builder/CspScriptAttributesBuilder.cs b/src/Joonasw.AspNetCore.SecurityHeaders/Csp/Builder/CspScriptAttributesBuilder.cs new file mode 100644 index 0000000..04f76d7 --- /dev/null +++ b/src/Joonasw.AspNetCore.SecurityHeaders/Csp/Builder/CspScriptAttributesBuilder.cs @@ -0,0 +1,12 @@ +using Joonasw.AspNetCore.SecurityHeaders.Csp.Options; + +namespace Joonasw.AspNetCore.SecurityHeaders.Csp.Builder +{ + /// + /// Builder for Content Security Policy + /// rules related to JavaScript attributes. + /// + public class CspScriptAttributesBuilder : CspScriptsBuilderBase + { + } +} diff --git a/src/Joonasw.AspNetCore.SecurityHeaders/Csp/Builder/CspScriptElementsBuilder.cs b/src/Joonasw.AspNetCore.SecurityHeaders/Csp/Builder/CspScriptElementsBuilder.cs new file mode 100644 index 0000000..2447a25 --- /dev/null +++ b/src/Joonasw.AspNetCore.SecurityHeaders/Csp/Builder/CspScriptElementsBuilder.cs @@ -0,0 +1,12 @@ +using Joonasw.AspNetCore.SecurityHeaders.Csp.Options; + +namespace Joonasw.AspNetCore.SecurityHeaders.Csp.Builder +{ + /// + /// Builder for Content Security Policy + /// rules related to JavaScript elements. + /// + public class CspScriptElementsBuilder : CspScriptsBuilderBase + { + } +} diff --git a/src/Joonasw.AspNetCore.SecurityHeaders/Csp/Builder/CspScriptsBuilder.cs b/src/Joonasw.AspNetCore.SecurityHeaders/Csp/Builder/CspScriptsBuilder.cs index 6fc6301..9ed70a8 100644 --- a/src/Joonasw.AspNetCore.SecurityHeaders/Csp/Builder/CspScriptsBuilder.cs +++ b/src/Joonasw.AspNetCore.SecurityHeaders/Csp/Builder/CspScriptsBuilder.cs @@ -1,5 +1,4 @@ -using System; -using Joonasw.AspNetCore.SecurityHeaders.Csp.Options; +using Joonasw.AspNetCore.SecurityHeaders.Csp.Options; namespace Joonasw.AspNetCore.SecurityHeaders.Csp.Builder { @@ -7,135 +6,7 @@ namespace Joonasw.AspNetCore.SecurityHeaders.Csp.Builder /// Builder for Content Security Policy /// rules related to JavaScript. /// - public class CspScriptsBuilder + public class CspScriptsBuilder : CspScriptsBuilderBase { - private readonly CspScriptSrcOptions _options = new CspScriptSrcOptions(); - - /// - /// Block all JavaScript. - /// - public void FromNowhere() - { - _options.AllowNone = true; - } - - /// - /// Allow JavaScript from current domain. - /// - /// The builder for call chaining - public CspScriptsBuilder FromSelf() - { - _options.AllowSelf = true; - return this; - } - - /// - /// Allow JavaScript from the given - /// . - /// - /// The URI to allow. - /// The builder for call chaining - public CspScriptsBuilder From(string uri) - { - if (uri == null) throw new ArgumentNullException(nameof(uri)); - if (uri.Length == 0) throw new ArgumentException("Uri can't be empty", nameof(uri)); - - _options.AllowedSources.Add(uri); - return this; - } - - /// - /// Allow JavaScript with the given - /// . - /// - /// The hash to allow. - /// The builder for call chaining - public CspScriptsBuilder WithHash(string hash) - { - if (hash == null) throw new ArgumentNullException(nameof(hash)); - if (hash.Length == 0) throw new ArgumentException("Hash can't be empty", nameof(hash)); - - _options.AllowedHashes.Add(hash); - return this; - } - - /// - /// Allow inline scripts. - /// If you have an XSS vulnerability, this will allow them to execute. - /// Cannot be enabled together with AddNonce. - /// - /// The builder for call chaining - public CspScriptsBuilder AllowUnsafeInline() - { - _options.AllowUnsafeInline = true; - return this; - } - - /// - /// Allow usage of eval(). Do not enable unless you really - /// need it. - /// - /// The builder for call chaining - public CspScriptsBuilder AllowUnsafeEval() - { - _options.AllowUnsafeEval = true; - return this; - } - - /// - /// Generates a random nonce for each request and - /// adds it to the CSP header. Allows inline scripts that - /// have the nonce attribute set to the random value on - /// the script element. - /// Note that you must call AddCsp() on the service collection - /// in ConfigureServices() to add the necessary dependencies. - /// - /// The builder for call chaining - public CspScriptsBuilder AddNonce() - { - _options.AddNonce = true; - return this; - } - - /// - /// Allow JavaScript from anywhere, except - /// data:, blob:, and filesystem: schemes. - /// - /// The builder for call chaining - public CspScriptsBuilder FromAnywhere() - { - _options.AllowAny = true; - return this; - } - - /// - /// Allow JavaScript only over secure connections. - /// - /// The builder for call chaining - public CspScriptsBuilder OnlyOverHttps() - { - _options.AllowOnlyHttps = true; - return this; - } - - /// - /// Allow scripts that have been loaded with - /// a trusted hash/nonce to load additional - /// scripts. - /// This enabled a "strict" mode - /// for scripts, requiring a hash or nonce - /// on all of them. - /// - /// The builder for call chaining - public CspScriptsBuilder WithStrictDynamic() - { - _options.StrictDynamic = true; - return this; - } - - public CspScriptSrcOptions BuildOptions() - { - return _options; - } } } diff --git a/src/Joonasw.AspNetCore.SecurityHeaders/Csp/Builder/CspScriptsBuilderBase.cs b/src/Joonasw.AspNetCore.SecurityHeaders/Csp/Builder/CspScriptsBuilderBase.cs new file mode 100644 index 0000000..5979ee1 --- /dev/null +++ b/src/Joonasw.AspNetCore.SecurityHeaders/Csp/Builder/CspScriptsBuilderBase.cs @@ -0,0 +1,142 @@ +using System; +using Joonasw.AspNetCore.SecurityHeaders.Csp.Options; + +namespace Joonasw.AspNetCore.SecurityHeaders.Csp.Builder +{ + /// + /// Builder for Content Security Policy + /// rules related to JavaScript. + /// + public abstract class CspScriptsBuilderBase + where TOptions : CspScriptSrcOptionsBase, new() + { + private readonly TOptions _options = new TOptions(); + + /// + /// Block all JavaScript. + /// + public void FromNowhere() + { + _options.AllowNone = true; + } + + /// + /// Allow JavaScript from current domain. + /// + /// The builder for call chaining + public CspScriptsBuilderBase FromSelf() + { + _options.AllowSelf = true; + return this; + } + + /// + /// Allow JavaScript from the given + /// . + /// + /// The URI to allow. + /// The builder for call chaining + public CspScriptsBuilderBase From(string uri) + { + if (uri == null) throw new ArgumentNullException(nameof(uri)); + if (uri.Length == 0) throw new ArgumentException("Uri can't be empty", nameof(uri)); + + _options.AllowedSources.Add(uri); + return this; + } + + /// + /// Allow JavaScript with the given + /// . + /// + /// The hash to allow. + /// The builder for call chaining + public CspScriptsBuilderBase WithHash(string hash) + { + if (hash == null) throw new ArgumentNullException(nameof(hash)); + if (hash.Length == 0) throw new ArgumentException("Hash can't be empty", nameof(hash)); + + _options.AllowedHashes.Add(hash); + return this; + } + + /// + /// Allow inline scripts. + /// If you have an XSS vulnerability, this will allow them to execute. + /// Cannot be enabled together with AddNonce. + /// + /// The builder for call chaining + public CspScriptsBuilderBase AllowUnsafeInline() + { + _options.AllowUnsafeInline = true; + return this; + } + + /// + /// Allow usage of eval(). Do not enable unless you really + /// need it. + /// + /// The builder for call chaining + public CspScriptsBuilderBase AllowUnsafeEval() + { + _options.AllowUnsafeEval = true; + return this; + } + + /// + /// Generates a random nonce for each request and + /// adds it to the CSP header. Allows inline scripts that + /// have the nonce attribute set to the random value on + /// the script element. + /// Note that you must call AddCsp() on the service collection + /// in ConfigureServices() to add the necessary dependencies. + /// + /// The builder for call chaining + public CspScriptsBuilderBase AddNonce() + { + _options.AddNonce = true; + return this; + } + + /// + /// Allow JavaScript from anywhere, except + /// data:, blob:, and filesystem: schemes. + /// + /// The builder for call chaining + public CspScriptsBuilderBase FromAnywhere() + { + _options.AllowAny = true; + return this; + } + + /// + /// Allow JavaScript only over secure connections. + /// + /// The builder for call chaining + public CspScriptsBuilderBase OnlyOverHttps() + { + _options.AllowOnlyHttps = true; + return this; + } + + /// + /// Allow scripts that have been loaded with + /// a trusted hash/nonce to load additional + /// scripts. + /// This enabled a "strict" mode + /// for scripts, requiring a hash or nonce + /// on all of them. + /// + /// The builder for call chaining + public CspScriptsBuilderBase WithStrictDynamic() + { + _options.StrictDynamic = true; + return this; + } + + public TOptions BuildOptions() + { + return _options; + } + } +} diff --git a/src/Joonasw.AspNetCore.SecurityHeaders/Csp/Builder/CspStyleAttributesBuilder.cs b/src/Joonasw.AspNetCore.SecurityHeaders/Csp/Builder/CspStyleAttributesBuilder.cs new file mode 100644 index 0000000..99c3db0 --- /dev/null +++ b/src/Joonasw.AspNetCore.SecurityHeaders/Csp/Builder/CspStyleAttributesBuilder.cs @@ -0,0 +1,12 @@ +using Joonasw.AspNetCore.SecurityHeaders.Csp.Options; + +namespace Joonasw.AspNetCore.SecurityHeaders.Csp.Builder +{ + /// + /// Builder for Content Security Policy + /// rules related to CSS attributes. + /// + public class CspStyleAttributesBuilder : CspStylesBuilderBase + { + } +} diff --git a/src/Joonasw.AspNetCore.SecurityHeaders/Csp/Builder/CspStyleElementsBuilder.cs b/src/Joonasw.AspNetCore.SecurityHeaders/Csp/Builder/CspStyleElementsBuilder.cs new file mode 100644 index 0000000..91d8810 --- /dev/null +++ b/src/Joonasw.AspNetCore.SecurityHeaders/Csp/Builder/CspStyleElementsBuilder.cs @@ -0,0 +1,12 @@ +using Joonasw.AspNetCore.SecurityHeaders.Csp.Options; + +namespace Joonasw.AspNetCore.SecurityHeaders.Csp.Builder +{ + /// + /// Builder for Content Security Policy + /// rules related to CSS elements. + /// + public class CspStyleElementsBuilder : CspStylesBuilderBase + { + } +} diff --git a/src/Joonasw.AspNetCore.SecurityHeaders/Csp/Builder/CspStylesBuilder.cs b/src/Joonasw.AspNetCore.SecurityHeaders/Csp/Builder/CspStylesBuilder.cs index 49d9324..4593775 100644 --- a/src/Joonasw.AspNetCore.SecurityHeaders/Csp/Builder/CspStylesBuilder.cs +++ b/src/Joonasw.AspNetCore.SecurityHeaders/Csp/Builder/CspStylesBuilder.cs @@ -1,5 +1,4 @@ -using System; -using Joonasw.AspNetCore.SecurityHeaders.Csp.Options; +using Joonasw.AspNetCore.SecurityHeaders.Csp.Options; namespace Joonasw.AspNetCore.SecurityHeaders.Csp.Builder { @@ -7,108 +6,7 @@ namespace Joonasw.AspNetCore.SecurityHeaders.Csp.Builder /// Builder for Content Security Policy /// rules related to CSS. /// - public class CspStylesBuilder + public class CspStylesBuilder : CspStylesBuilderBase { - private readonly CspStyleSrcOptions _options = new CspStyleSrcOptions(); - - /// - /// Block all CSS. - /// - public void FromNowhere() - { - _options.AllowNone = true; - } - - /// - /// Allow CSS from current domain. - /// - /// The builder for call chaining - public CspStylesBuilder FromSelf() - { - _options.AllowSelf = true; - return this; - } - - /// - /// Allow CSS from the given - /// . - /// - /// The URI to allow. - /// The builder for call chaining - public CspStylesBuilder From(string uri) - { - if (uri == null) throw new ArgumentNullException(nameof(uri)); - if (uri.Length == 0) throw new ArgumentException("Uri can't be empty", nameof(uri)); - - _options.AllowedSources.Add(uri); - return this; - } - - /// - /// Allow CSS with the given - /// . - /// - /// The hash to allow. - /// The builder for call chaining - public CspStylesBuilder WithHash(string hash) - { - if (hash == null) throw new ArgumentNullException(nameof(hash)); - if (hash.Length == 0) throw new ArgumentException("Hash can't be empty", nameof(hash)); - - _options.AllowedHashes.Add(hash); - return this; - } - - /// - /// Allow CSS from anywhere, except - /// data:, blob:, and filesystem: schemes. - /// - /// The builder for call chaining - public CspStylesBuilder FromAnywhere() - { - _options.AllowAny = true; - return this; - } - - /// - /// Allow inline CSS. - /// Cannot be enabled together with AddNonce. - /// - /// The builder for call chaining - public CspStylesBuilder AllowUnsafeInline() - { - _options.AllowUnsafeInline = true; - return this; - } - - /// - /// Generates a random nonce for each request and - /// adds it to the CSP header. Allows inline styles that - /// have the nonce attribute set to the random value on - /// the script element. - /// Note that you must call AddCsp() on the service collection - /// in ConfigureServices() to add the necessary dependencies. - /// - /// The builder for call chaining - public CspStylesBuilder AddNonce() - { - _options.AddNonce = true; - return this; - } - - /// - /// Allow CSS only over secure connections. - /// - /// The builder for call chaining - public CspStylesBuilder OnlyOverHttps() - { - _options.AllowOnlyHttps = true; - return this; - } - - public CspStyleSrcOptions BuildOptions() - { - return _options; - } } } diff --git a/src/Joonasw.AspNetCore.SecurityHeaders/Csp/Builder/CspStylesBuilderBase.cs b/src/Joonasw.AspNetCore.SecurityHeaders/Csp/Builder/CspStylesBuilderBase.cs new file mode 100644 index 0000000..ae86d32 --- /dev/null +++ b/src/Joonasw.AspNetCore.SecurityHeaders/Csp/Builder/CspStylesBuilderBase.cs @@ -0,0 +1,115 @@ +using System; +using Joonasw.AspNetCore.SecurityHeaders.Csp.Options; + +namespace Joonasw.AspNetCore.SecurityHeaders.Csp.Builder +{ + /// + /// Builder for Content Security Policy + /// rules related to CSS. + /// + public abstract class CspStylesBuilderBase + where TOptions : CspStyleSrcOptionsBase, new() + { + protected readonly TOptions _options = new TOptions(); + + /// + /// Block all CSS. + /// + public void FromNowhere() + { + _options.AllowNone = true; + } + + /// + /// Allow CSS from current domain. + /// + /// The builder for call chaining + public CspStylesBuilderBase FromSelf() + { + _options.AllowSelf = true; + return this; + } + + /// + /// Allow CSS from the given + /// . + /// + /// The URI to allow. + /// The builder for call chaining + public CspStylesBuilderBase From(string uri) + { + if (uri == null) throw new ArgumentNullException(nameof(uri)); + if (uri.Length == 0) throw new ArgumentException("Uri can't be empty", nameof(uri)); + + _options.AllowedSources.Add(uri); + return this; + } + + /// + /// Allow CSS with the given + /// . + /// + /// The hash to allow. + /// The builder for call chaining + public CspStylesBuilderBase WithHash(string hash) + { + if (hash == null) throw new ArgumentNullException(nameof(hash)); + if (hash.Length == 0) throw new ArgumentException("Hash can't be empty", nameof(hash)); + + _options.AllowedHashes.Add(hash); + return this; + } + + /// + /// Allow CSS from anywhere, except + /// data:, blob:, and filesystem: schemes. + /// + /// The builder for call chaining + public CspStylesBuilderBase FromAnywhere() + { + _options.AllowAny = true; + return this; + } + + /// + /// Allow inline CSS. + /// Cannot be enabled together with AddNonce. + /// + /// The builder for call chaining + public CspStylesBuilderBase AllowUnsafeInline() + { + _options.AllowUnsafeInline = true; + return this; + } + + /// + /// Generates a random nonce for each request and + /// adds it to the CSP header. Allows inline styles that + /// have the nonce attribute set to the random value on + /// the script element. + /// Note that you must call AddCsp() on the service collection + /// in ConfigureServices() to add the necessary dependencies. + /// + /// The builder for call chaining + public CspStylesBuilderBase AddNonce() + { + _options.AddNonce = true; + return this; + } + + /// + /// Allow CSS only over secure connections. + /// + /// The builder for call chaining + public CspStylesBuilderBase OnlyOverHttps() + { + _options.AllowOnlyHttps = true; + return this; + } + + public TOptions BuildOptions() + { + return _options; + } + } +} diff --git a/src/Joonasw.AspNetCore.SecurityHeaders/Csp/Options/CspScriptSrcAttributeOptions.cs b/src/Joonasw.AspNetCore.SecurityHeaders/Csp/Options/CspScriptSrcAttributeOptions.cs new file mode 100644 index 0000000..c21f6c5 --- /dev/null +++ b/src/Joonasw.AspNetCore.SecurityHeaders/Csp/Options/CspScriptSrcAttributeOptions.cs @@ -0,0 +1,9 @@ +namespace Joonasw.AspNetCore.SecurityHeaders.Csp.Options +{ + public class CspScriptSrcAttributeOptions : CspScriptSrcOptionsBase + { + public CspScriptSrcAttributeOptions() : base("script-src-attr") + { + } + } +} diff --git a/src/Joonasw.AspNetCore.SecurityHeaders/Csp/Options/CspScriptSrcElementOptions.cs b/src/Joonasw.AspNetCore.SecurityHeaders/Csp/Options/CspScriptSrcElementOptions.cs new file mode 100644 index 0000000..66ce82f --- /dev/null +++ b/src/Joonasw.AspNetCore.SecurityHeaders/Csp/Options/CspScriptSrcElementOptions.cs @@ -0,0 +1,9 @@ +namespace Joonasw.AspNetCore.SecurityHeaders.Csp.Options +{ + public class CspScriptSrcElementOptions : CspScriptSrcOptionsBase + { + public CspScriptSrcElementOptions() : base("script-src-elem") + { + } + } +} diff --git a/src/Joonasw.AspNetCore.SecurityHeaders/Csp/Options/CspScriptSrcOptions.cs b/src/Joonasw.AspNetCore.SecurityHeaders/Csp/Options/CspScriptSrcOptions.cs index 2e1df1c..7ced246 100644 --- a/src/Joonasw.AspNetCore.SecurityHeaders/Csp/Options/CspScriptSrcOptions.cs +++ b/src/Joonasw.AspNetCore.SecurityHeaders/Csp/Options/CspScriptSrcOptions.cs @@ -1,74 +1,9 @@ -using System; -using System.Collections.Generic; - -namespace Joonasw.AspNetCore.SecurityHeaders.Csp.Options +namespace Joonasw.AspNetCore.SecurityHeaders.Csp.Options { - public class CspScriptSrcOptions : CspSrcOptionsBase + public class CspScriptSrcOptions : CspScriptSrcOptionsBase { - public bool AddNonce { get; set; } - - public bool AllowUnsafeEval { get; set; } - - public bool AllowUnsafeInline { get; set; } - - /// - /// Allow scripts that have been loaded with - /// a trusted hash/nonce to load additional - /// scripts. - /// This enabled a "strict" mode - /// for scripts, requiring a hash or nonce - /// on all of them. - /// - public bool StrictDynamic { get; set; } - - /// - /// Collection of hashes that can be loaded. - /// - public ICollection AllowedHashes { get; set; } - - public CspScriptSrcOptions() - : base("script-src") + public CspScriptSrcOptions() : base("script-src") { - AllowedHashes = new List(); - } - - protected override ICollection GetParts(ICspNonceService nonceService) - { - ICollection parts = base.GetParts(nonceService); - - foreach (string allowedHash in AllowedHashes) - { - parts.Add($"'{allowedHash}'"); - } - - if (AddNonce) - { - if (nonceService == null) - { - throw new ArgumentNullException( - nameof(nonceService), - "Nonce service was not found, it needs to be added to the service collection"); - } - - parts.Add($"'nonce-{nonceService.GetNonce()}'"); - } - - if (AllowUnsafeEval) - { - parts.Add("'unsafe-eval'"); - } - - if (AllowUnsafeInline) - { - parts.Add("'unsafe-inline'"); - } - - if (StrictDynamic) - { - parts.Add("'strict-dynamic'"); - } - - return parts; } } } diff --git a/src/Joonasw.AspNetCore.SecurityHeaders/Csp/Options/CspScriptSrcOptionsBase.cs b/src/Joonasw.AspNetCore.SecurityHeaders/Csp/Options/CspScriptSrcOptionsBase.cs new file mode 100644 index 0000000..958e8d9 --- /dev/null +++ b/src/Joonasw.AspNetCore.SecurityHeaders/Csp/Options/CspScriptSrcOptionsBase.cs @@ -0,0 +1,74 @@ +using System; +using System.Collections.Generic; + +namespace Joonasw.AspNetCore.SecurityHeaders.Csp.Options +{ + public abstract class CspScriptSrcOptionsBase : CspSrcOptionsBase + { + public bool AddNonce { get; set; } + + public bool AllowUnsafeEval { get; set; } + + public bool AllowUnsafeInline { get; set; } + + /// + /// Allow scripts that have been loaded with + /// a trusted hash/nonce to load additional + /// scripts. + /// This enabled a "strict" mode + /// for scripts, requiring a hash or nonce + /// on all of them. + /// + public bool StrictDynamic { get; set; } + + /// + /// Collection of hashes that can be loaded. + /// + public ICollection AllowedHashes { get; set; } + + protected CspScriptSrcOptionsBase(string directiveName) + : base(directiveName) + { + AllowedHashes = new List(); + } + + protected override ICollection GetParts(ICspNonceService nonceService) + { + ICollection parts = base.GetParts(nonceService); + + foreach (string allowedHash in AllowedHashes) + { + parts.Add($"'{allowedHash}'"); + } + + if (AddNonce) + { + if (nonceService == null) + { + throw new ArgumentNullException( + nameof(nonceService), + "Nonce service was not found, it needs to be added to the service collection"); + } + + parts.Add($"'nonce-{nonceService.GetNonce()}'"); + } + + if (AllowUnsafeEval) + { + parts.Add("'unsafe-eval'"); + } + + if (AllowUnsafeInline) + { + parts.Add("'unsafe-inline'"); + } + + if (StrictDynamic) + { + parts.Add("'strict-dynamic'"); + } + + return parts; + } + } +} diff --git a/src/Joonasw.AspNetCore.SecurityHeaders/Csp/Options/CspStyleSrcAttributeOptions.cs b/src/Joonasw.AspNetCore.SecurityHeaders/Csp/Options/CspStyleSrcAttributeOptions.cs new file mode 100644 index 0000000..0cdfadc --- /dev/null +++ b/src/Joonasw.AspNetCore.SecurityHeaders/Csp/Options/CspStyleSrcAttributeOptions.cs @@ -0,0 +1,9 @@ +namespace Joonasw.AspNetCore.SecurityHeaders.Csp.Options +{ + public class CspStyleSrcAttributeOptions : CspStyleSrcOptionsBase + { + public CspStyleSrcAttributeOptions() : base("style-src-attr") + { + } + } +} diff --git a/src/Joonasw.AspNetCore.SecurityHeaders/Csp/Options/CspStyleSrcElementOptions.cs b/src/Joonasw.AspNetCore.SecurityHeaders/Csp/Options/CspStyleSrcElementOptions.cs new file mode 100644 index 0000000..7ced20c --- /dev/null +++ b/src/Joonasw.AspNetCore.SecurityHeaders/Csp/Options/CspStyleSrcElementOptions.cs @@ -0,0 +1,9 @@ +namespace Joonasw.AspNetCore.SecurityHeaders.Csp.Options +{ + public class CspStyleSrcElementOptions : CspStyleSrcOptionsBase + { + public CspStyleSrcElementOptions() : base("style-src-elem") + { + } + } +} diff --git a/src/Joonasw.AspNetCore.SecurityHeaders/Csp/Options/CspStyleSrcOptions.cs b/src/Joonasw.AspNetCore.SecurityHeaders/Csp/Options/CspStyleSrcOptions.cs index 42c0bdc..4fe3084 100644 --- a/src/Joonasw.AspNetCore.SecurityHeaders/Csp/Options/CspStyleSrcOptions.cs +++ b/src/Joonasw.AspNetCore.SecurityHeaders/Csp/Options/CspStyleSrcOptions.cs @@ -1,52 +1,9 @@ -using System; -using System.Collections.Generic; - -namespace Joonasw.AspNetCore.SecurityHeaders.Csp.Options +namespace Joonasw.AspNetCore.SecurityHeaders.Csp.Options { - public class CspStyleSrcOptions : CspSrcOptionsBase + public class CspStyleSrcOptions : CspStyleSrcOptionsBase { - public bool AddNonce { get; set; } - - public bool AllowUnsafeInline { get; set; } - - /// - /// Collection of hashes that can be loaded. - /// - public ICollection AllowedHashes { get; set; } - - public CspStyleSrcOptions() - : base("style-src") + public CspStyleSrcOptions() : base("style-src") { - AllowedHashes = new List(); - } - - protected override ICollection GetParts(ICspNonceService nonceService) - { - ICollection parts = base.GetParts(nonceService); - - foreach (string allowedHash in AllowedHashes) - { - parts.Add($"'{allowedHash}'"); - } - - if (AddNonce) - { - if (nonceService == null) - { - throw new ArgumentNullException( - nameof(nonceService), - "Nonce service was not found, it needs to be added to the service collection"); - } - - parts.Add($"'nonce-{nonceService.GetNonce()}'"); - } - - if (AllowUnsafeInline) - { - parts.Add("'unsafe-inline'"); - } - - return parts; } } } diff --git a/src/Joonasw.AspNetCore.SecurityHeaders/Csp/Options/CspStyleSrcOptionsBase.cs b/src/Joonasw.AspNetCore.SecurityHeaders/Csp/Options/CspStyleSrcOptionsBase.cs new file mode 100644 index 0000000..b59ce39 --- /dev/null +++ b/src/Joonasw.AspNetCore.SecurityHeaders/Csp/Options/CspStyleSrcOptionsBase.cs @@ -0,0 +1,52 @@ +using System; +using System.Collections.Generic; + +namespace Joonasw.AspNetCore.SecurityHeaders.Csp.Options +{ + public abstract class CspStyleSrcOptionsBase : CspSrcOptionsBase + { + public bool AddNonce { get; set; } + + public bool AllowUnsafeInline { get; set; } + + /// + /// Collection of hashes that can be loaded. + /// + public ICollection AllowedHashes { get; set; } + + protected CspStyleSrcOptionsBase(string directiveName) + : base(directiveName) + { + AllowedHashes = new List(); + } + + protected override ICollection GetParts(ICspNonceService nonceService) + { + ICollection parts = base.GetParts(nonceService); + + foreach (string allowedHash in AllowedHashes) + { + parts.Add($"'{allowedHash}'"); + } + + if (AddNonce) + { + if (nonceService == null) + { + throw new ArgumentNullException( + nameof(nonceService), + "Nonce service was not found, it needs to be added to the service collection"); + } + + parts.Add($"'nonce-{nonceService.GetNonce()}'"); + } + + if (AllowUnsafeInline) + { + parts.Add("'unsafe-inline'"); + } + + return parts; + } + } +} diff --git a/src/Joonasw.AspNetCore.SecurityHeaders/CspOptions.cs b/src/Joonasw.AspNetCore.SecurityHeaders/CspOptions.cs index 25ab7e6..0ae72b5 100644 --- a/src/Joonasw.AspNetCore.SecurityHeaders/CspOptions.cs +++ b/src/Joonasw.AspNetCore.SecurityHeaders/CspOptions.cs @@ -18,10 +18,26 @@ public class CspOptions /// public CspScriptSrcOptions Script { get; set; } /// + /// Rules to apply for JavaScript attributes. + /// + public CspScriptSrcAttributeOptions ScriptAttribute { get; set; } + /// + /// Rules to apply for JavaScript elements. + /// + public CspScriptSrcElementOptions ScriptElement { get; set; } + /// /// Rules to apply for CSS. /// public CspStyleSrcOptions Style { get; set; } /// + /// Rules to apply for CSS attributes. + /// + public CspStyleSrcAttributeOptions StyleAttribute { get; set; } + /// + /// Rules to apply for CSS elements. + /// + public CspStyleSrcElementOptions StyleElement { get; set; } + /// /// Default rules to apply if no directive is /// present for the resource type. /// @@ -145,7 +161,11 @@ public class CspOptions public CspOptions() { Script = new CspScriptSrcOptions(); + ScriptAttribute = new CspScriptSrcAttributeOptions(); + ScriptElement = new CspScriptSrcElementOptions(); Style = new CspStyleSrcOptions(); + StyleAttribute = new CspStyleSrcAttributeOptions(); + StyleElement = new CspStyleSrcElementOptions(); Default = new CspDefaultSrcOptions(); #pragma warning disable CS0618 // Type or member is obsolete Child = new CspChildSrcOptions(); @@ -183,7 +203,11 @@ public CspOptions() { Default.ToString(nonceService), Script.ToString(nonceService), + ScriptAttribute.ToString(nonceService), + ScriptElement.ToString(nonceService), Style.ToString(nonceService), + StyleAttribute.ToString(nonceService), + StyleElement.ToString(nonceService), #pragma warning disable CS0618 // Type or member is obsolete Child.ToString(nonceService), #pragma warning restore CS0618 // Type or member is obsolete diff --git a/test/Joonasw.AspNetCore.SecurityHeaders.Tests/CspOptionsTests.cs b/test/Joonasw.AspNetCore.SecurityHeaders.Tests/CspOptionsTests.cs index f0f28bc..8cb53be 100644 --- a/test/Joonasw.AspNetCore.SecurityHeaders.Tests/CspOptionsTests.cs +++ b/test/Joonasw.AspNetCore.SecurityHeaders.Tests/CspOptionsTests.cs @@ -99,6 +99,31 @@ public void WorkerAndFrameOptions_ResultIsCorrect() Assert.Equal("frame-src *;worker-src 'self'", headerValue); } + [Fact] + public void StyleAnyElemAttr_ResultIsCorrect() + { + var options = new CspOptions + { + Style = new CspStyleSrcOptions() + { + AllowAny = true + }, + StyleAttribute = new CspStyleSrcAttributeOptions() + { + AllowAny = true, + }, + StyleElement = new CspStyleSrcElementOptions() + { + AllowAny = true, + }, + }; + + var (headerName, headerValue) = options.ToString(null); + + Assert.Equal("Content-Security-Policy", headerName); + Assert.Equal("style-src *;style-src-attr *;style-src-elem *", headerValue); + } + [Fact] public void WithoutReportOnly_HeaderNameIsCorrect() {