├── src
├── SharedAssemblyInfo.cs
├── OwinContrib.SecurityHeaders.Tests
│ ├── Properties
│ │ └── AssemblyInfo.cs
│ ├── OwinEnvironmentSpecBase.cs
│ ├── Infrastructure
│ │ └── Rfc7230UtilityTests.cs
│ ├── AssemblyTests.cs
│ ├── app.config
│ ├── packages.config
│ ├── ContentTypeOptionsMiddleware.cs
│ ├── HeaderHelper.cs
│ ├── CspUriReferenceListTests.cs
│ ├── XssProtectionHeaderMiddlewareSpecs.cs
│ ├── CsproMiddlewareTests.cs
│ ├── CspSandboxTokenListTests.cs
│ ├── HostSourceTests.cs
│ ├── CspMediaTypeListTests.cs
│ ├── CspMiddlewareTests.cs
│ ├── CspConfigurationTests.cs
│ ├── AntiClickJackingMiddlewareSpecs.cs
│ ├── CspAncestorSourceListTests.cs
│ ├── StrictTransportSecurityMiddlewareSpecs.cs
│ ├── CspSourceListTests.cs
│ └── SecurityHeadersMiddleware.Tests.csproj
├── SecurityHeadersMiddleware.Examples
│ ├── packages.config
│ ├── Properties
│ │ └── AssemblyInfo.cs
│ ├── ContentTypeOptionsExamples.cs
│ ├── XXssProtectionExamples.cs
│ ├── StrictTransportSecurityExamples.cs
│ ├── AntiClickjackingExamples.cs
│ └── SecurityHeadersMiddleware.Examples.csproj
├── OwinContrib.SecurityHeaders
│ ├── IDirectiveValueBuilder.cs
│ ├── packages.config
│ ├── Infrastructure
│ │ ├── State.cs
│ │ ├── Rfc6454Utility.cs
│ │ ├── HeaderConstants.cs
│ │ ├── Rfc7230Utility.cs
│ │ ├── CharExtension.cs
│ │ ├── StringExtension.cs
│ │ └── ParamGuard.cs
│ ├── CspSourceList.HashSource.cs
│ ├── CspSourceList.NonceSource.cs
│ ├── OwinEnvironmentExtension.cs
│ ├── Properties
│ │ └── AssemblyInfo.cs
│ ├── XFrameOption.cs
│ ├── HostSourceCollection.cs
│ ├── XssProtectionOption.cs
│ ├── SourceListKeyword.cs
│ ├── ContenTypeOptionsHeaderMiddleware.cs
│ ├── CspSourceList.KeywordSource.cs
│ ├── CspSourceList.HostSource.cs
│ ├── SandboxKeyword.cs
│ ├── XssProtectionHeaderMiddleware.cs
│ ├── ContentSecurityPolicyMiddleware.cs
│ ├── ContentSecurityPolicyReportOnlyMiddleware.cs
│ ├── CspSourceList.SchemeSource.cs
│ ├── CspSourceList.cs
│ ├── AntiClickjackingMiddleware.cs
│ ├── StrictTransportSecurityOptions.cs
│ ├── StrictTransportSecurityHeaderMiddleware.cs
│ ├── CspMediaTypeList.cs
│ ├── CspSandboxTokenList.cs
│ ├── CspAncestorSourceList.cs
│ ├── CspUriReferenceList.cs
│ ├── SecurityHeaders.cs
│ ├── SecurityHeadersMiddleware.csproj
│ ├── ContentSecurityPolicyConfiguration.cs
│ └── HostSource.cs
├── SecurityHeadersMiddleware.OwinAppBuilder
│ ├── packages.config
│ ├── Properties
│ │ └── AssemblyInfo.cs
│ ├── SecurityHeadersMiddleware.OwinAppBuilder.csproj
│ └── AppBuilderExtensions.cs
├── SecurityHeadersMiddleware.PerformanceTests
│ ├── App.config
│ ├── packages.config
│ ├── Properties
│ │ └── AssemblyInfo.cs
│ ├── Program.cs
│ └── SecurityHeadersMiddleware.PerformanceTests.csproj
├── Release notes.txt
├── Roadmap.txt
├── SecurityHeadersMiddleware.sln.DotSettings
└── SecurityHeadersMiddleware.sln
├── .gitattributes
├── license.txt
├── README.md
└── .gitignore
/src/SharedAssemblyInfo.cs:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/StefanOssendorf/SecurityHeadersMiddleware/HEAD/src/SharedAssemblyInfo.cs
--------------------------------------------------------------------------------
/src/OwinContrib.SecurityHeaders.Tests/Properties/AssemblyInfo.cs:
--------------------------------------------------------------------------------
1 | using System.Reflection;
2 |
3 | [assembly: AssemblyTitle("SecurityHeadersMiddleware.Tests")]
4 | [assembly: AssemblyDescription("")]
--------------------------------------------------------------------------------
/src/SecurityHeadersMiddleware.Examples/packages.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/src/OwinContrib.SecurityHeaders/IDirectiveValueBuilder.cs:
--------------------------------------------------------------------------------
1 | namespace SecurityHeadersMiddleware {
2 | internal interface IDirectiveValueBuilder {
3 | string ToDirectiveValue();
4 | }
5 | }
--------------------------------------------------------------------------------
/src/SecurityHeadersMiddleware.OwinAppBuilder/packages.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/src/SecurityHeadersMiddleware.Examples/Properties/AssemblyInfo.cs:
--------------------------------------------------------------------------------
1 | using System.Reflection;
2 |
3 | [assembly: AssemblyTitle("Security Headers Middleware Examples")]
4 | [assembly: AssemblyDescription("")]
--------------------------------------------------------------------------------
/src/OwinContrib.SecurityHeaders/packages.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/src/SecurityHeadersMiddleware.PerformanceTests/App.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/src/OwinContrib.SecurityHeaders/Infrastructure/State.cs:
--------------------------------------------------------------------------------
1 | using SecurityHeadersMiddleware.LibOwin;
2 |
3 | namespace SecurityHeadersMiddleware.Infrastructure {
4 | internal class State {
5 | public T Settings { get; set; }
6 | public IOwinResponse Response { get; set; }
7 | }
8 | }
--------------------------------------------------------------------------------
/src/OwinContrib.SecurityHeaders.Tests/OwinEnvironmentSpecBase.cs:
--------------------------------------------------------------------------------
1 | using System.Net.Http;
2 |
3 | namespace SecurityHeadersMiddleware.Tests {
4 | public abstract class OwinEnvironmentSpecBase {
5 | protected static HttpClient Client;
6 | protected static HttpResponseMessage Response;
7 | }
8 | }
--------------------------------------------------------------------------------
/src/OwinContrib.SecurityHeaders/CspSourceList.HashSource.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace SecurityHeadersMiddleware {
4 | partial class CspSourceList {
5 | internal void AddHash(string hash) {
6 | //TODO: Maybe later...
7 | throw new NotImplementedException();
8 | }
9 | }
10 | }
--------------------------------------------------------------------------------
/src/OwinContrib.SecurityHeaders/CspSourceList.NonceSource.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace SecurityHeadersMiddleware {
4 | partial class CspSourceList {
5 | internal void AddNonce(string nonce) {
6 | //TODO: Maybe later...
7 | throw new NotImplementedException();
8 | }
9 | }
10 | }
--------------------------------------------------------------------------------
/src/OwinContrib.SecurityHeaders/OwinEnvironmentExtension.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using SecurityHeadersMiddleware.LibOwin;
3 |
4 | namespace SecurityHeadersMiddleware {
5 | internal static class OwinEnvironmentExtension {
6 | public static IOwinContext AsContext(this IDictionary env) {
7 | return new OwinContext(env);
8 | }
9 | }
10 | }
--------------------------------------------------------------------------------
/src/SecurityHeadersMiddleware.PerformanceTests/packages.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/src/Release notes.txt:
--------------------------------------------------------------------------------
1 | 2.1
2 | - STS "preload" parameter added
3 | -- https://developer.mozilla.org/en-US/docs/Web/Security/HTTP_strict_transport_security
4 | -- https://tools.ietf.org/html/rfc6797#section-12.3
5 |
6 |
7 | 2.0
8 | - CSP2 conformance (except Hash & Nonce)
9 | - Referrer directive removed
10 | - Reflected-xss directive removed
11 | - AncestorSource added
12 | - FrameSrc deprecated
13 | - bug fixes
14 | - internal improvements
--------------------------------------------------------------------------------
/src/SecurityHeadersMiddleware.OwinAppBuilder/Properties/AssemblyInfo.cs:
--------------------------------------------------------------------------------
1 | using System.Reflection;
2 | using System.Runtime.CompilerServices;
3 | using System.Runtime.InteropServices;
4 |
5 | [assembly: AssemblyTitle("Security Headers Middleware")]
6 | [assembly: AssemblyDescription("")]
7 | [assembly: ComVisible(false)]
8 | [assembly: Guid("c44292d5-1ab9-4d45-8a3e-a8012fbfdd2e")]
9 | [assembly: InternalsVisibleTo("SecurityHeadersMiddleware.Tests")]
--------------------------------------------------------------------------------
/src/OwinContrib.SecurityHeaders/Properties/AssemblyInfo.cs:
--------------------------------------------------------------------------------
1 | using System.Reflection;
2 | using System.Runtime.CompilerServices;
3 |
4 | [assembly: AssemblyTitle("Security Headers Middleware")]
5 | [assembly: AssemblyDescription("")]
6 | [assembly: InternalsVisibleTo("SecurityHeadersMiddleware.OwinAppBuilder")]
7 | [assembly: InternalsVisibleTo("SecurityHeadersMiddleware.Tests")]
8 | [assembly: InternalsVisibleTo("SecurityHeadersMiddleware.PerformanceTests")]
--------------------------------------------------------------------------------
/src/Roadmap.txt:
--------------------------------------------------------------------------------
1 | Inspired from: https://www.owasp.org/index.php/List_of_useful_HTTP_headers
2 | Middlewares for
3 | - X-Xss-Protection
4 | - X-Frame-Options
5 | - Strict-Transport-Security
6 | - X-Content-Type-Options
7 | - Content-Security-Policy
8 |
9 | Already implemented:
10 | - X-Xss-Protection
11 | - X-Frame-Options
12 | - Strict-Transport-Security
13 | - X-Content-Type-Options
14 | - Content-Security-Policy
15 | - Content-Security-Policy-Report-Only
--------------------------------------------------------------------------------
/src/OwinContrib.SecurityHeaders/XFrameOption.cs:
--------------------------------------------------------------------------------
1 | namespace SecurityHeadersMiddleware {
2 | ///
3 | /// The X-Frame-Options keywords.
4 | ///
5 | public enum XFrameOption {
6 | ///
7 | /// Headervalue deny.
8 | ///
9 | Deny = 1,
10 |
11 | ///
12 | /// Headevalue sameorigin.
13 | ///
14 | SameOrigin = 2
15 | }
16 | }
--------------------------------------------------------------------------------
/src/OwinContrib.SecurityHeaders.Tests/Infrastructure/Rfc7230UtilityTests.cs:
--------------------------------------------------------------------------------
1 | using Machine.Specifications;
2 | using SecurityHeadersMiddleware.Infrastructure;
3 | using Xunit;
4 |
5 | namespace SecurityHeadersMiddleware.Tests.Infrastructure {
6 | public class Rfc7230UtilityTests {
7 | [Fact]
8 | public void When_validate_null_as_token_it_should_return_false() {
9 | Rfc7230Utility.IsToken(null).ShouldBeFalse();
10 | }
11 | }
12 | }
--------------------------------------------------------------------------------
/src/OwinContrib.SecurityHeaders/HostSourceCollection.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.ObjectModel;
2 |
3 | namespace SecurityHeadersMiddleware {
4 | internal class HostSourceCollection : Collection {
5 | protected override void InsertItem(int index, HostSource item) {
6 | //TODO Contains necessary?
7 | if (Contains(item)) {
8 | return;
9 | }
10 |
11 | base.InsertItem(index, item);
12 | }
13 | }
14 | }
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | # Auto detect text files and perform LF normalization
2 | * text=auto
3 |
4 | # Custom for Visual Studio
5 | *.cs diff=csharp
6 | *.sln merge=union
7 | *.csproj merge=union
8 | *.vbproj merge=union
9 | *.fsproj merge=union
10 | *.dbproj merge=union
11 |
12 | # Standard to msysgit
13 | *.doc diff=astextplain
14 | *.DOC diff=astextplain
15 | *.docx diff=astextplain
16 | *.DOCX diff=astextplain
17 | *.dot diff=astextplain
18 | *.DOT diff=astextplain
19 | *.pdf diff=astextplain
20 | *.PDF diff=astextplain
21 | *.rtf diff=astextplain
22 | *.RTF diff=astextplain
23 |
--------------------------------------------------------------------------------
/src/OwinContrib.SecurityHeaders/Infrastructure/Rfc6454Utility.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace SecurityHeadersMiddleware.Infrastructure {
4 | internal static class Rfc6454Utility {
5 | public static bool HasSameOrigin(Uri uri1, Uri uri2) {
6 | return Uri.Compare(uri1, uri2, UriComponents.SchemeAndServer, UriFormat.SafeUnescaped, StringComparison.OrdinalIgnoreCase) == 0;
7 | }
8 |
9 | public static string SerializeOrigin(Uri uri) {
10 | return uri.GetComponents(UriComponents.SchemeAndServer, UriFormat.SafeUnescaped);
11 | }
12 | }
13 | }
--------------------------------------------------------------------------------
/src/OwinContrib.SecurityHeaders/XssProtectionOption.cs:
--------------------------------------------------------------------------------
1 | namespace SecurityHeadersMiddleware {
2 | ///
3 | /// Specified the allowed options for the X-XSS-Protection header.
4 | ///
5 | public enum XssProtectionOption {
6 | ///
7 | /// Represents the option "1; mode=block" (recommended)
8 | ///
9 | EnabledWithModeBlock,
10 | ///
11 | /// Represents the option "1"
12 | ///
13 | Enabled,
14 | ///
15 | /// Represents the option "0" (not recommended)
16 | ///
17 | Disabled
18 | }
19 | }
--------------------------------------------------------------------------------
/src/OwinContrib.SecurityHeaders/Infrastructure/HeaderConstants.cs:
--------------------------------------------------------------------------------
1 | namespace SecurityHeadersMiddleware.Infrastructure {
2 | internal static class HeaderConstants {
3 | public const string XssProtection = "X-Xss-Protection";
4 | public const string XFrameOptions = "X-Frame-Options";
5 | public const string StrictTransportSecurity = "Strict-Transport-Security";
6 | public const string Location = "Location";
7 | public const string XContentTypeOptions = "X-Content-Type-Options";
8 | public const string ContentSecurityPolicy = "Content-Security-Policy";
9 | public const string ContentSecurityPolicyReportOnly = "Content-Security-Policy-Report-Only";
10 | }
11 | }
--------------------------------------------------------------------------------
/src/OwinContrib.SecurityHeaders/Infrastructure/Rfc7230Utility.cs:
--------------------------------------------------------------------------------
1 | using System.Linq;
2 |
3 | namespace SecurityHeadersMiddleware.Infrastructure {
4 | internal static class Rfc7230Utility {
5 | private static readonly char[] TCharSpecials = {'!', '#', '$', '%', '&', '\'', '*', '+', '-', '.', '^', '_', '`', '|', '~'};
6 |
7 | public static bool IsToken(string token) {
8 | if (token.IsNullOrWhiteSpace()) {
9 | return false;
10 | }
11 | return token.All(IsTChar);
12 | }
13 |
14 | public static bool IsTChar(char source) {
15 | return source.IsRfc5234Alpha() || source.IsRfc5234Digit() || TCharSpecials.Any(c => c == source);
16 | }
17 | }
18 | }
--------------------------------------------------------------------------------
/src/OwinContrib.SecurityHeaders.Tests/AssemblyTests.cs:
--------------------------------------------------------------------------------
1 | using System.Linq;
2 | using System.Reflection;
3 | using FluentAssertions;
4 | using Xunit;
5 |
6 | namespace SecurityHeadersMiddleware.Tests {
7 | public class AssemblyTests {
8 | [Fact]
9 | public void SecurityHeadersMiddlewareAssembly_should_not_reference_owin_assembly() {
10 | var assembly = Assembly.GetAssembly(typeof (ContentSecurityPolicyMiddleware));
11 | var referencedAssemblies = assembly.GetReferencedAssemblies();
12 | referencedAssemblies.Should().NotContain(dll => dll.Name == "SecurityHeadersMiddleware.OwinAppBuilder", "SecurityHeadersMiddleware should not reference SecurityHeadersMiddleware.OwinAppBuilder");
13 | }
14 | }
15 | }
--------------------------------------------------------------------------------
/src/OwinContrib.SecurityHeaders.Tests/app.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/src/OwinContrib.SecurityHeaders/Infrastructure/CharExtension.cs:
--------------------------------------------------------------------------------
1 | namespace SecurityHeadersMiddleware.Infrastructure {
2 | internal static class CharExtension {
3 | public static bool IsAscii(this char source) {
4 | return source >= 0 && source <= 127;
5 | }
6 |
7 | public static bool IsCTL(this char source) {
8 | return source <= 31 || source == 127;
9 | }
10 |
11 | public static bool IsRfc5234Alpha(this char source) {
12 | // A-Z and a-z
13 | return source <= 0x5A && source >= 0x41 || source <= 0x7A && source >= 0x61;
14 | }
15 |
16 | public static bool IsRfc5234Digit(this char source) {
17 | return source <= 0x39 && source >= 0x30;
18 | }
19 | }
20 | }
--------------------------------------------------------------------------------
/src/SecurityHeadersMiddleware.Examples/ContentTypeOptionsExamples.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Threading.Tasks;
4 | using Owin;
5 | using SecurityHeadersMiddleware.OwinAppBuilder;
6 |
7 | namespace SecurityHeadersMiddleware.Examples {
8 | using BuildFunc = Action, Func, Task>, Func, Task>>>>;
9 |
10 | public class ContentTypeOptionsExamples {
11 | public void Examples() {
12 | IAppBuilder appbuilder = null;
13 | BuildFunc buildFunc = null;
14 |
15 | // Add X-Content-Type-Option: nosniff
16 | buildFunc.ContentTypeOptions();
17 | appbuilder.ContentTypeOptions();
18 | }
19 | }
20 | }
--------------------------------------------------------------------------------
/src/OwinContrib.SecurityHeaders/SourceListKeyword.cs:
--------------------------------------------------------------------------------
1 | namespace SecurityHeadersMiddleware {
2 | ///
3 | /// Specifies the allowed source list keywords according to the CSP.
4 | /// See http://www.w3.org/TR/CSP2/#keyword-source
5 | ///
6 | public enum SourceListKeyword {
7 | ///
8 | /// Keyword: 'self'
9 | ///
10 | Self,
11 |
12 | ///
13 | /// Keyword: 'unsafe-inline'
14 | ///
15 | UnsafeInline,
16 |
17 | ///
18 | /// Keyword: 'unsafe-eval'
19 | ///
20 | UnsafeEval,
21 |
22 | ///
23 | /// Keyword: 'unsafe-redirect'
24 | ///
25 | UnsafeRedirect
26 | }
27 | }
--------------------------------------------------------------------------------
/src/SecurityHeadersMiddleware.Examples/XXssProtectionExamples.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Threading.Tasks;
4 | using Owin;
5 | using SecurityHeadersMiddleware.OwinAppBuilder;
6 |
7 | namespace SecurityHeadersMiddleware.Examples {
8 | using BuildFunc = Action, Func, Task>, Func, Task>>>>;
9 |
10 | public class XXssProtectionExamples {
11 | public void Examples() {
12 | IAppBuilder appbuilder = null;
13 | BuildFunc buildFunc = null;
14 |
15 | // Add X-Xss-Protection: 1; mode=block
16 | buildFunc.XssProtectionHeader();
17 | appbuilder.XssProtectionHeader();
18 |
19 | // Add X-Xss-Protection: 0
20 | buildFunc.XssProtectionHeader(disabled: true);
21 | appbuilder.XssProtectionHeader(disabled: true);
22 | }
23 | }
24 | }
--------------------------------------------------------------------------------
/src/OwinContrib.SecurityHeaders/ContenTypeOptionsHeaderMiddleware.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Threading.Tasks;
4 | using SecurityHeadersMiddleware.Infrastructure;
5 | using SecurityHeadersMiddleware.LibOwin;
6 |
7 | namespace SecurityHeadersMiddleware {
8 | internal static class ContenTypeOptionsHeaderMiddleware {
9 | public static Func, Task>, Func, Task>> ContentTypeOptionsHeader() {
10 | return next =>
11 | env => {
12 | var response = env.AsContext().Response;
13 | response.OnSendingHeaders(ApplyHeader, response);
14 | return next(env);
15 | };
16 | }
17 |
18 | private static void ApplyHeader(object obj) {
19 | var response = (IOwinResponse) obj;
20 | response.Headers[HeaderConstants.XContentTypeOptions] = "nosniff";
21 | }
22 | }
23 | }
--------------------------------------------------------------------------------
/src/OwinContrib.SecurityHeaders/Infrastructure/StringExtension.cs:
--------------------------------------------------------------------------------
1 | using System.Linq;
2 |
3 | namespace SecurityHeadersMiddleware.Infrastructure {
4 | internal static class StringExtension {
5 | public static string FormatWith(this string source, params object[] values) {
6 | return string.Format(source, values);
7 | }
8 |
9 | public static bool IsNullOrWhiteSpace(this string source) {
10 | return string.IsNullOrWhiteSpace(source);
11 | }
12 |
13 | public static bool IsEmpty(this string source) {
14 | return source == string.Empty;
15 | }
16 |
17 | public static string PercentEncode(this string source) {
18 | var value = source;
19 | if (source.Contains(";")) {
20 | value = source.Replace(";", "%3B");
21 | }
22 | if (source.Contains(",")) {
23 | value = source.Replace(",", "%2C");
24 | }
25 | return value;
26 | }
27 | }
28 | }
--------------------------------------------------------------------------------
/src/OwinContrib.SecurityHeaders.Tests/packages.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/license.txt:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2014 Stefan Ossendorf
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
--------------------------------------------------------------------------------
/src/OwinContrib.SecurityHeaders/CspSourceList.KeywordSource.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using System.Text;
3 |
4 | namespace SecurityHeadersMiddleware {
5 | partial class CspSourceList {
6 | private readonly List mKeywords;
7 |
8 | ///
9 | /// Adds a keyword to the source-list.
10 | ///
11 | /// The keyword.
12 | public void AddKeyword(SourceListKeyword keyword) {
13 | ThrowIfNoneIsSet();
14 | if (mKeywords.Contains(keyword)) {
15 | return;
16 | }
17 | mKeywords.Add(keyword);
18 | }
19 |
20 | private string BuildKeywordValues() {
21 | var sb = new StringBuilder();
22 | foreach (var keyword in mKeywords) {
23 | var value = keyword.ToString().ToLower();
24 | if (value.StartsWith("unsafe")) {
25 | value = value.Insert(6, "-");
26 | }
27 | sb.AppendFormat("'{0}' ", value);
28 | }
29 | return sb.ToString();
30 | }
31 | }
32 | }
--------------------------------------------------------------------------------
/src/OwinContrib.SecurityHeaders/CspSourceList.HostSource.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using SecurityHeadersMiddleware.Infrastructure;
3 |
4 | namespace SecurityHeadersMiddleware {
5 | partial class CspSourceList {
6 | private readonly HostSourceCollection mHosts;
7 |
8 | ///
9 | /// Adds a host to the source-list.
10 | ///
11 | /// The host.
12 | public void AddHost(Uri host) {
13 | AddHost(host.GetComponents(UriComponents.SchemeAndServer | UriComponents.Path, UriFormat.UriEscaped));
14 | }
15 |
16 | ///
17 | /// Adds a host to the source-list.
18 | ///
19 | /// The host.
20 | public void AddHost(string host) {
21 | ThrowIfNoneIsSet();
22 | host.MustNotNull("host");
23 | host.MustNotBeWhiteSpaceOrEmpty("host");
24 | var hostSource = new HostSource(host);
25 | mHosts.Add(hostSource);
26 | }
27 |
28 | private string BuildHostValues() {
29 | return string.Join(" ", mHosts).PercentEncode();
30 | }
31 | }
32 | }
--------------------------------------------------------------------------------
/src/OwinContrib.SecurityHeaders/SandboxKeyword.cs:
--------------------------------------------------------------------------------
1 | namespace SecurityHeadersMiddleware {
2 | ///
3 | /// Specifies the allowed keywords according to the CSP.
4 | /// See: http://www.w3.org/TR/CSP2/#directive-sandbox ,
5 | /// http://developers.whatwg.org/the-iframe-element.html#attr-iframe-sandbox and
6 | /// http://lists.w3.org/Archives/Public/public-webappsec/2014Aug/0019.html
7 | ///
8 | public enum SandboxKeyword {
9 | ///
10 | /// Keyword: allow-forms
11 | ///
12 | AllowForms,
13 |
14 | ///
15 | /// Keyword: allow-pointer-lock
16 | ///
17 | AllowPointerLock,
18 |
19 | ///
20 | /// Keyword: allow-popups
21 | ///
22 | AllowPopups,
23 |
24 | ///
25 | /// Keyword: allow-same-origin
26 | ///
27 | AllowSameOrigin,
28 |
29 | ///
30 | /// Keyword: allow-scripts
31 | ///
32 | AllowScripts,
33 |
34 | ///
35 | /// Keyword: allow-top-navigation
36 | ///
37 | AllowTopNavigation
38 | }
39 | }
--------------------------------------------------------------------------------
/src/OwinContrib.SecurityHeaders/Infrastructure/ParamGuard.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace SecurityHeadersMiddleware.Infrastructure {
4 | internal static class ParamGuard {
5 | public static void MustNotNull(this object source, string paramName) {
6 | if (source == null) {
7 | throw new ArgumentNullException(paramName);
8 | }
9 | }
10 |
11 | public static void MustHaveAtLeastOneValue(this T[] source, string paramName) {
12 | if (source.Length <= 0) {
13 | throw new ArgumentOutOfRangeException(paramName);
14 | }
15 | }
16 |
17 | public static void MustNotBeWhiteSpaceOrEmpty(this string source, string paramName) {
18 | if (string.IsNullOrWhiteSpace(source)) {
19 | throw new ArgumentException("{0} have to be not null, not empty or only contains white-space characters.".FormatWith(paramName));
20 | }
21 | }
22 |
23 | public static void MustBeDefined(this Enum source, string paramName) {
24 | var enumType = source.GetType();
25 | if (!Enum.IsDefined(enumType, source)) {
26 | throw new ArgumentOutOfRangeException("{0} is not a defined value on {1} (Parameter: {2})".FormatWith(source, enumType.ToString(), paramName));
27 | }
28 | }
29 | }
30 | }
--------------------------------------------------------------------------------
/src/OwinContrib.SecurityHeaders.Tests/ContentTypeOptionsMiddleware.cs:
--------------------------------------------------------------------------------
1 | using System.Net.Http;
2 | using System.Threading.Tasks;
3 | using Machine.Specifications;
4 | using Microsoft.Owin.Testing;
5 | using Owin;
6 | using SecurityHeadersMiddleware.OwinAppBuilder;
7 |
8 | namespace SecurityHeadersMiddleware.Tests {
9 | [Subject(typeof (ContenTypeOptionsHeaderMiddleware))]
10 | public class When_using_contentTypeOptionsHeaderMiddleware : OwinEnvironmentSpecBase {
11 | private Establish context = () => Client = CtoClientHelper.Create();
12 | private Because of = () => Response = Client.GetAsync("http://wwww.example.org").Await();
13 | private It should_contain_contentTypeOptions_header = () => Response.XContentTypeOptions().ShouldNotBeNull();
14 | private It should_containt_nosniff_as_header_value = () => Response.XContentTypeOptions().ShouldEqual("nosniff");
15 | }
16 |
17 | internal static class CtoClientHelper {
18 | public static HttpClient Create() {
19 | return TestServer.Create(builder => {
20 | builder.UseOwin().ContentTypeOptions();
21 | builder
22 | .Use((context, next) => {
23 | context.Response.StatusCode = 200;
24 | context.Response.ReasonPhrase = "OK";
25 | return Task.FromResult(0);
26 | });
27 | }).HttpClient;
28 | }
29 | }
30 | }
--------------------------------------------------------------------------------
/src/SecurityHeadersMiddleware.Examples/StrictTransportSecurityExamples.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Threading.Tasks;
4 | using Owin;
5 | using SecurityHeadersMiddleware.OwinAppBuilder;
6 |
7 | namespace SecurityHeadersMiddleware.Examples {
8 | using BuildFunc = Action, Func, Task>, Func, Task>>>>;
9 |
10 | public class StrictTransportSecurityExamples {
11 | public void Examples() {
12 | IAppBuilder appbuilder = null;
13 | BuildFunc buildFunc = null;
14 |
15 | // Remark: 31536000 = 1 year in seconds
16 |
17 | // Add Strict-Transport-Security: max-age=31536000;includeSubDomains
18 | buildFunc.StrictTransportSecurity();
19 | appbuilder.StrictTransportSecurity();
20 |
21 | // Add Strict-Transport-Security with the configured settings
22 | var config = new StrictTransportSecurityOptions {
23 | IncludeSubDomains = true,
24 | MaxAge = 31536000,
25 | RedirectToSecureTransport = true,
26 | RedirectUriBuilder = uri => "", // Only do this, when you want to replace the default behavior (from http to https).
27 | RedirectReasonPhrase = statusCode => "ResonPhrase"
28 | };
29 | buildFunc.StrictTransportSecurity(config);
30 | appbuilder.StrictTransportSecurity(config);
31 | }
32 | }
33 | }
--------------------------------------------------------------------------------
/src/OwinContrib.SecurityHeaders.Tests/HeaderHelper.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Net.Http;
5 | using SecurityHeadersMiddleware.Infrastructure;
6 |
7 | namespace SecurityHeadersMiddleware.Tests {
8 | internal static class HeaderHelper {
9 | public static string XFrameOptionsHeader(this HttpResponseMessage source) {
10 | return source.Headers.GetValues(HeaderConstants.XFrameOptions).First();
11 | }
12 |
13 | public static IEnumerable StsHeader(this HttpResponseMessage source) {
14 | return source.Headers.GetValues(HeaderConstants.StrictTransportSecurity).Single().Split(new[] {';'}, StringSplitOptions.RemoveEmptyEntries).Select(value => value.Trim());
15 | }
16 |
17 | public static string XContentTypeOptions(this HttpResponseMessage source) {
18 | return source.Headers.GetValues(HeaderConstants.XContentTypeOptions).Single();
19 | }
20 |
21 | public static string XssProtection(this HttpResponseMessage source) {
22 | return source.Headers.GetValues(HeaderConstants.XssProtection).First();
23 | }
24 |
25 | public static string Csp(this HttpResponseMessage source) {
26 | return source.Headers.GetValues(HeaderConstants.ContentSecurityPolicy).First();
27 | }
28 |
29 | public static string Cspro(this HttpResponseMessage source) {
30 | return source.Headers.GetValues(HeaderConstants.ContentSecurityPolicyReportOnly).First();
31 | }
32 | }
33 | }
--------------------------------------------------------------------------------
/src/OwinContrib.SecurityHeaders/XssProtectionHeaderMiddleware.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Threading.Tasks;
4 | using SecurityHeadersMiddleware.Infrastructure;
5 | using SecurityHeadersMiddleware.LibOwin;
6 |
7 | namespace SecurityHeadersMiddleware {
8 | internal static class XssProtectionHeaderMiddleware {
9 | public static Func, Task>, Func, Task>> XssProtectionHeader(XssProtectionOption option) {
10 | return
11 | next =>
12 | env => {
13 | var response = env.AsContext().Response;
14 | response
15 | .OnSendingHeaders(resp => {
16 |
17 | ((IOwinResponse)resp).Headers[HeaderConstants.XssProtection] = GetHeaderValue(option);
18 | }, response);
19 | return next(env);
20 | };
21 | }
22 |
23 | private static string GetHeaderValue(XssProtectionOption option) {
24 | switch (option) {
25 | case XssProtectionOption.EnabledWithModeBlock:
26 | return "1; mode=block";
27 | case XssProtectionOption.Enabled:
28 | return "1";
29 | case XssProtectionOption.Disabled:
30 | return "0";
31 | default:
32 | throw new ArgumentOutOfRangeException(nameof(option));
33 | }
34 | }
35 | }
36 | }
--------------------------------------------------------------------------------
/src/SecurityHeadersMiddleware.Examples/AntiClickjackingExamples.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Threading.Tasks;
4 | using Owin;
5 | using SecurityHeadersMiddleware.OwinAppBuilder;
6 |
7 | namespace SecurityHeadersMiddleware.Examples {
8 | using BuildFunc = Action, Func, Task>, Func, Task>>>>;
9 |
10 | public class AntiClickjackingExamples {
11 | public void Examples() {
12 | IAppBuilder appbuilder = null;
13 | BuildFunc buildFunc = null;
14 |
15 | // Add X-Frame-Options: DENY
16 | buildFunc.AntiClickjackingHeader();
17 | appbuilder.AntiClickjackingHeader();
18 |
19 | // Add X-Frame-Options: SAMEORIGIN
20 | buildFunc.AntiClickjackingHeader(XFrameOption.SameOrigin);
21 | appbuilder.AntiClickjackingHeader(XFrameOption.SameOrigin);
22 |
23 | // Add X-Frame-Options: ALLOW-FROM http://www.exmple.com when the Request uri is the allow-from uri.
24 | // Otherwise DENY will be sent.
25 | buildFunc.AntiClickjackingHeader("http://www.example.com", "https://www.example.com");
26 | appbuilder.AntiClickjackingHeader("http://www.example.com", "https://www.example.com");
27 | // or with URIs
28 | buildFunc.AntiClickjackingHeader(new Uri("http://www.example.com"), new Uri("https://www.example.com"));
29 | appbuilder.AntiClickjackingHeader(new Uri("http://www.example.com"), new Uri("https://www.example.com"));
30 | }
31 | }
32 | }
--------------------------------------------------------------------------------
/src/SecurityHeadersMiddleware.PerformanceTests/Properties/AssemblyInfo.cs:
--------------------------------------------------------------------------------
1 | using System.Reflection;
2 | using System.Runtime.CompilerServices;
3 | using System.Runtime.InteropServices;
4 |
5 | // Allgemeine Informationen über eine Assembly werden über die folgenden
6 | // Attribute gesteuert. Ändern Sie diese Attributwerte, um die Informationen zu ändern,
7 | // die mit einer Assembly verknüpft sind.
8 | [assembly: AssemblyTitle("SecurityHeadersMiddleware.PerformanceTests")]
9 | [assembly: AssemblyDescription("")]
10 | [assembly: AssemblyConfiguration("")]
11 | [assembly: AssemblyCompany("")]
12 | [assembly: AssemblyProduct("SecurityHeadersMiddleware.PerformanceTests")]
13 | [assembly: AssemblyCopyright("Copyright © 2015")]
14 | [assembly: AssemblyTrademark("")]
15 | [assembly: AssemblyCulture("")]
16 |
17 | // Durch Festlegen von ComVisible auf "false" werden die Typen in dieser Assembly unsichtbar
18 | // für COM-Komponenten. Wenn Sie auf einen Typ in dieser Assembly von
19 | // COM zugreifen müssen, legen Sie das ComVisible-Attribut für diesen Typ auf "true" fest.
20 | [assembly: ComVisible(false)]
21 |
22 | // Die folgende GUID bestimmt die ID der Typbibliothek, wenn dieses Projekt für COM verfügbar gemacht wird
23 | [assembly: Guid("c1ea1621-7ef7-4237-8bcc-02ace176b852")]
24 |
25 | // Versionsinformationen für eine Assembly bestehen aus den folgenden vier Werten:
26 | //
27 | // Hauptversion
28 | // Nebenversion
29 | // Buildnummer
30 | // Revision
31 | //
32 | // Sie können alle Werte angeben oder die standardmäßigen Build- und Revisionsnummern
33 | // übernehmen, indem Sie "*" eingeben:
34 | // [assembly: AssemblyVersion("1.0.*")]
35 | [assembly: AssemblyVersion("1.0.0.0")]
36 | [assembly: AssemblyFileVersion("1.0.0.0")]
37 |
--------------------------------------------------------------------------------
/src/SecurityHeadersMiddleware.sln.DotSettings:
--------------------------------------------------------------------------------
1 |
2 | <data><IncludeFilters /><ExcludeFilters><Filter ModuleMask="SecurityHeadersMiddleware" ModuleVersionMask="*" ClassMask="SecurityHeadersMiddleware.Infrastructure.ParamGuard" FunctionMask="*" IsEnabled="True" /><Filter ModuleMask="SecurityHeadersMiddleware" ModuleVersionMask="*" ClassMask="SecurityHeadersMiddleware.LibOwin.Infrastructure.*" FunctionMask="*" IsEnabled="True" /><Filter ModuleMask="SecurityHeadersMiddleware" ModuleVersionMask="*" ClassMask="SecurityHeadersMiddleware.LibOwin.*" FunctionMask="*" IsEnabled="True" /><Filter ModuleMask="SecurityHeadersMiddleware.Examples" ModuleVersionMask="*" ClassMask="*" FunctionMask="*" IsEnabled="True" /><Filter ModuleMask="SecurityHeadersMiddleware.PerformanceTests" ModuleVersionMask="*" ClassMask="*" FunctionMask="*" IsEnabled="True" /><Filter ModuleMask="SecurityHeadersMiddleware.Tests" ModuleVersionMask="*" ClassMask="*" FunctionMask="*" IsEnabled="True" /><Filter ModuleMask="SecurityHeadersMiddleware" ModuleVersionMask="*" ClassMask="SecurityHeadersMiddleware.HostSource" FunctionMask="GetHashCode" IsEnabled="True" /><Filter ModuleMask="SecurityHeadersMiddleware" ModuleVersionMask="*" ClassMask="SecurityHeadersMiddleware.HostSource" FunctionMask="Equals" IsEnabled="True" /><Filter ModuleMask="SecurityHeadersMiddleware.OwinAppBuilder" ModuleVersionMask="*" ClassMask="*" FunctionMask="*" IsEnabled="True" /></ExcludeFilters></data>
--------------------------------------------------------------------------------
/src/OwinContrib.SecurityHeaders/ContentSecurityPolicyMiddleware.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Threading.Tasks;
4 | using SecurityHeadersMiddleware.Infrastructure;
5 | using SecurityHeadersMiddleware.LibOwin;
6 |
7 | namespace SecurityHeadersMiddleware {
8 | internal static class ContentSecurityPolicyMiddleware {
9 | public static Func, Task>, Func, Task>> ContentSecurityPolicyHeader(ContentSecurityPolicyConfiguration configuration) {
10 | return next =>
11 | env => {
12 | var context = env.AsContext();
13 | var state = new State {
14 | Settings = configuration,
15 | Response = context.Response
16 | };
17 | context.Response.OnSendingHeaders(ApplyHeader, state);
18 | return next(env);
19 | };
20 | }
21 |
22 | private static void ApplyHeader(State obj) {
23 | var response = obj.Response;
24 | var cspConfig = obj.Settings;
25 |
26 | if (ContainsCspHeader(obj.Response.Headers)) {
27 | // A server MUST NOT send more than one HTTP header field named Content-Security-Policy with a given resource representation.
28 | // Source: http://www.w3.org/TR/CSP2/#content-security-policy-header-field (Date: 06.04.2015)
29 | return;
30 | }
31 |
32 | response.Headers[HeaderConstants.ContentSecurityPolicy] = cspConfig.ToHeaderValue();
33 | }
34 |
35 | private static bool ContainsCspHeader(IHeaderDictionary headers) {
36 | return headers.ContainsKey(HeaderConstants.ContentSecurityPolicy);
37 | }
38 | }
39 | }
--------------------------------------------------------------------------------
/src/OwinContrib.SecurityHeaders/ContentSecurityPolicyReportOnlyMiddleware.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Threading.Tasks;
4 | using SecurityHeadersMiddleware.Infrastructure;
5 | using SecurityHeadersMiddleware.LibOwin;
6 |
7 | namespace SecurityHeadersMiddleware {
8 | internal static class ContentSecurityPolicyReportOnlyMiddleware {
9 | public static Func, Task>, Func, Task>> ContentSecurityPolicyHeader(ContentSecurityPolicyConfiguration configuration) {
10 | return next =>
11 | env => {
12 | var context = env.AsContext();
13 | var state = new State {
14 | Settings = configuration,
15 | Response = context.Response
16 | };
17 | context.Response.OnSendingHeaders(ApplyHeader, state);
18 | return next(env);
19 | };
20 | }
21 |
22 | private static void ApplyHeader(State obj) {
23 | var response = obj.Response;
24 | var cspConfig = obj.Settings;
25 |
26 | if (ContainsCspHeader(response.Headers)) {
27 | // "A server MUST NOT send more than one HTTP header field named Content-Security-Policy-Report-Only with a given resource representation."
28 | // Source: http://www.w3.org/TR/CSP2/#content-security-policy-report-only-header-field (Date: 06.04.2015)
29 | return;
30 | }
31 |
32 | response.Headers[HeaderConstants.ContentSecurityPolicyReportOnly] = cspConfig.ToHeaderValue();
33 | }
34 |
35 | private static bool ContainsCspHeader(IHeaderDictionary headers) {
36 | return headers.ContainsKey(HeaderConstants.ContentSecurityPolicyReportOnly);
37 | }
38 | }
39 | }
--------------------------------------------------------------------------------
/src/OwinContrib.SecurityHeaders/CspSourceList.SchemeSource.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Text.RegularExpressions;
4 | using SecurityHeadersMiddleware.Infrastructure;
5 |
6 | namespace SecurityHeadersMiddleware {
7 | partial class CspSourceList {
8 | private readonly List mSchemes;
9 |
10 | ///
11 | /// Adds a scheme to the source-list.
12 | ///
13 | /// The scheme.
14 | ///
15 | /// has to satisfy this regex: ^[a-z][a-z0-9+\-.]*:?$
16 | /// For more information see http://www.w3.org/TR/CSP2/#scheme-source
17 | ///
18 | public void AddScheme(string scheme) {
19 | ThrowIfNoneIsSet();
20 | scheme.MustNotNull("scheme");
21 | scheme = scheme.ToLower();
22 | const string schemeRegex = @"(^[a-z][a-z0-9+\-.]*)(:?)$";
23 | var match = Regex.Match(scheme, schemeRegex, RegexOptions.IgnoreCase);
24 | if (!match.Success) {
25 | const string msg = "Scheme value '{0}' is invalid.{1}" +
26 | "Valid schemes:{1}http: or http or ftp: or ftp{1}" +
27 | "First charachter must be a letter.{1}" +
28 | "For more Information see: {2}";
29 | throw new FormatException(msg.FormatWith(scheme, Environment.NewLine, "http://www.w3.org/TR/CSP2/#scheme-source"));
30 | }
31 | var schemeToAdd = "{0}:".FormatWith(match.Groups[1].Value.ToLower());
32 | if (mSchemes.Contains(schemeToAdd)) {
33 | return;
34 | }
35 | mSchemes.Add(schemeToAdd);
36 | }
37 |
38 | private string BuildSchemeValues() {
39 | return string.Join(" ", mSchemes);
40 | }
41 | }
42 | }
--------------------------------------------------------------------------------
/src/OwinContrib.SecurityHeaders.Tests/CspUriReferenceListTests.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using FluentAssertions;
5 | using Xunit;
6 |
7 | namespace SecurityHeadersMiddleware.Tests {
8 | public class CspUriReferenceListTests {
9 | [Fact]
10 | public void When_adding_an_invalid_uri_it_should_throw_a_formatException() {
11 | var refList = new CspUriReferenceList();
12 | Assert.Throws(() => refList.AddReportUri("http//example.org"));
13 | }
14 |
15 | [Fact]
16 | public void When_adding_a_valid_uri_it_should_create_the_headerValue() {
17 | var list = new CspUriReferenceList();
18 | list.AddReportUri(new Uri("http://www.example.com"));
19 | list.ToDirectiveValue().Trim().Should().Be("http://www.example.com/");
20 | }
21 |
22 | [Fact]
23 | public void When_adding_a_uri_twice_it_should_only_be_once_in_the_headerValue() {
24 | var list = new CspUriReferenceList();
25 | list.AddReportUri("http://www.example.com");
26 | list.AddReportUri("http://www.example.com:80");
27 | list.ToDirectiveValue().Trim().Should().Be("http://www.example.com/");
28 | }
29 |
30 | [Fact]
31 | public void When_adding_several_uris_they_should_be_separated_by_at_least_one_whitespace() {
32 | var compareList = new List {
33 | "http://www.example.com/".ToLower(),
34 | "http://www.example.com/list?id=10#Fragment=12".ToLower(),
35 | "http://www.example.com/list?id=10".ToLower(),
36 | "http://www.example.com/list".ToLower()
37 | };
38 | var list = new CspUriReferenceList();
39 | list.AddReportUri("http://www.example.com");
40 | list.AddReportUri("http://www.example.com/list");
41 | list.AddReportUri("http://www.example.com/list?id=10");
42 | list.AddReportUri("http://www.example.com/list?id=10#fragment=12");
43 | var split = list.ToDirectiveValue().Split(new[] {" "}, StringSplitOptions.RemoveEmptyEntries).Select(val => val.Trim()).ToArray();
44 | split.Length.Should().Be(4);
45 | split.Should().Contain(compareList);
46 | }
47 | }
48 | }
--------------------------------------------------------------------------------
/src/OwinContrib.SecurityHeaders.Tests/XssProtectionHeaderMiddlewareSpecs.cs:
--------------------------------------------------------------------------------
1 | using System.Net.Http;
2 | using System.Threading.Tasks;
3 | using FluentAssertions;
4 | using Machine.Specifications;
5 | using Microsoft.Owin.Testing;
6 | using Owin;
7 | using SecurityHeadersMiddleware.OwinAppBuilder;
8 | using Xunit;
9 |
10 | namespace SecurityHeadersMiddleware.Tests {
11 | [Subject(typeof (XssProtectionHeaderMiddleware))]
12 | public class When_protection_is_enabled : OwinEnvironmentSpecBase {
13 | private Establish context = () => Client = XssClientHelper.CreateClient(false);
14 | private Because of = () => Response = Client.GetAsync("http://www.example.org").Await();
15 | private It Should_have_header_in_response = () => Response.XssProtection().ShouldEqual("1; mode=block");
16 | }
17 |
18 | [Subject(typeof (XssProtectionHeaderMiddleware))]
19 | public class When_disabling_protection : OwinEnvironmentSpecBase {
20 | private Establish context = () => Client = XssClientHelper.CreateClient(true);
21 | private Because of = () => Response = Client.GetAsync("http://www.example.org").Await();
22 | private It should_have_header_with_value_0 = () => Response.XssProtection().ShouldEqual("0");
23 | }
24 |
25 |
26 | public class XssProtectionHeaderMiddlewareTests {
27 | [Fact]
28 | public async Task When_set_xss_options_to_krznbf_it_should_only_send_value_1() {
29 | var sut = XssClientHelper.CreateClient(XssProtectionOption.Enabled);
30 | var resp = await sut.GetAsync("http://www.example.org");
31 | resp.XssProtection().Should().Be("1");
32 | }
33 | }
34 |
35 | internal static class XssClientHelper {
36 |
37 | public static HttpClient CreateClient(XssProtectionOption option) {
38 | return TestServer.Create(app => {
39 | app.UseOwin().XssProtectionHeader(option);
40 | app.Run(async context => await context.Response.WriteAsync("All fine"));
41 | }).HttpClient;
42 | }
43 |
44 | public static HttpClient CreateClient(bool disabled) {
45 | return TestServer.Create(app => {
46 | app.UseOwin().XssProtectionHeader(disabled);
47 | app.Run(async context => await context.Response.WriteAsync("All fine"));
48 | }).HttpClient;
49 | }
50 | }
51 | }
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | Security Headers Middleware
2 | ===========
3 |
4 | [](https://ci.appveyor.com/project/StefanOssendorf/securityheadersmiddleware) [](https://www.nuget.org/packages/SecurityHeadersMiddleware/) [](https://www.nuget.org/packages/SecurityHeadersMiddleware.AppBuilder/)
5 |
6 | Middlewares to set useful security-related HTTP headers in your OWIN application. (From [OWASP list](https://www.owasp.org/index.php/List_of_useful_HTTP_headers "OWASP list"))
7 |
8 | **Already implemented**
9 | - Strict-Transport-Security incl. options
10 | - X-Frame-Options incl. supporting multiple origins
11 | - X-XSS-Protection incl. disabling (but I don't know why).
12 | - X-Content-Type-Options
13 | - Content-Security-Policy 2 (except Hash and Nonce)
14 | - Content-Security-Policy-Report-Only
15 |
16 | **Workaround for using in .Net Core (Thanks to @imperugo)**
17 |
18 | https://github.com/aspnet-contrib/AspNet.Hosting.Extensions
19 |
20 | #### Using
21 | See the tests as examples of usage:
22 | - [Strict-Transport-Security](https://github.com/StefanOssendorf/OwinContrib.SecurityHeaders/blob/master/src/OwinContrib.SecurityHeaders.Tests/StrictTransportSecurityMiddlewareSpecs.cs)
23 | - [X-Frame-Options](https://github.com/StefanOssendorf/OwinContrib.SecurityHeaders/blob/master/src/OwinContrib.SecurityHeaders.Tests/AntiClickJackingMiddlewareSpecs.cs)
24 | - [X-XSS-Protection](https://github.com/StefanOssendorf/OwinContrib.SecurityHeaders/blob/master/src/OwinContrib.SecurityHeaders.Tests/XssProtectionHeaderMiddlewareSpecs.cs)
25 | - [X-Content-Type-Options](https://github.com/StefanOssendorf/OwinContrib.SecurityHeaders/blob/master/src/OwinContrib.SecurityHeaders.Tests/ContentTypeOptionsMiddleware.cs)
26 | - [Content-Security-Policy](https://github.com/StefanOssendorf/SecurityHeadersMiddleware/blob/master/src/OwinContrib.SecurityHeaders.Tests/CspMiddlewareTests.cs)
For Source-List usage see [Content-Security-Policy source lists](https://github.com/StefanOssendorf/SecurityHeadersMiddleware/blob/master/src/OwinContrib.SecurityHeaders.Tests/CspSourceListTests.cs)
27 |
28 | #### Developed with
29 | [MarkdownPad 2](http://markdownpad.com/ "MarkdownPad 2")
30 | [JetBrains ReSharper](http://www.jetbrains.com/resharper/ "R#")
31 |
--------------------------------------------------------------------------------
/src/OwinContrib.SecurityHeaders/CspSourceList.cs:
--------------------------------------------------------------------------------
1 | /*
2 | * Regex expression are from http://jmrware.com/articles/2009/uri_regexp/URI_regex.html
3 | */
4 |
5 | using System;
6 | using System.Collections.Generic;
7 | using System.Text;
8 | using SecurityHeadersMiddleware.Infrastructure;
9 |
10 | namespace SecurityHeadersMiddleware {
11 | ///
12 | /// Represents a source-list according to the CSP specification (http://www.w3.org/TR/CSP2/#source-list).
13 | ///
14 | public partial class CspSourceList : IDirectiveValueBuilder {
15 | private bool mIsNone;
16 |
17 | ///
18 | /// Initializes a new instance of the class.
19 | ///
20 | public CspSourceList() {
21 | mSchemes = new List();
22 | mKeywords = new List();
23 | mHosts = new HostSourceCollection();
24 | mIsNone = false;
25 | }
26 |
27 | ///
28 | /// Creates the directive header value.
29 | ///
30 | /// The directive header value without directive-name.
31 | public string ToDirectiveValue() {
32 | if (mIsNone) {
33 | return "'none'";
34 | }
35 | var sb = new StringBuilder();
36 | sb.AppendFormat(" {0} ", TrimAndEscape(BuildSchemeValues()));
37 | sb.AppendFormat(" {0} ", TrimAndEscape(BuildHostValues()));
38 | sb.AppendFormat(" {0} ", TrimAndEscape(BuildKeywordValues()));
39 | return sb.ToString().Trim();
40 | }
41 |
42 | ///
43 | /// Sets the source-list to 'none'.
After this nothing can be added and will cause an
44 | /// .
45 | ///
46 | public void SetToNone() {
47 | mIsNone = true;
48 | mSchemes.Clear();
49 | mKeywords.Clear();
50 | mHosts.Clear();
51 | }
52 |
53 | private void ThrowIfNoneIsSet() {
54 | if (mIsNone) {
55 | throw new InvalidOperationException("This list ist set to 'none'. No additional values are allowed. Don't set this liste to 'none' when you need to add values.");
56 | }
57 | }
58 |
59 | private static string TrimAndEscape(string input) {
60 | return input.Trim().PercentEncode();
61 | }
62 | }
63 | }
--------------------------------------------------------------------------------
/src/OwinContrib.SecurityHeaders.Tests/CsproMiddlewareTests.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Net.Http;
3 | using System.Threading.Tasks;
4 | using FluentAssertions;
5 | using Machine.Specifications;
6 | using Microsoft.Owin;
7 | using Microsoft.Owin.Testing;
8 | using Owin;
9 | using SecurityHeadersMiddleware.Infrastructure;
10 | using SecurityHeadersMiddleware.OwinAppBuilder;
11 | using Xunit;
12 |
13 | namespace SecurityHeadersMiddleware.Tests {
14 | public class CsproMiddlewareTests {
15 | [Fact]
16 | public async Task When_adding_csp_middleware_a_response_should_serve_the_csp_header() {
17 | var config = new ContentSecurityPolicyConfiguration();
18 | config.ScriptSrc.AddScheme("https:");
19 | config.ImgSrc.AddKeyword(SourceListKeyword.Self);
20 | var client = CsproClientHelper.Create(config);
21 | var response = await client.GetAsync("https://wwww.example.com");
22 | response.Cspro().Should().NotBeNullOrWhiteSpace();
23 | }
24 | [Fact]
25 | public async Task When_adding_csp_middleware_and_another_middleware_has_already_added_a_csp_header_the_middlewar_should_not_add_the_header()
26 | {
27 | var cfg = new ContentSecurityPolicyConfiguration();
28 | cfg.ScriptSrc.AddKeyword(SourceListKeyword.Self);
29 | var client = CsproClientHelper.Create(cfg,
30 | builder => builder.Use(async (ctx, next) => {
31 | ctx.Response.OnSendingHeaders(ctx2 => {
32 | ((IOwinResponse)ctx2).Headers.Add(HeaderConstants.ContentSecurityPolicyReportOnly, new[] { "Dummy" });
33 | }, ctx.Response);
34 | await next();
35 | }));
36 | var resp = await client.GetAsync("http://www.example.com");
37 | var header = resp.Cspro();
38 | header.ShouldEqual("Dummy");
39 | }
40 | }
41 |
42 | internal static class CsproClientHelper {
43 | public static HttpClient Create(ContentSecurityPolicyConfiguration configuration, Action intercepter = null) {
44 | return TestServer.Create(builder => {
45 | intercepter?.Invoke(builder);
46 | builder.UseOwin().ContentSecurityPolicyReportOnly(configuration);
47 | builder
48 | .Use((context, next) => {
49 | context.Response.StatusCode = 200;
50 | context.Response.ReasonPhrase = "OK";
51 | return Task.FromResult(0);
52 | });
53 | }).HttpClient;
54 | }
55 | }
56 | }
--------------------------------------------------------------------------------
/src/OwinContrib.SecurityHeaders.Tests/CspSandboxTokenListTests.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Linq;
3 | using FluentAssertions;
4 | using Xunit;
5 |
6 | namespace SecurityHeadersMiddleware.Tests {
7 | public class CspSandboxTokenListTests {
8 | [Fact]
9 | public void When_adding_an_invalid_sandboxToken_it_should_throw_a_formatException() {
10 | var list = new CspSandboxTokenList();
11 | Assert.Throws(() => list.AddToken("acd\"2asda"));
12 | }
13 |
14 | [Fact]
15 | public void When_adding_a_valid_sandboxToken_it_should_create_a_headerValue() {
16 | var list = new CspSandboxTokenList();
17 | list.AddToken("allow-scripts");
18 | list.ToDirectiveValue().Should().Be("allow-scripts");
19 | }
20 |
21 | [Fact]
22 | public void When_adding_a_token_twice_it_should_only_be_once_in_the_headerValue() {
23 | var list = new CspSandboxTokenList();
24 | list.AddToken("allow-scripts");
25 | list.AddToken("allow-scripts");
26 | list.ToDirectiveValue().Trim().Should().Be("allow-scripts");
27 | }
28 |
29 | [Fact]
30 | public void Keywords_should_create_the_correct_header_values() {
31 | var list = new CspSandboxTokenList();
32 | list.AddKeyword(SandboxKeyword.AllowForms);
33 | list.AddKeyword(SandboxKeyword.AllowPointerLock);
34 | list.AddKeyword(SandboxKeyword.AllowPopups);
35 | list.AddKeyword(SandboxKeyword.AllowSameOrigin);
36 | list.AddKeyword(SandboxKeyword.AllowScripts);
37 | list.AddKeyword(SandboxKeyword.AllowTopNavigation);
38 | var split = list.ToDirectiveValue().Split(new[] {" "}, StringSplitOptions.None).Select(item => item.Trim());
39 | var expectedValues = new[] {
40 | "allow-forms", "allow-pointer-lock", "allow-popups", "allow-same-origin", "allow-scripts", "allow-top-navigation"
41 | };
42 | split.Should().Contain(expectedValues);
43 | }
44 |
45 | [Fact]
46 | public void When_set_to_empty_it_should_return_an_empty_headerValue() {
47 | var list = new CspSandboxTokenList();
48 | list.SetToEmptyValue();
49 | list.ToDirectiveValue().Should().BeEmpty();
50 | }
51 |
52 | [Fact]
53 | public void When_add_an_invalid_keyword_it_should_throw_an_argumentOutOfRangeException() {
54 | var list = new CspSandboxTokenList();
55 | Assert.Throws(() => list.AddKeyword((SandboxKeyword) (-1)));
56 | }
57 | }
58 | }
--------------------------------------------------------------------------------
/src/SecurityHeadersMiddleware.PerformanceTests/Program.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Diagnostics;
4 |
5 | namespace SecurityHeadersMiddleware.PerformanceTests {
6 | class Program {
7 | static void Main(string[] args) {
8 | Console.WriteLine("Beginn {0}", DateTime.Now);
9 | //PerfConfigToHeaderValue();
10 | PerfAddHostToConfig();
11 | Console.WriteLine("Ende {0}", DateTime.Now);
12 | }
13 | const int iterations = 100000;
14 |
15 | private static void PerfAddHostToConfig() {
16 | var config = new ContentSecurityPolicyConfiguration();
17 | var uriList = new List();
18 |
19 | for (int i = 0; i < iterations; i++) {
20 | uriList.Add(string.Format("https://www.example{0}.org/abcd{0}/", i));
21 | }
22 | PrepareTest(() => config.ScriptSrc.AddHost("https://www.example.org/abcd/"));
23 |
24 | var sw = Stopwatch.StartNew();
25 | for (int i = 0; i < iterations; i++) {
26 | config.ScriptSrc.AddHost(uriList[i]);
27 | }
28 | sw.Stop();
29 | Console.WriteLine("PerfAddHostToConfig");
30 | PrintTime(sw);
31 | }
32 |
33 | private static void PerfConfigToHeaderValue() {
34 | var config = new ContentSecurityPolicyConfiguration();
35 | config.StyleSrc.AddKeyword(SourceListKeyword.Self);
36 | config.Sandbox.SetToEmptyValue();
37 | config.ConnectSrc.AddScheme("https");
38 | config.PluginTypes.AddMediaType("application/xml");
39 | config.BaseUri.AddScheme("https");
40 | config.BaseUri.AddHost("www.example.org");
41 |
42 | PrepareTest(() => config.ToHeaderValue());
43 |
44 | var sw = new Stopwatch();
45 | sw.Start();
46 | for (int i = 0; i < iterations; i++) {
47 | config.ToHeaderValue();
48 | Trace.Write(i);
49 | }
50 | sw.Stop();
51 |
52 | Console.WriteLine("PerfConfigToHeaderValue");
53 | PrintTime(sw);
54 | }
55 |
56 | private static void PrintTime(Stopwatch sw) {
57 | Console.WriteLine("Whole Time {0}", sw.Elapsed);
58 | Console.WriteLine("Average Time {0}", TimeSpan.FromMilliseconds((double) sw.ElapsedMilliseconds/iterations));
59 | Console.ReadLine();
60 | }
61 |
62 | private static void PrepareTest(Action action) {
63 | for (int i = 0; i < 20; i++) {
64 | action();
65 | }
66 | }
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/src/OwinContrib.SecurityHeaders.Tests/HostSourceTests.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.IO;
3 | using Xunit;
4 |
5 | namespace SecurityHeadersMiddleware.Tests {
6 |
7 | public class HostSourceTests {
8 | [Fact]
9 | public void When_creating_a_valid_full_host_source_it_should_not_throw_a_formatException() {
10 | new HostSource("http://*.example.com:80/path/");
11 | }
12 |
13 | [Fact]
14 | public void When_creating_a_valid_host_source_without_schemePart_it_should_not_throw_a_formatException() {
15 | new HostSource("*.example.com:80/path/");
16 | }
17 |
18 | [Fact]
19 | public void When_creating_a_valid_host_source_with_only_path_part_it_should_not_throw_a_formatException() {
20 | new HostSource("*.example.com/path/");
21 | }
22 |
23 | [Fact]
24 | public void When_creating_a_valid_host_source_with_only_host_part_it_should_not_throw_a_formatException() {
25 | new HostSource("*.example.com");
26 | }
27 |
28 | [Fact]
29 | public void When_creating_an_invalid_host_part_it_should_throw_a_formatException() {
30 | AssertFormatException("ftp://*.example./abcd/");
31 | }
32 |
33 | [Fact]
34 | public void When_creating_an_invalid_port_part_it_should_throw_a_formatException() {
35 | AssertFormatException("*.example.com:1as");
36 | }
37 |
38 | [Fact]
39 | public void When_creating_an_invalid_path_it_should_throw_a_formatException() {
40 | AssertFormatException("*.example.com/%1");
41 | }
42 |
43 | [Fact]
44 | public void When_creating_an_empty_host_it_should_throw_a_argumentException() {
45 | Assert.Throws(() => new HostSource(""));
46 | }
47 |
48 | [Fact]
49 | public void When_creating_a_valid_host_source_with_queryString() {
50 | new HostSource("http://www.exmpale.com/abcd/?a=1");
51 | }
52 |
53 | [Fact]
54 | public void When_creating_a_valid_host_source_with_fragment() {
55 | new HostSource("http://www.example.com/abcd/#clearly");
56 | }
57 |
58 | [Fact]
59 | public void When_creating_a_host_with_invalid_scheme_it_should_throw_a_formatException() {
60 | AssertFormatException(".http://www.example.com");
61 | }
62 |
63 | [Fact]
64 | public void When_creating_a_host_without_hostPart_it_should_throw_a_formatException() {
65 | AssertFormatException("http://:80/path/");
66 | }
67 |
68 | [Fact]
69 | public void When_creating_a_host_with_only_star_char_should_be_valid() {
70 | new HostSource("*");
71 | }
72 |
73 | private static void AssertFormatException(string value) {
74 | Assert.Throws(() => new HostSource(value));
75 | }
76 | }
77 | }
--------------------------------------------------------------------------------
/src/OwinContrib.SecurityHeaders/AntiClickjackingMiddleware.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Threading.Tasks;
5 | using SecurityHeadersMiddleware.Infrastructure;
6 | using SecurityHeadersMiddleware.LibOwin;
7 |
8 | namespace SecurityHeadersMiddleware {
9 | internal static class AntiClickjackingMiddleware {
10 | public static Func, Task>, Func, Task>> AntiClickjackingHeader(XFrameOption option) {
11 | return AntiClickjackingHeader((int) option);
12 | }
13 |
14 | public static Func, Task>, Func, Task>> AntiClickjackingHeader(params string[] origins) {
15 | var uriOrigins = origins.Where(o => !string.IsNullOrWhiteSpace(o)).Select(o => new Uri(o)).ToArray();
16 | return AntiClickjackingHeader(uriOrigins);
17 | }
18 |
19 | public static Func, Task>, Func, Task>> AntiClickjackingHeader(params Uri[] origins) {
20 | return AntiClickjackingHeader(3, origins);
21 | }
22 |
23 | public static Func, Task>, Func, Task>> AntiClickjackingHeader(int option, params Uri[] origins) {
24 | return next =>
25 | env => {
26 | var context = env.AsContext();
27 | var response = context.Response;
28 | var options = new HeaderOptions {
29 | FrameOption = option,
30 | Origins = origins,
31 | Response = response,
32 | RequestUri = context.Request.Uri
33 | };
34 | response.OnSendingHeaders(ApplyHeader, options);
35 | return next(env);
36 | };
37 | }
38 |
39 | private static void ApplyHeader(HeaderOptions obj) {
40 | var value = "";
41 | switch (obj.FrameOption) {
42 | case 1:
43 | value = "DENY";
44 | break;
45 | case 2:
46 | value = "SAMEORIGIN";
47 | break;
48 | default:
49 | value = DetermineValue(obj.Origins, obj.RequestUri);
50 | break;
51 | }
52 | obj.Response.Headers[HeaderConstants.XFrameOptions] = value;
53 | }
54 |
55 | private static string DetermineValue(Uri[] origins, Uri requestUri) {
56 | var uri = Array.Find(origins, u => Rfc6454Utility.HasSameOrigin(u, requestUri));
57 | return uri == null ? "DENY" : "ALLOW-FROM {0}".FormatWith(Rfc6454Utility.SerializeOrigin(uri));
58 | }
59 |
60 | private class HeaderOptions {
61 | public int FrameOption { get; set; }
62 | public Uri[] Origins { get; set; }
63 | public IOwinResponse Response { get; set; }
64 | public Uri RequestUri { get; set; }
65 | }
66 | }
67 | }
--------------------------------------------------------------------------------
/src/OwinContrib.SecurityHeaders/StrictTransportSecurityOptions.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace SecurityHeadersMiddleware {
4 | ///
5 | /// Options for strict-transport-security (STS).
6 | ///
7 | public class StrictTransportSecurityOptions {
8 | private const uint DefaultMaxAge = 31536000; // 12 Month
9 | private Func mRedirectReasonPhrase;
10 | private Func mRedirectUriBuilder;
11 |
12 | ///
13 | /// Initializes a new instance of the class.
14 | ///
15 | public StrictTransportSecurityOptions() {
16 | MaxAge = DefaultMaxAge;
17 | IncludeSubDomains = true;
18 | RedirectUriBuilder = DefaultRedirectUriBuilder;
19 | }
20 |
21 | ///
22 | /// Gets or sets the max-age header value (in seconds).
23 | ///
24 | ///
25 | /// The max-age parameter value.
26 | ///
27 | public uint MaxAge { get; set; }
28 |
29 | ///
30 | /// Gets or sets a value indicating whether [include sub domains].
31 | ///
32 | ///
33 | /// true if [include sub domains]; otherwise, false.
34 | ///
35 | public bool IncludeSubDomains { get; set; }
36 |
37 | ///
38 | /// Gets or sets a value indicating whether to in include the preload parameter or not.
39 | ///
40 | public bool Preload { get; set; }
41 |
42 | ///
43 | /// Gets or sets a value indicating whether redirect to secure transport or not.
44 | ///
45 | ///
46 | /// true if [redirect to secure transport]; otherwise, false.
47 | ///
48 | public bool RedirectToSecureTransport { get; set; }
49 |
50 | ///
51 | /// Gets or sets the delegate to set a reasonphrase.
52 | /// Default reasonphrase is empty.
53 | ///
54 | public Func RedirectReasonPhrase {
55 | get { return mRedirectReasonPhrase ?? DefaultResonPhrase; }
56 | set { mRedirectReasonPhrase = value; }
57 | }
58 |
59 | ///
60 | /// Gets or sets the delegate to build the redirect uri.
61 | /// Default builder changes the url to https scheme.
62 | ///
63 | public Func RedirectUriBuilder {
64 | get { return mRedirectUriBuilder; }
65 | set { mRedirectUriBuilder = value ?? DefaultRedirectUriBuilder; }
66 | }
67 |
68 | private static string DefaultResonPhrase(int statusCode) {
69 | return "";
70 | }
71 |
72 | private static string DefaultRedirectUriBuilder(Uri uri) {
73 | var builder = new UriBuilder(uri) {
74 | Scheme = Uri.UriSchemeHttps,
75 | Port = -1
76 | };
77 | return builder.ToString();
78 | }
79 | }
80 | }
--------------------------------------------------------------------------------
/src/OwinContrib.SecurityHeaders.Tests/CspMediaTypeListTests.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using Xunit;
3 |
4 | namespace SecurityHeadersMiddleware.Tests {
5 | public class CspMediaTypeListTests {
6 | [Fact]
7 | public void When_trying_to_add_a_mediaType_without_slash_it_should_throw_a_formatException() {
8 | var csp = new CspMediaTypeList();
9 | Assert.Throws(() => csp.AddMediaType("abcd"));
10 | }
11 |
12 | [Fact]
13 | public void When_trying_to_add_a_mediaType_with_a_ctl_in_type_it_should_throw_a_formatException() {
14 | var csp = new CspMediaTypeList();
15 | var mediaType = (char) 10 + "acd/x-abcd";
16 | Assert.Throws(() => csp.AddMediaType(mediaType));
17 | }
18 |
19 | [Fact]
20 | public void When_trying_to_add_a_mediaType_with_a_space_it_should_throw_a_formatException() {
21 | var csp = new CspMediaTypeList();
22 | Assert.Throws(() => csp.AddMediaType("ac d/x-abcd"));
23 | }
24 |
25 | [Fact]
26 | public void When_trying_to_add_an_invalid_subType_it_should_throw_a_formatException() {
27 | var csp = new CspMediaTypeList();
28 | Assert.Throws(() => csp.AddMediaType("xml/x;sd"));
29 | }
30 |
31 | // Source of mediatypes: https://www.iana.org/assignments/media-types/media-types.xhtml
32 | [Fact]
33 | public void When_adding_some_IANA_specified_mediaTypes_it_should_not_throw() {
34 | var csp = new CspMediaTypeList();
35 | csp.AddMediaType("text/1d-interleaved-parityfec");
36 | csp.AddMediaType("text/provenance-notation");
37 | csp.AddMediaType("text/vnd.net2phone.commcenter.command");
38 | csp.AddMediaType("video/H264-RCDO");
39 | csp.AddMediaType("video/vnd.dece.mobile");
40 | csp.AddMediaType("video/vnd.iptvforum.1dparityfec-2005");
41 | csp.AddMediaType("multipart/form-data");
42 | csp.AddMediaType("multipart/form-data");
43 | csp.AddMediaType("multipart/report");
44 | csp.AddMediaType("model/vnd.valve.source.compiled-map");
45 | csp.AddMediaType("model/vnd.moml+xml");
46 | csp.AddMediaType("model/example");
47 | csp.AddMediaType("message/global-disposition-notification");
48 | csp.AddMediaType("message/disposition-notification");
49 | csp.AddMediaType("message/s-http");
50 | csp.AddMediaType("image/vnd.sealedmedia.softseal-jpg");
51 | csp.AddMediaType("image/vnd.airzip.accelerator.azv");
52 | csp.AddMediaType("image/jpeg");
53 | csp.AddMediaType("audio/vnd.sealedmedia.softseal-mpeg");
54 | csp.AddMediaType("audio/vnd.dolby.heaac.2");
55 | csp.AddMediaType("audio/dsr-es202050");
56 | csp.AddMediaType("application/xml-external-parsed-entity");
57 | csp.AddMediaType("application/vnd.yamaha.openscoreformat.osfpvg+xml");
58 | csp.AddMediaType("application/xml");
59 | }
60 |
61 | [Fact]
62 | public void When_adding_valid_mediaTypes_it_should_not_throw() {
63 | var csp = new CspMediaTypeList();
64 | csp.AddMediaType("text/plain");
65 | csp.AddMediaType("text/x-myownsubtype");
66 | }
67 | }
68 | }
--------------------------------------------------------------------------------
/src/OwinContrib.SecurityHeaders.Tests/CspMiddlewareTests.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Linq;
3 | using System.Net.Http;
4 | using System.Threading.Tasks;
5 | using FluentAssertions;
6 | using Machine.Specifications;
7 | using Microsoft.Owin;
8 | using Microsoft.Owin.Testing;
9 | using Owin;
10 | using SecurityHeadersMiddleware.Infrastructure;
11 | using SecurityHeadersMiddleware.OwinAppBuilder;
12 | using Xunit;
13 |
14 | namespace SecurityHeadersMiddleware.Tests {
15 | public class CspMiddlewareTests {
16 | [Fact]
17 | public async Task When_adding_csp_middleware_a_response_should_serve_the_csp_header() {
18 | var config = new ContentSecurityPolicyConfiguration();
19 | config.ScriptSrc.AddScheme("https:");
20 | config.ImgSrc.AddKeyword(SourceListKeyword.Self);
21 | var client = CspClientHelper.Create(config);
22 | var response = await client.GetAsync("https://wwww.example.com");
23 | response.Csp().Should().NotBeNullOrWhiteSpace();
24 | }
25 |
26 | [Fact]
27 | public async Task When_adding_csp_middleware_the_response_should_contain_the_expected_csp_directives() {
28 | var config = new ContentSecurityPolicyConfiguration();
29 | config.ScriptSrc.AddScheme("https:");
30 | config.ImgSrc.AddKeyword(SourceListKeyword.Self);
31 | var client = CspClientHelper.Create(config);
32 | var response = await client.GetAsync("https://wwww.example.com");
33 | var headerValue = response.Csp();
34 | var values = headerValue.Split(new[] {";"}, StringSplitOptions.None).Select(i => i.Trim()).ToList();
35 | values.Count.Should().Be(2);
36 | values.Should().Contain(i => i.Equals("img-src 'self'"));
37 | values.Should().Contain(i => i.Equals("script-src https:"));
38 | }
39 |
40 | [Fact]
41 | public async Task When_adding_csp_middleware_and_another_middleware_has_already_added_a_csp_header_the_middlewar_should_not_add_the_header() {
42 | var cfg = new ContentSecurityPolicyConfiguration();
43 | cfg.ScriptSrc.AddKeyword(SourceListKeyword.Self);
44 | var client = CspClientHelper.Create(cfg,
45 | builder => builder.Use(async (ctx,next) => {
46 | ctx.Response.OnSendingHeaders(ctx2 => {
47 | ((IOwinResponse)ctx2).Headers.Add(HeaderConstants.ContentSecurityPolicy, new []{"Dummy"});
48 | }, ctx.Response);
49 | await next();
50 | }));
51 | var resp = await client.GetAsync("http://www.example.com");
52 | var header = resp.Csp();
53 | header.ShouldEqual("Dummy");
54 | }
55 | }
56 |
57 | internal static class CspClientHelper {
58 | public static HttpClient Create(ContentSecurityPolicyConfiguration configuration, Action intercepter = null) {
59 | return TestServer.Create(builder => {
60 | intercepter?.Invoke(builder);
61 | builder.UseOwin().ContentSecurityPolicy(configuration);
62 | builder
63 | .Use((context, next) => {
64 | context.Response.StatusCode = 200;
65 | context.Response.ReasonPhrase = "OK";
66 | return Task.FromResult(0);
67 | });
68 | }).HttpClient;
69 | }
70 |
71 | }
72 | }
--------------------------------------------------------------------------------
/src/SecurityHeadersMiddleware.OwinAppBuilder/SecurityHeadersMiddleware.OwinAppBuilder.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Debug
6 | AnyCPU
7 | {D3381FFC-FFA0-478D-9708-199E2F9789F5}
8 | Library
9 | Properties
10 | SecurityHeadersMiddleware.OwinAppBuilder
11 | SecurityHeadersMiddleware.OwinAppBuilder
12 | v4.5
13 | 512
14 |
15 |
16 | true
17 | full
18 | false
19 | bin\Debug\
20 | DEBUG;TRACE
21 | prompt
22 | 4
23 | bin\Debug\SecurityHeadersMiddleware.OwinAppBuilder.XML
24 |
25 |
26 | pdbonly
27 | true
28 | bin\Release\
29 | TRACE
30 | prompt
31 | 4
32 | bin\Release\SecurityHeadersMiddleware.OwinAppBuilder.XML
33 |
34 |
35 |
36 | ..\packages\Owin.1.0\lib\net40\Owin.dll
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 | Properties\SharedAssemblyInfo.cs
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 | {53953109-6b63-4a49-bda3-eb78e1506fa9}
59 | SecurityHeadersMiddleware
60 |
61 |
62 |
63 |
70 |
--------------------------------------------------------------------------------
/src/OwinContrib.SecurityHeaders/StrictTransportSecurityHeaderMiddleware.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Threading.Tasks;
4 | using SecurityHeadersMiddleware.Infrastructure;
5 | using SecurityHeadersMiddleware.LibOwin;
6 |
7 | namespace SecurityHeadersMiddleware {
8 | internal static class StrictTransportSecurityHeaderMiddleware {
9 | public static Func, Task>, Func, Task>> StrictTransportSecurityHeader(StrictTransportSecurityOptions options) {
10 | return next =>
11 | env => {
12 | var context = env.AsContext();
13 | var request = context.Request;
14 | if (RedirectToSecureTransport(options, request)) {
15 | SetResponseForRedirect(context, options);
16 | return Task.FromResult(0);
17 | }
18 |
19 | // Only over secure transport (http://tools.ietf.org/html/rfc6797#section-7.2)
20 | // Quotation: "An HSTS Host MUST NOT include the STS header field in HTTP responses conveyed over non-secure transport."
21 | if (request.IsSecure) {
22 | var response = context.Response;
23 | var state = new State {
24 | Settings = options,
25 | Response = response
26 | };
27 | response.OnSendingHeaders(ApplyHeader, state);
28 | }
29 | return next(env);
30 | };
31 | }
32 |
33 | private static void SetResponseForRedirect(IOwinContext context, StrictTransportSecurityOptions options) {
34 | var response = context.Response;
35 | response.StatusCode = 301;
36 | response.ReasonPhrase = options.RedirectReasonPhrase(301);
37 | response.Headers[HeaderConstants.Location] = options.RedirectUriBuilder(context.Request.Uri);
38 | }
39 |
40 | private static void ApplyHeader(State obj) {
41 | var options = obj.Settings;
42 | var response = (OwinResponse) obj.Response;
43 | response.Headers[HeaderConstants.StrictTransportSecurity] = ConstructHeaderValue(options);
44 | }
45 |
46 | private static string ConstructHeaderValue(StrictTransportSecurityOptions options) {
47 | var age = MaxAge(options.MaxAge);
48 | var subDomains = IncludeSubDomains(options.IncludeSubDomains);
49 | var preload = Preload(options.Preload);
50 | return "{0}{1}{2}".FormatWith(age, subDomains, preload);
51 | }
52 |
53 | #region [ Helper ]
54 |
55 | private static string MaxAge(uint seconds) {
56 | return "max-age={0}".FormatWith(seconds);
57 | }
58 |
59 | private static string IncludeSubDomains(bool includeSubDomains) {
60 | return includeSubDomains ? "; includeSubDomains" : "";
61 | }
62 |
63 | private static string Preload(bool preload) {
64 | return preload ? "; preload" : "";
65 | }
66 |
67 | private static bool RedirectToSecureTransport(StrictTransportSecurityOptions options, IOwinRequest request) {
68 | return options.RedirectToSecureTransport && !request.IsSecure;
69 | }
70 |
71 | #endregion
72 | }
73 | }
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | #################
2 | ## Eclipse
3 | #################
4 |
5 | *.pydevproject
6 | .project
7 | .metadata
8 | bin/
9 | tmp/
10 | *.tmp
11 | *.bak
12 | *.swp
13 | *~.nib
14 | local.properties
15 | .classpath
16 | .settings/
17 | .loadpath
18 |
19 | # External tool builders
20 | .externalToolBuilders/
21 |
22 | # Locally stored "Eclipse launch configurations"
23 | *.launch
24 |
25 | # CDT-specific
26 | .cproject
27 |
28 | # PDT-specific
29 | .buildpath
30 |
31 |
32 | #################
33 | ## Visual Studio
34 | #################
35 |
36 | ## Ignore Visual Studio temporary files, build results, and
37 | ## files generated by popular Visual Studio add-ons.
38 |
39 | # User-specific files
40 | *.suo
41 | *.user
42 | *.sln.docstates
43 |
44 | # Build results
45 |
46 | [Dd]ebug/
47 | [Rr]elease/
48 | x64/
49 | build/
50 | [Bb]in/
51 | [Oo]bj/
52 |
53 | # MSTest test Results
54 | [Tt]est[Rr]esult*/
55 | [Bb]uild[Ll]og.*
56 |
57 | *_i.c
58 | *_p.c
59 | *.ilk
60 | *.meta
61 | *.obj
62 | *.pch
63 | *.pdb
64 | *.pgc
65 | *.pgd
66 | *.rsp
67 | *.sbr
68 | *.tlb
69 | *.tli
70 | *.tlh
71 | *.tmp
72 | *.tmp_proj
73 | *.log
74 | *.vspscc
75 | *.vssscc
76 | .builds
77 | *.pidb
78 | *.log
79 | *.scc
80 |
81 | # Visual C++ cache files
82 | ipch/
83 | *.aps
84 | *.ncb
85 | *.opensdf
86 | *.sdf
87 | *.cachefile
88 |
89 | # Visual Studio profiler
90 | *.psess
91 | *.vsp
92 | *.vspx
93 |
94 | # Guidance Automation Toolkit
95 | *.gpState
96 |
97 | # ReSharper is a .NET coding add-in
98 | _ReSharper*/
99 | *.[Rr]e[Ss]harper
100 |
101 | # TeamCity is a build add-in
102 | _TeamCity*
103 |
104 | # DotCover is a Code Coverage Tool
105 | *.dotCover
106 |
107 | # NCrunch
108 | *.ncrunch*
109 | .*crunch*.local.xml
110 |
111 | # Installshield output folder
112 | [Ee]xpress/
113 |
114 | # DocProject is a documentation generator add-in
115 | DocProject/buildhelp/
116 | DocProject/Help/*.HxT
117 | DocProject/Help/*.HxC
118 | DocProject/Help/*.hhc
119 | DocProject/Help/*.hhk
120 | DocProject/Help/*.hhp
121 | DocProject/Help/Html2
122 | DocProject/Help/html
123 |
124 | # Click-Once directory
125 | publish/
126 |
127 | # Publish Web Output
128 | *.Publish.xml
129 | *.pubxml
130 |
131 | # NuGet Packages Directory
132 | ## TODO: If you have NuGet Package Restore enabled, uncomment the next line
133 | #packages/
134 |
135 | # Windows Azure Build Output
136 | csx
137 | *.build.csdef
138 |
139 | # Windows Store app package directory
140 | AppPackages/
141 |
142 | # Others
143 | sql/
144 | *.Cache
145 | ClientBin/
146 | [Ss]tyle[Cc]op.*
147 | ~$*
148 | *~
149 | *.dbmdl
150 | *.[Pp]ublish.xml
151 | *.pfx
152 | *.publishsettings
153 |
154 | # RIA/Silverlight projects
155 | Generated_Code/
156 |
157 | # Backup & report files from converting an old project file to a newer
158 | # Visual Studio version. Backup files are not needed, because we have git ;-)
159 | _UpgradeReport_Files/
160 | Backup*/
161 | UpgradeLog*.XML
162 | UpgradeLog*.htm
163 |
164 | # SQL Server files
165 | App_Data/*.mdf
166 | App_Data/*.ldf
167 |
168 | #############
169 | ## Windows detritus
170 | #############
171 |
172 | # Windows image file caches
173 | Thumbs.db
174 | ehthumbs.db
175 |
176 | # Folder config file
177 | Desktop.ini
178 |
179 | # Recycle Bin used on file shares
180 | $RECYCLE.BIN/
181 |
182 | # Mac crap
183 | .DS_Store
184 |
185 |
186 | #############
187 | ## Python
188 | #############
189 |
190 | *.py[co]
191 |
192 | # Packages
193 | *.egg
194 | *.egg-info
195 | dist/
196 | build/
197 | eggs/
198 | parts/
199 | var/
200 | sdist/
201 | develop-eggs/
202 | .installed.cfg
203 |
204 | # Installer logs
205 | pip-log.txt
206 |
207 | # Unit test / coverage reports
208 | .coverage
209 | .tox
210 |
211 | #Translations
212 | *.mo
213 |
214 | #Mr Developer
215 | .mr.developer.cfg
216 | /src/packages
217 | *.nupkg
218 |
--------------------------------------------------------------------------------
/src/SecurityHeadersMiddleware.Examples/SecurityHeadersMiddleware.Examples.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Debug
6 | AnyCPU
7 | {75703E81-F232-4F6B-ACCA-7D39F9B44ABF}
8 | Library
9 | Properties
10 | SecurityHeadersMiddleware.Examples
11 | SecurityHeadersMiddleware.Examples
12 | v4.5
13 | 512
14 |
15 |
16 | true
17 | full
18 | false
19 | bin\Debug\
20 | DEBUG;TRACE
21 | prompt
22 | 4
23 |
24 |
25 | pdbonly
26 | true
27 | bin\Release\
28 | TRACE
29 | prompt
30 | 4
31 |
32 |
33 |
34 | False
35 | ..\packages\Owin.1.0\lib\net40\Owin.dll
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 | Properties\SharedAssemblyInfo.cs
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 | {53953109-6b63-4a49-bda3-eb78e1506fa9}
58 | SecurityHeadersMiddleware
59 |
60 |
61 | {d3381ffc-ffa0-478d-9708-199e2f9789f5}
62 | SecurityHeadersMiddleware.OwinAppBuilder
63 |
64 |
65 |
66 |
67 |
68 |
69 |
76 |
--------------------------------------------------------------------------------
/src/OwinContrib.SecurityHeaders/CspMediaTypeList.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using SecurityHeadersMiddleware.Infrastructure;
6 |
7 | namespace SecurityHeadersMiddleware {
8 | ///
9 | /// Represents a media-type-list according to the CSP specification (http://www.w3.org/TR/CSP2/#media-type-list).
10 | ///
11 | public class CspMediaTypeList : IDirectiveValueBuilder {
12 | private const string TSpecial = "()<>@,;:\\\"/[]?= ";
13 | private readonly List mMediaTypes;
14 |
15 | ///
16 | /// Initializes a new instance of the class.
17 | ///
18 | public CspMediaTypeList() {
19 | mMediaTypes = new List();
20 | }
21 |
22 | ///
23 | /// Creates the directive header value.
24 | ///
25 | /// The directive header value without directive-name.
26 | public string ToDirectiveValue() {
27 | if (mMediaTypes.Count == 0) {
28 | return "";
29 | }
30 | var sb = new StringBuilder();
31 | foreach (var mediaType in mMediaTypes) {
32 | sb.AppendFormat(" {0} ", mediaType);
33 | }
34 | return sb.ToString();
35 | }
36 |
37 | ///
38 | /// Adds a media type to the media-type-list.
39 | ///
40 | /// The media type.
41 | public void AddMediaType(string mediaType) {
42 | mediaType.MustNotBeWhiteSpaceOrEmpty("mediaType");
43 | VerifyMediaType(mediaType);
44 | mMediaTypes.Add(mediaType);
45 | }
46 |
47 | private static void VerifyMediaType(string mediaType) {
48 | var split = mediaType.Split(new[] {"/"}, StringSplitOptions.None);
49 | if (split.Length != 2 || split[0].Length == 0 || split[1].Length == 0) {
50 | const string msg = "Mediatype value '{0}' does not satisfy the required format.{1}" +
51 | "Valid mediatypes: text/plain or text/html{1}" +
52 | "For more information see: {2} (media-type).";
53 | throw new FormatException(msg.FormatWith(mediaType, Environment.NewLine, "http://www.w3.org/TR/CSP2/#media-type-list1"));
54 | }
55 | VerifyType(split[0].ToLower(), mediaType);
56 | VeritySubType(split[1].ToLower(), mediaType);
57 | }
58 |
59 | private static void VerifyType(string type, string mediaType) {
60 | if (type.All(IsToken)) {
61 | return;
62 | }
63 | const string msg = "The type part '{0} of mediatype '{1}' does not satisfy the required format.{2}" +
64 | "Type contains illegal characters.{2}" +
65 | "For more information see: {3} (media-type).";
66 | throw new FormatException(msg.FormatWith(type, mediaType, Environment.NewLine, "http://www.w3.org/TR/CSP2/#media-type-list1"));
67 | }
68 |
69 | private static void VeritySubType(string subType, string mediaType) {
70 | if (subType.All(IsToken)) {
71 | return;
72 | }
73 | const string msg = "The subtype part '{0} of mediatype '{1}' does not satisfy the required format.{2}" +
74 | "Subtype contains illegal characters.{2}" +
75 | "For more information see: {3} (media-type).";
76 | throw new FormatException(msg.FormatWith(subType, mediaType, Environment.NewLine, "http://www.w3.org/TR/CSP2/#media-type-list1"));
77 | }
78 |
79 | private static bool IsToken(char value) {
80 | return value.IsAscii() && !value.IsCTL() && !TSpecial.Contains(value);
81 | }
82 | }
83 | }
--------------------------------------------------------------------------------
/src/SecurityHeadersMiddleware.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio 14
4 | VisualStudioVersion = 14.0.22823.1
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Documents", "Documents", "{E768EE25-8180-4CAA-88F4-B9C8A5E16E66}"
7 | ProjectSection(SolutionItems) = preProject
8 | Release notes.txt = Release notes.txt
9 | Roadmap.txt = Roadmap.txt
10 | EndProjectSection
11 | EndProject
12 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SecurityHeadersMiddleware", "OwinContrib.SecurityHeaders\SecurityHeadersMiddleware.csproj", "{53953109-6B63-4A49-BDA3-EB78E1506FA9}"
13 | EndProject
14 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SecurityHeadersMiddleware.Tests", "OwinContrib.SecurityHeaders.Tests\SecurityHeadersMiddleware.Tests.csproj", "{753D4EEA-13E5-429B-93AC-4A7E7A1FACD3}"
15 | EndProject
16 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SecurityHeadersMiddleware.OwinAppBuilder", "SecurityHeadersMiddleware.OwinAppBuilder\SecurityHeadersMiddleware.OwinAppBuilder.csproj", "{D3381FFC-FFA0-478D-9708-199E2F9789F5}"
17 | EndProject
18 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{9795E771-F940-4C4F-AAD8-7638AA405C09}"
19 | ProjectSection(SolutionItems) = preProject
20 | SharedAssemblyInfo.cs = SharedAssemblyInfo.cs
21 | EndProjectSection
22 | EndProject
23 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SecurityHeadersMiddleware.Examples", "SecurityHeadersMiddleware.Examples\SecurityHeadersMiddleware.Examples.csproj", "{75703E81-F232-4F6B-ACCA-7D39F9B44ABF}"
24 | EndProject
25 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".nuget", ".nuget", "{707156A5-7AE4-461D-9764-48B4B7700F49}"
26 | EndProject
27 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SecurityHeadersMiddleware.PerformanceTests", "SecurityHeadersMiddleware.PerformanceTests\SecurityHeadersMiddleware.PerformanceTests.csproj", "{6948B148-499B-4D14-B4CA-D3268D6E2A95}"
28 | EndProject
29 | Global
30 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
31 | Debug|Any CPU = Debug|Any CPU
32 | Release|Any CPU = Release|Any CPU
33 | EndGlobalSection
34 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
35 | {53953109-6B63-4A49-BDA3-EB78E1506FA9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
36 | {53953109-6B63-4A49-BDA3-EB78E1506FA9}.Debug|Any CPU.Build.0 = Debug|Any CPU
37 | {53953109-6B63-4A49-BDA3-EB78E1506FA9}.Release|Any CPU.ActiveCfg = Release|Any CPU
38 | {53953109-6B63-4A49-BDA3-EB78E1506FA9}.Release|Any CPU.Build.0 = Release|Any CPU
39 | {753D4EEA-13E5-429B-93AC-4A7E7A1FACD3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
40 | {753D4EEA-13E5-429B-93AC-4A7E7A1FACD3}.Debug|Any CPU.Build.0 = Debug|Any CPU
41 | {753D4EEA-13E5-429B-93AC-4A7E7A1FACD3}.Release|Any CPU.ActiveCfg = Release|Any CPU
42 | {753D4EEA-13E5-429B-93AC-4A7E7A1FACD3}.Release|Any CPU.Build.0 = Release|Any CPU
43 | {D3381FFC-FFA0-478D-9708-199E2F9789F5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
44 | {D3381FFC-FFA0-478D-9708-199E2F9789F5}.Debug|Any CPU.Build.0 = Debug|Any CPU
45 | {D3381FFC-FFA0-478D-9708-199E2F9789F5}.Release|Any CPU.ActiveCfg = Release|Any CPU
46 | {D3381FFC-FFA0-478D-9708-199E2F9789F5}.Release|Any CPU.Build.0 = Release|Any CPU
47 | {75703E81-F232-4F6B-ACCA-7D39F9B44ABF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
48 | {75703E81-F232-4F6B-ACCA-7D39F9B44ABF}.Debug|Any CPU.Build.0 = Debug|Any CPU
49 | {75703E81-F232-4F6B-ACCA-7D39F9B44ABF}.Release|Any CPU.ActiveCfg = Release|Any CPU
50 | {75703E81-F232-4F6B-ACCA-7D39F9B44ABF}.Release|Any CPU.Build.0 = Release|Any CPU
51 | {6948B148-499B-4D14-B4CA-D3268D6E2A95}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
52 | {6948B148-499B-4D14-B4CA-D3268D6E2A95}.Debug|Any CPU.Build.0 = Debug|Any CPU
53 | {6948B148-499B-4D14-B4CA-D3268D6E2A95}.Release|Any CPU.ActiveCfg = Release|Any CPU
54 | {6948B148-499B-4D14-B4CA-D3268D6E2A95}.Release|Any CPU.Build.0 = Release|Any CPU
55 | EndGlobalSection
56 | GlobalSection(SolutionProperties) = preSolution
57 | HideSolutionNode = FALSE
58 | EndGlobalSection
59 | EndGlobal
60 |
--------------------------------------------------------------------------------
/src/OwinContrib.SecurityHeaders.Tests/CspConfigurationTests.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using FluentAssertions;
5 | using Xunit;
6 |
7 | namespace SecurityHeadersMiddleware.Tests {
8 | public class CspConfigurationTests {
9 | [Fact]
10 | public void When_generating_header_value_and_no_configurations_are_set_return_empty_string() {
11 | var config = new ContentSecurityPolicyConfiguration();
12 | config.ToHeaderValue().Should().BeEmpty();
13 | }
14 |
15 | [Fact]
16 | public void When_set_scriptSrc_to_none_the_header_value_should_contain_the_directive_with_none() {
17 | var config = new ContentSecurityPolicyConfiguration();
18 | config.ScriptSrc.SetToNone();
19 | config.ToHeaderValue().Should().Be("script-src 'none'");
20 | }
21 |
22 | [Fact]
23 | public void When_set_two_sources_they_should_be_separated_by_a_semicolon() {
24 | var config = new ContentSecurityPolicyConfiguration();
25 | config.ScriptSrc.AddScheme("https");
26 | config.ImgSrc.AddKeyword(SourceListKeyword.Self);
27 | var value = config.ToHeaderValue();
28 | var split = value.Split(new[] {";"}, StringSplitOptions.None);
29 | split.Length.Should().Be(2);
30 | split.Should().Contain(item => item.Trim().Equals("script-src https:"));
31 | split.Should().Contain(item => item.Trim().Equals("img-src 'self'"));
32 | }
33 |
34 | [Fact]
35 | public void Source_types_should_be_separated_by_a_semicolon() {
36 | var config = new ContentSecurityPolicyConfiguration();
37 | config.StyleSrc.AddKeyword(SourceListKeyword.Self);
38 | config.ImgSrc.AddScheme("https");
39 | config.MediaSrc.AddKeyword(SourceListKeyword.UnsafeInline);
40 | config.BaseUri.AddScheme("https");
41 | var split = config.ToHeaderValue().Split(new[] {";"}, StringSplitOptions.None);
42 | split.Length.Should().Be(4);
43 | }
44 |
45 | [Fact]
46 | public void All_source_types_should_be_in_the_header_value() {
47 | var config = new ContentSecurityPolicyConfiguration();
48 | config.BaseUri.AddKeyword(SourceListKeyword.Self);
49 | config.ChildSrc.AddKeyword(SourceListKeyword.Self);
50 | config.ConnectSrc.AddKeyword(SourceListKeyword.Self);
51 | config.DefaultSrc.AddKeyword(SourceListKeyword.Self);
52 | config.FontSrc.AddKeyword(SourceListKeyword.Self);
53 | config.FormAction.AddKeyword(SourceListKeyword.Self);
54 | config.FrameAncestors.SetToNone();
55 | config.FrameSrc.AddKeyword(SourceListKeyword.Self);
56 | config.ImgSrc.AddKeyword(SourceListKeyword.Self);
57 | config.MediaSrc.AddKeyword(SourceListKeyword.Self);
58 | config.ObjectSrc.AddKeyword(SourceListKeyword.Self);
59 | config.ScriptSrc.AddKeyword(SourceListKeyword.Self);
60 | config.StyleSrc.AddKeyword(SourceListKeyword.Self);
61 | config.PluginTypes.AddMediaType("application/xml");
62 | config.ReportUri.AddReportUri("https://www.example.com/report-uri");
63 | config.Sandbox.AddToken("allow-scripts");
64 | var expected = new List {
65 | "base-uri", "child-src", "connect-src", "default-src", "font-src", "form-action", "frame-ancestors", "frame-src",
66 | "img-src", "media-src", "object-src", "plugin-types", "report-uri", "sandbox",
67 | "script-src", "style-src"
68 | };
69 | var values = config.ToHeaderValue().Split(new[] {";"}, StringSplitOptions.None).SelectMany(i => i.Split(new[] {" "}, StringSplitOptions.None)).ToList();
70 | values.Should().Contain(expected);
71 | }
72 |
73 | [Fact]
74 | public void When_sandbox_value_is_set_to_empty_the_directive_should_be_created() {
75 | var config = new ContentSecurityPolicyConfiguration();
76 | config.Sandbox.SetToEmptyValue();
77 | config.ToHeaderValue().Split(new[] {";"}, StringSplitOptions.None).Select(item => item.Trim()).Should().OnlyContain(item => item == "sandbox");
78 | }
79 | }
80 | }
--------------------------------------------------------------------------------
/src/OwinContrib.SecurityHeaders/CspSandboxTokenList.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Text;
4 | using SecurityHeadersMiddleware.Infrastructure;
5 |
6 | namespace SecurityHeadersMiddleware {
7 | ///
8 | /// Represents a sandbox-token-list according to the CSP specification (http://www.w3.org/TR/CSP2/#directive-sandbox).
9 | ///
10 | public class CspSandboxTokenList : IDirectiveValueBuilder {
11 | private readonly List mTokens;
12 |
13 | ///
14 | /// Initializes a new instance of the class.
15 | ///
16 | public CspSandboxTokenList() {
17 | mTokens = new List();
18 | IsEmpty = false;
19 | }
20 |
21 | internal bool IsEmpty { get; private set; }
22 |
23 | ///
24 | /// Creates the directive header value.
25 | ///
26 | /// The directive header value without directive-name.
27 | public string ToDirectiveValue() {
28 | if (IsEmpty) {
29 | return "";
30 | }
31 | var sb = new StringBuilder();
32 | foreach (var token in mTokens) {
33 | sb.AppendFormat(" {0} ", token);
34 | }
35 | return sb.ToString().Trim();
36 | }
37 |
38 | ///
39 | /// Adds the keyword to the sandbox-token-list.
40 | ///
41 | /// The keyword.
42 | public void AddKeyword(SandboxKeyword keyword) {
43 | var token = TokenValueOfKeyword(keyword);
44 | AddToken(token);
45 | }
46 |
47 | ///
48 | /// Adds a token to the sandbox-token-list.
49 | ///
50 | /// The token.
51 | ///
52 | /// For more information see: http://www.w3.org/TR/CSP2/#sandbox-token
53 | ///
54 | public void AddToken(string token) {
55 | token.MustNotNull("token");
56 | token.MustNotBeWhiteSpaceOrEmpty("token");
57 | if (mTokens.Contains(token)) {
58 | return;
59 | }
60 | if (!Rfc7230Utility.IsToken(token)) {
61 | const string msg = "Token value '{0}' is invalid.{1}" +
62 | "Valid tokens: allow-forms or abcedfg{1}" +
63 | "All characters must be a-z, A-Z, 0-9 or !, #, $, %, &, \\, *, +, -, ., ^, _, `, |, ~ " +
64 | "For more Information see: {2}";
65 | throw new FormatException(msg.FormatWith(token, Environment.NewLine, "http://www.w3.org/TR/CSP2/#directive-sandbox"));
66 | }
67 | mTokens.Add(token);
68 | IsEmpty = false;
69 | }
70 |
71 | ///
72 | /// Sets the value to an empty token.
73 | /// According to http://lists.w3.org/Archives/Public/public-webappsec/2014Aug/0019.html and
74 | /// http://developers.whatwg.org/the-iframe-element.html#attr-iframe-sandbox it's an valid exception.
75 | ///
76 | public void SetToEmptyValue() {
77 | mTokens.Clear();
78 | IsEmpty = true;
79 | }
80 |
81 | private static string TokenValueOfKeyword(SandboxKeyword keyword) {
82 | switch (keyword) {
83 | case SandboxKeyword.AllowForms:
84 | return "allow-forms";
85 | case SandboxKeyword.AllowPointerLock:
86 | return "allow-pointer-lock";
87 | case SandboxKeyword.AllowPopups:
88 | return "allow-popups";
89 | case SandboxKeyword.AllowSameOrigin:
90 | return "allow-same-origin";
91 | case SandboxKeyword.AllowScripts:
92 | return "allow-scripts";
93 | case SandboxKeyword.AllowTopNavigation:
94 | return "allow-top-navigation";
95 | default:
96 | throw new ArgumentOutOfRangeException("keyword");
97 | }
98 | }
99 | }
100 | }
--------------------------------------------------------------------------------
/src/OwinContrib.SecurityHeaders.Tests/AntiClickJackingMiddlewareSpecs.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Net.Http;
3 | using System.Threading.Tasks;
4 | using Machine.Specifications;
5 | using Microsoft.Owin.Testing;
6 | using Owin;
7 | using SecurityHeadersMiddleware.OwinAppBuilder;
8 |
9 | namespace SecurityHeadersMiddleware.Tests {
10 | public abstract class AntiClickjackingSpecBase : OwinEnvironmentSpecBase {
11 | private Because of = () => { Response = Client.GetAsync("http://example.com").Await(); };
12 | }
13 |
14 | [Subject(typeof (AntiClickjackingMiddleware))]
15 | public class When_using_use_with_no_parameter : AntiClickjackingSpecBase {
16 | private Establish context = () => Client = ClientHelper.CreateClient();
17 | private It should_set_xFrameOptions_header_to_deny = () => Response.XFrameOptionsHeader().ShouldEqual("DENY");
18 | }
19 |
20 | [Subject(typeof (AntiClickjackingMiddleware))]
21 | public class When_using_use_with_xFrameOption_deny : AntiClickjackingSpecBase {
22 | private Establish context = () => Client = ClientHelper.CreateClient(XFrameOption.Deny);
23 | private It should_set_xFrameOptions_header_to_deny = () => Response.XFrameOptionsHeader().ShouldEqual("DENY");
24 | }
25 |
26 | [Subject(typeof (AntiClickjackingMiddleware))]
27 | public class When_using_use_with_xFrameOption_sameOrigin : AntiClickjackingSpecBase {
28 | private Establish context = () => Client = ClientHelper.CreateClient(XFrameOption.SameOrigin);
29 | private It should_set_xFrameOptions_header_to_sameOrigin = () => Response.XFrameOptionsHeader().ShouldEqual("SAMEORIGIN");
30 | }
31 |
32 | [Subject(typeof (AntiClickjackingMiddleware))]
33 | public class When_using_use_with_example_com_origin : AntiClickjackingSpecBase {
34 | private Establish context = () => Client = ClientHelper.CreateClient(new Uri("http://example.com"));
35 | private It should_set_xFrameOptions_header_to_allowFrom_example_com_origin = () => Response.XFrameOptionsHeader().ShouldEqual("ALLOW-FROM http://example.com");
36 | }
37 |
38 | [Subject(typeof (AntiClickjackingMiddleware))]
39 | public class When_using_use_with_multiple_origins_configured {
40 | private static HttpClient Client;
41 | private Establish context = () => Client = ClientHelper.CreateClient("http://example.com", "http://example.org");
42 |
43 | private It should_set_xFrameOptions_header_to_allowFrom_example_com_origin = () => {
44 | HttpResponseMessage response = Client.GetAsync("http://example.com").Await();
45 | response.XFrameOptionsHeader().ShouldEqual("ALLOW-FROM http://example.com");
46 | };
47 |
48 | private It should_set_xFrameOptions_header_to_allowFrom_example_org_origin = () => {
49 | HttpResponseMessage response = Client.GetAsync("http://example.org").Await();
50 | response.XFrameOptionsHeader().ShouldEqual("ALLOW-FROM http://example.org");
51 | };
52 |
53 | private It should_set_xFrameOptions_header_to_deny_with_unknown_origin = () => {
54 | HttpResponseMessage response = Client.GetAsync("http://unknown.org").Await();
55 | response.XFrameOptionsHeader().ShouldEqual("DENY");
56 | };
57 | }
58 |
59 | internal static class ClientHelper {
60 | public static HttpClient CreateClient() {
61 | return CreateClient(b => b.UseOwin().AntiClickjackingHeader());
62 | }
63 |
64 | public static HttpClient CreateClient(XFrameOption option) {
65 | return CreateClient(b => b.UseOwin().AntiClickjackingHeader(option));
66 | }
67 |
68 | public static HttpClient CreateClient(params string[] origins) {
69 | return CreateClient(b => b.UseOwin().AntiClickjackingHeader(origins));
70 | }
71 |
72 | public static HttpClient CreateClient(params Uri[] origins) {
73 | return CreateClient(b => b.UseOwin().AntiClickjackingHeader(origins));
74 | }
75 |
76 | private static HttpClient CreateClient(Action registerAntiClickjackingMiddleware) {
77 | return TestServer.Create(builder => {
78 | registerAntiClickjackingMiddleware(builder);
79 | builder
80 | .Use((context, next) => {
81 | context.Response.StatusCode = 200;
82 | context.Response.ReasonPhrase = "OK";
83 | return Task.FromResult(0);
84 | });
85 | }).HttpClient;
86 | }
87 | }
88 | }
--------------------------------------------------------------------------------
/src/SecurityHeadersMiddleware.PerformanceTests/SecurityHeadersMiddleware.PerformanceTests.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Debug
6 | AnyCPU
7 | {6948B148-499B-4D14-B4CA-D3268D6E2A95}
8 | Exe
9 | Properties
10 | SecurityHeadersMiddleware.PerformanceTests
11 | SecurityHeadersMiddleware.PerformanceTests
12 | v4.5
13 | 512
14 | true
15 |
16 |
17 |
18 | AnyCPU
19 | true
20 | full
21 | false
22 | bin\Debug\
23 | DEBUG;TRACE
24 | prompt
25 | 4
26 |
27 |
28 | AnyCPU
29 | pdbonly
30 | true
31 | bin\Release\
32 | TRACE
33 | prompt
34 | 4
35 |
36 |
37 |
38 | ..\packages\Microsoft.Owin.3.0.1\lib\net45\Microsoft.Owin.dll
39 | True
40 |
41 |
42 | ..\packages\Microsoft.Owin.Hosting.3.0.1\lib\net45\Microsoft.Owin.Hosting.dll
43 | True
44 |
45 |
46 | ..\packages\Microsoft.Owin.Testing.3.0.1\lib\net45\Microsoft.Owin.Testing.dll
47 | True
48 |
49 |
50 | ..\packages\Owin.1.0\lib\net40\Owin.dll
51 | True
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 | {53953109-6B63-4A49-BDA3-EB78E1506FA9}
73 | SecurityHeadersMiddleware
74 |
75 |
76 | {d3381ffc-ffa0-478d-9708-199e2f9789f5}
77 | SecurityHeadersMiddleware.OwinAppBuilder
78 |
79 |
80 |
81 |
88 |
--------------------------------------------------------------------------------
/src/OwinContrib.SecurityHeaders/CspAncestorSourceList.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Text;
4 | using System.Text.RegularExpressions;
5 | using SecurityHeadersMiddleware.Infrastructure;
6 |
7 | namespace SecurityHeadersMiddleware {
8 | ///
9 | /// Represents a ancestor-source-list according to the CSP specification
10 | /// (http://www.w3.org/TR/CSP2/#ancestor_source_list).
11 | ///
12 | public class CspAncestorSourceList : IDirectiveValueBuilder {
13 | private readonly HostSourceCollection mHosts;
14 | private readonly List mSchemes;
15 | private bool mIsNone;
16 |
17 | ///
18 | /// Initializes a new instance of the class.
19 | ///
20 | public CspAncestorSourceList() {
21 | mSchemes = new List();
22 | mHosts = new HostSourceCollection();
23 | mIsNone = false;
24 | }
25 |
26 | ///
27 | /// Creates the directive header value.
28 | ///
29 | /// The directive header value without directive-name.
30 | public string ToDirectiveValue() {
31 | if (mIsNone) {
32 | return "'none'";
33 | }
34 | var sb = new StringBuilder();
35 | sb.AppendFormat(" {0} ", BuildHostValues());
36 | sb.AppendFormat(" {0} ", BuildSchemeValues());
37 | return sb.ToString().Trim().PercentEncode();
38 | }
39 |
40 | ///
41 | /// Adds a scheme to the ancestor-source-list.
42 | ///
43 | /// The scheme.
44 | ///
45 | /// has to satisfy this regex: (^[a-z][a-z0-9+\-.]*)(:?)$
46 | /// For more information see http://www.w3.org/TR/CSP2/#scheme-source
47 | ///
48 | public void AddScheme(string scheme) {
49 | ThrowIfNoneIsSet();
50 | scheme.MustNotNull("scheme");
51 | scheme = scheme.ToLower();
52 | const string schemeRegex = @"(^[a-z][a-z0-9+\-.]*)(:?)$";
53 | var match = Regex.Match(scheme, schemeRegex, RegexOptions.IgnoreCase);
54 | if (!match.Success) {
55 | const string msg = "Scheme value '{0}' is invalid.{1}" +
56 | "Valid schemes:{1}http: or http or ftp: or ftp{1}" +
57 | "First charachter must be a letter.{1}" +
58 | "For more Information see: {2}";
59 | throw new FormatException(msg.FormatWith(scheme, Environment.NewLine, "http://www.w3.org/TR/CSP2/#scheme-source"));
60 | }
61 | var schemeToAdd = "{0}:".FormatWith(match.Groups[1].Value.ToLower());
62 | if (mSchemes.Contains(schemeToAdd)) {
63 | return;
64 | }
65 | mSchemes.Add(schemeToAdd);
66 | }
67 |
68 | ///
69 | /// Adds a host to the ancestor-source-list.
70 | ///
71 | /// The host.
72 | public void AddHost(Uri host) {
73 | AddHost(host.GetComponents(UriComponents.SchemeAndServer | UriComponents.Path, UriFormat.UriEscaped));
74 | }
75 |
76 | ///
77 | /// Adds a host to the ancestor-source-list.
78 | ///
79 | /// The host.
80 | public void AddHost(string host) {
81 | ThrowIfNoneIsSet();
82 | host.MustNotNull("host");
83 | host.MustNotBeWhiteSpaceOrEmpty("host");
84 | var source = new HostSource(host);
85 | mHosts.Add(source);
86 | }
87 |
88 | ///
89 | /// Sets the ancestor-source-list to 'none'.
After this nothing can be added and will cause an
90 | /// .
91 | ///
92 | public void SetToNone() {
93 | mSchemes.Clear();
94 | mHosts.Clear();
95 | mIsNone = true;
96 | }
97 |
98 | private string BuildSchemeValues() {
99 | return string.Join(" ", mSchemes).Trim();
100 | }
101 |
102 | private string BuildHostValues() {
103 | return string.Join(" ", mHosts).Trim();
104 | }
105 |
106 | private void ThrowIfNoneIsSet() {
107 | if (mIsNone) {
108 | throw new InvalidOperationException("This list ist set to 'none'. No additional values are allowed. Don't set this liste to 'none' when you need to add values.");
109 | }
110 | }
111 | }
112 | }
--------------------------------------------------------------------------------
/src/OwinContrib.SecurityHeaders.Tests/CspAncestorSourceListTests.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using FluentAssertions;
3 | using Machine.Specifications;
4 | using Xunit;
5 |
6 | namespace SecurityHeadersMiddleware.Tests {
7 | public class CspAncestorSourceListTests {
8 | private CspAncestorSourceList CreateSUT() {
9 | return new CspAncestorSourceList();
10 | }
11 |
12 | [Fact]
13 | public void When_adding_a_valid_host_source_it_should_succeed() {
14 | var list = CreateSUT();
15 | list.AddHost("http://*.example.com:80/path/");
16 | }
17 |
18 | [Fact]
19 | public void When_adding_an_invalid_host_source_it_should_fail() {
20 | var list = CreateSUT();
21 | Assert.Throws(() => list.AddHost("holy crap this will fail!"));
22 | }
23 |
24 | [Fact]
25 | public void When_adding_an_invalid_scheme_it_should_throw_a_formatException() {
26 | var list = CreateSUT();
27 | Assert.Throws(() => list.AddScheme("+invalid"));
28 | }
29 |
30 | [Fact]
31 | public void When_adding_an_scheme_it_should_create_the_correct_value_for_the_header() {
32 | var list = CreateSUT();
33 | list.AddScheme("http");
34 | list.ToDirectiveValue().Trim().ShouldEqual("http:");
35 | }
36 |
37 | [Fact]
38 | public void When_adding_multiple_schemes_it_should_create_the_schemes_whiteSpace_separated() {
39 | var list = CreateSUT();
40 | list.AddScheme("http");
41 | list.AddScheme("ftp");
42 | list.AddScheme("https");
43 | list.ToDirectiveValue().ShouldEqual("http: ftp: https:");
44 | }
45 |
46 | [Fact]
47 | public void When_set_list_to_none_adding_a_scheme_should_throw_an_invalidOperationException() {
48 | var list = CreateSUT();
49 | list.SetToNone();
50 | Assert.Throws(() => list.AddScheme("http"));
51 | }
52 |
53 | [Fact]
54 | public void When_set_list_to_none_adding_a_host_should_throw_an_invalidOperationException() {
55 | var list = CreateSUT();
56 | list.SetToNone();
57 | Assert.Throws(() => list.AddHost("http://www.example.com"));
58 | }
59 |
60 | [Fact]
61 | public void When_set_list_to_none_it_should_create_header_value_with_none() {
62 | var list = CreateSUT();
63 | list.SetToNone();
64 | list.ToDirectiveValue().ShouldEqual("'none'");
65 | }
66 |
67 | [Fact]
68 | public void When_adding_one_scheme_multiple_times_it_should_only_be_once_in_the_header_value() {
69 | var list = CreateSUT();
70 | list.AddScheme("http:");
71 | list.AddScheme("http");
72 | list.ToDirectiveValue().Trim().ShouldEqual("http:");
73 | }
74 |
75 | [Fact]
76 | public void When_adding_one_host_it_should_create_the_correct_header_value() {
77 | var list = CreateSUT();
78 | list.AddHost("http://*.example.com:*/path/file.js");
79 | list.ToDirectiveValue().Trim().ShouldEqual("http://*.example.com:*/path/file.js");
80 | }
81 |
82 | [Fact]
83 | public void When_adding_one_host_multiple_times_it_should_only_be_once_in_the_header_value() {
84 | var sut = CreateSUT();
85 | sut.AddHost("http://www.example.org");
86 | sut.AddHost("http://www.example.org");
87 | sut.ToDirectiveValue().Trim().Should().Be("http://www.example.org");
88 | }
89 |
90 | [Fact]
91 | public void When_adding_an_http_host_with_and_without_default_values_they_should_be_treated_as_equal() {
92 | var sut = CreateSUT();
93 | sut.AddHost("http://www.example.org");
94 | sut.AddHost("http://www.example.org:80");
95 | sut.ToDirectiveValue().Trim().Should().Be("http://www.example.org");
96 | }
97 |
98 | [Fact]
99 | public void When_adding_a_valid_uri_as_host_it_should_not_throw_a_exception() {
100 | var list = CreateSUT();
101 | list.AddHost(new Uri("https://www.example.com/abcd/"));
102 | }
103 |
104 | [Fact]
105 | public void When_using_a_semicolon_in_a_value_it_should_be_escaped() {
106 | var list = CreateSUT();
107 | list.AddHost("http://example.com/abcd;/asdas");
108 | list.ToDirectiveValue().Should().Be("http://example.com/abcd%3B/asdas");
109 | }
110 |
111 | [Fact]
112 | public void When_using_a_comma_in_a_value_it_should_be_escaped() {
113 | var list = CreateSUT();
114 | list.AddHost("http://example.com/abcd,/asdas");
115 | list.ToDirectiveValue().Should().Be("http://example.com/abcd%2C/asdas");
116 | }
117 |
118 | }
119 | }
--------------------------------------------------------------------------------
/src/OwinContrib.SecurityHeaders/CspUriReferenceList.cs:
--------------------------------------------------------------------------------
1 | /*
2 | * Regex expression are from http://jmrware.com/articles/2009/uri_regexp/URI_regex.html
3 | */
4 |
5 | using System;
6 | using System.Collections.Generic;
7 | using System.Text;
8 | using System.Text.RegularExpressions;
9 | using SecurityHeadersMiddleware.Infrastructure;
10 |
11 | namespace SecurityHeadersMiddleware {
12 | ///
13 | /// Represents an uri-reference-list according to the CSP specification
14 | /// http://www.w3.org/TR/CSP2/#directive-report-uri).
15 | ///
16 | public class CspUriReferenceList : IDirectiveValueBuilder {
17 | #region Regex
18 |
19 | private static readonly Regex Rfc3986UriRegex = new Regex(@" ^
20 | # RFC-3986 URI component: URI
21 | [A-Za-z][A-Za-z0-9+\-.]* : # scheme "":""
22 | (?: // # hier-part
23 | (?: (?:[A-Za-z0-9\-._~!$&'()*+,;=:]|%[0-9A-Fa-f]{2})* @)?
24 | (?:
25 | \[
26 | (?:
27 | (?:
28 | (?: (?:[0-9A-Fa-f]{1,4}:){6}
29 | | :: (?:[0-9A-Fa-f]{1,4}:){5}
30 | | (?: [0-9A-Fa-f]{1,4})? :: (?:[0-9A-Fa-f]{1,4}:){4}
31 | | (?: (?:[0-9A-Fa-f]{1,4}:){0,1} [0-9A-Fa-f]{1,4})? :: (?:[0-9A-Fa-f]{1,4}:){3}
32 | | (?: (?:[0-9A-Fa-f]{1,4}:){0,2} [0-9A-Fa-f]{1,4})? :: (?:[0-9A-Fa-f]{1,4}:){2}
33 | | (?: (?:[0-9A-Fa-f]{1,4}:){0,3} [0-9A-Fa-f]{1,4})? :: [0-9A-Fa-f]{1,4}:
34 | | (?: (?:[0-9A-Fa-f]{1,4}:){0,4} [0-9A-Fa-f]{1,4})? ::
35 | ) (?:
36 | [0-9A-Fa-f]{1,4} : [0-9A-Fa-f]{1,4}
37 | | (?: (?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?) \.){3}
38 | (?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)
39 | )
40 | | (?: (?:[0-9A-Fa-f]{1,4}:){0,5} [0-9A-Fa-f]{1,4})? :: [0-9A-Fa-f]{1,4}
41 | | (?: (?:[0-9A-Fa-f]{1,4}:){0,6} [0-9A-Fa-f]{1,4})? ::
42 | )
43 | | [Vv][0-9A-Fa-f]+\.[A-Za-z0-9\-._~!$&'()*+,;=:]+
44 | )
45 | \]
46 | | (?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}
47 | (?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)
48 | | (?:[A-Za-z0-9\-._~!$&'()*+,;=]|%[0-9A-Fa-f]{2})*
49 | )
50 | (?: : [0-9]* )?
51 | (?:/ (?:[A-Za-z0-9\-._~!$&'()*+,;=:@]|%[0-9A-Fa-f]{2})* )*
52 | | /
53 | (?: (?:[A-Za-z0-9\-._~!$&'()*+,;=:@]|%[0-9A-Fa-f]{2})+
54 | (?:/ (?:[A-Za-z0-9\-._~!$&'()*+,;=:@]|%[0-9A-Fa-f]{2})* )*
55 | )?
56 | | (?:[A-Za-z0-9\-._~!$&'()*+,;=:@]|%[0-9A-Fa-f]{2})+
57 | (?:/ (?:[A-Za-z0-9\-._~!$&'()*+,;=:@]|%[0-9A-Fa-f]{2})* )*
58 | |
59 | )
60 | (?:\? (?:[A-Za-z0-9\-._~!$&'()*+,;=:@/?]|%[0-9A-Fa-f]{2})* )? # [ ""?"" query ]
61 | (?:\# (?:[A-Za-z0-9\-._~!$&'()*+,;=:@/?]|%[0-9A-Fa-f]{2})* )? # [ ""#"" fragment ]
62 | $ ", RegexOptions.IgnorePatternWhitespace);
63 |
64 | #endregion
65 |
66 | private readonly List mUris;
67 |
68 | ///
69 | /// Initializes a new instance of the class.
70 | ///
71 | public CspUriReferenceList() {
72 | mUris = new List();
73 | }
74 |
75 | ///
76 | /// Creates the directive header value.
77 | ///
78 | /// The directive header value without directive-name.
79 | public string ToDirectiveValue() {
80 | var sb = new StringBuilder();
81 | foreach (var uri in mUris) {
82 | sb.AppendFormat(" {0} ", uri);
83 | }
84 | return sb.ToString();
85 | }
86 |
87 | ///
88 | /// Adds a uri to the uri-reference-list.
89 | ///
90 | /// The uri.
91 | public void AddReportUri(Uri uri) {
92 | uri.MustNotNull("uri");
93 |
94 | var uriString = uri.ToString();
95 | //TODO Contains necessary?
96 | if (mUris.Contains(uriString)) {
97 | return;
98 | }
99 | mUris.Add(uriString);
100 | }
101 |
102 | ///
103 | /// Adds a uri to the uri-reference-list.
104 | ///
105 | /// The uri.
106 | ///
107 | /// has to satisfy the URI definition.
108 | /// For more information see: http://www.w3.org/TR/CSP2/#uri-reference respectively
109 | /// http://tools.ietf.org/html/rfc3986#section-4.1
110 | ///
111 | public void AddReportUri(string uri) {
112 | uri.MustNotNull("uri");
113 | uri.MustNotBeWhiteSpaceOrEmpty("uri");
114 | AddReportUri(new Uri(uri));
115 | }
116 | }
117 | }
--------------------------------------------------------------------------------
/src/OwinContrib.SecurityHeaders.Tests/StrictTransportSecurityMiddlewareSpecs.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Linq;
3 | using System.Net;
4 | using System.Net.Http;
5 | using System.Threading.Tasks;
6 | using FluentAssertions;
7 | using Machine.Specifications;
8 | using Microsoft.Owin.Testing;
9 | using Owin;
10 | using SecurityHeadersMiddleware.Infrastructure;
11 | using SecurityHeadersMiddleware.OwinAppBuilder;
12 | using Xunit;
13 |
14 | namespace SecurityHeadersMiddleware.Tests {
15 | [Subject(typeof (StrictTransportSecurityHeaderMiddleware))]
16 | public class When_using_sts_default_implementation_over_non_secure_transport : OwinEnvironmentSpecBase {
17 | private Establish context = () => { Client = StsClientHelper.Create(); };
18 | private Because of = () => Response = Client.GetAsync("http://www.example.org").Await();
19 | private It Should_not_return_sts_header = () => Response.Headers.Contains(HeaderConstants.StrictTransportSecurity).ShouldBeFalse();
20 | private It should_not_send_a_location_header = () => Response.Headers.Location.ShouldBeNull();
21 | private It Should_not_set_statusCode_to_301 = () => Response.StatusCode.ShouldNotEqual(HttpStatusCode.MovedPermanently);
22 | }
23 |
24 | [Subject(typeof (StrictTransportSecurityHeaderMiddleware))]
25 | public class When_using_sts_default_implementation_over_secure_transport : OwinEnvironmentSpecBase {
26 | private Establish context = () => { Client = StsClientHelper.Create(); };
27 | private Because of = () => Response = Client.GetAsync("https://www.example.org").Await();
28 | private It should_contain_sts_header = () => Response.StsHeader().Any().ShouldBeTrue();
29 | private It Should_have_includeSubDomains_as_value = () => Response.StsHeader().ShouldContain("includeSubDomains");
30 | private It should_have_maxAge_with_31536000 = () => Response.StsHeader().Single(h => h.StartsWith("max-age=", StringComparison.OrdinalIgnoreCase)).ShouldBeEqualIgnoringCase("max-age=31536000");
31 | }
32 |
33 | [Subject(typeof (StrictTransportSecurityHeaderMiddleware))]
34 | public class When_using_configured_sts_over_secure_transport : OwinEnvironmentSpecBase {
35 | private Establish context = () => {
36 | Client = StsClientHelper.Create(
37 | new StrictTransportSecurityOptions {
38 | IncludeSubDomains = false,
39 | MaxAge = 0
40 | });
41 | };
42 |
43 | private Because of = () => Response = Client.GetAsync("https://www.example.org").Await();
44 | private It should_have_maxAge_with_0 = () => Response.StsHeader().ShouldContain(hv => hv.EqualsIgnoreCase("max-age=0"));
45 | private It should_not_contain_includeSubDomains = () => Response.StsHeader().ShouldNotContain(hv => hv.EqualsIgnoreCase("includeSubDomains"));
46 | }
47 |
48 | [Subject(typeof (StrictTransportSecurityHeaderMiddleware))]
49 | public class When_using_configured_sts_over_non_secure_transport : OwinEnvironmentSpecBase {
50 | private Establish context = () => {
51 | Client = StsClientHelper.Create(
52 | new StrictTransportSecurityOptions {
53 | RedirectToSecureTransport = true,
54 | RedirectReasonPhrase = sc => "Custom Resonphrase"
55 | });
56 | };
57 |
58 | private Because of = () => Response = Client.GetAsync("http://www.example.org").Await();
59 | private It should_set_custom_reasonPhrase = () => Response.ReasonPhrase.ShouldEqual("Custom Resonphrase");
60 | private It should_set_location_hedaer_to_https_scheme_uri = () => Response.Headers.Location.ShouldEqual(new Uri("https://www.example.org"));
61 | private It should_set_statusCode_to_301 = () => Response.StatusCode.ShouldEqual(HttpStatusCode.MovedPermanently);
62 | }
63 |
64 | public class StrictTransportSecurityTests {
65 | [Fact]
66 | public async Task When_redirecting_to_https_the_default_should_return_no_reasonPhrase() {
67 | var client = StsClientHelper.Create(new StrictTransportSecurityOptions {
68 | RedirectToSecureTransport = true
69 | });
70 | var response = await client.GetAsync("http://www.example.org");
71 | response.ReasonPhrase.Should().BeEmpty();
72 | }
73 |
74 | [Fact]
75 | public async Task When_preload_is_set_it_should_be_included_in_the_response_header_value() {
76 | var client = StsClientHelper.Create(new StrictTransportSecurityOptions {
77 | Preload = true,
78 | MaxAge = 50
79 | });
80 | var response = await client.GetAsync("https://wwww.example.org");
81 | response.StsHeader().Should().Contain("preload");
82 | }
83 | }
84 |
85 | internal class StsClientHelper {
86 | public static HttpClient Create() {
87 | return Create(new StrictTransportSecurityOptions());
88 | }
89 |
90 | public static HttpClient Create(StrictTransportSecurityOptions strictTransportSecurityOptions) {
91 | return TestServer.Create(builder => {
92 | builder.UseOwin().StrictTransportSecurity(strictTransportSecurityOptions);
93 | builder.Use((context, next) => {
94 | context.Response.StatusCode = 200;
95 | context.Response.ReasonPhrase = "OK";
96 | return Task.FromResult(0);
97 | });
98 | }).HttpClient;
99 | }
100 | }
101 |
102 | internal static class StringExtensions {
103 | public static bool EqualsIgnoreCase(this string source, string toCompare) {
104 | return source.Equals(toCompare, StringComparison.OrdinalIgnoreCase);
105 | }
106 | }
107 | }
--------------------------------------------------------------------------------
/src/OwinContrib.SecurityHeaders.Tests/CspSourceListTests.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using FluentAssertions;
3 | using Machine.Specifications;
4 | using Xunit;
5 |
6 | namespace SecurityHeadersMiddleware.Tests {
7 | public class CspSourceListTests {
8 | [Fact]
9 | public void When_adding_an_invalid_scheme_it_should_throw_a_formatException() {
10 | var list = new CspSourceList();
11 | Assert.Throws(() => list.AddScheme("+invalid"));
12 | }
13 |
14 | [Fact]
15 | public void When_adding_an_scheme_it_should_create_the_correct_value_for_the_header() {
16 | var list = new CspSourceList();
17 | list.AddScheme("http");
18 | list.ToDirectiveValue().Trim().ShouldEqual("http:");
19 | }
20 |
21 | [Fact]
22 | public void When_adding_multiple_schemes_it_should_create_the_schemes_whiteSpace_separated() {
23 | var list = new CspSourceList();
24 | list.AddScheme("http");
25 | list.AddScheme("ftp");
26 | list.AddScheme("https");
27 | list.ToDirectiveValue().ShouldEqual("http: ftp: https:");
28 | }
29 |
30 | [Fact]
31 | public void When_set_list_to_none_adding_a_scheme_should_throw_an_invalidOperationException() {
32 | var list = new CspSourceList();
33 | list.SetToNone();
34 | Assert.Throws(() => list.AddScheme("http"));
35 | }
36 |
37 | [Fact]
38 | public void When_set_list_to_none_adding_a_host_should_throw_an_invalidOperationException() {
39 | var list = new CspSourceList();
40 | list.SetToNone();
41 | Assert.Throws(() => list.AddHost("http://www.example.com"));
42 | }
43 |
44 | [Fact]
45 | public void When_set_list_to_none_adding_a_keyWord_should_throw_an_invalidOperationException() {
46 | var list = new CspSourceList();
47 | list.SetToNone();
48 | Assert.Throws(() => list.AddKeyword(SourceListKeyword.Self));
49 | }
50 |
51 | [Fact]
52 | public void When_set_list_to_none_it_should_create_header_value_with_none() {
53 | var list = new CspSourceList();
54 | list.SetToNone();
55 | list.ToDirectiveValue().ShouldEqual("'none'");
56 | }
57 |
58 | [Fact]
59 | public void When_adding_a_keyword_it_should_create_the_correct_value_for_the_header() {
60 | var list = new CspSourceList();
61 | list.AddKeyword(SourceListKeyword.Self);
62 | list.ToDirectiveValue().Trim().ShouldEqual("'self'");
63 | }
64 |
65 | [Fact]
66 | public void When_adding_a_keyword_multiple_times_it_should_return_it_only_once_in_header_value() {
67 | var list = new CspSourceList();
68 | list.AddKeyword(SourceListKeyword.Self);
69 | list.AddKeyword(SourceListKeyword.Self);
70 | list.AddKeyword(SourceListKeyword.Self);
71 | list.AddKeyword(SourceListKeyword.Self);
72 | list.ToDirectiveValue().Trim().ShouldEqual("'self'");
73 | }
74 |
75 | [Fact]
76 | public void When_addin_mutliple_keywords_it_should_create_the_keywords_whiteSpace_separated() {
77 | var list = new CspSourceList();
78 | list.AddKeyword(SourceListKeyword.Self);
79 | list.AddKeyword(SourceListKeyword.UnsafeEval);
80 | list.AddKeyword(SourceListKeyword.Self);
81 | list.AddKeyword(SourceListKeyword.UnsafeInline);
82 | list.AddKeyword(SourceListKeyword.UnsafeRedirect);
83 | list.ToDirectiveValue().Trim().ShouldEqual("'self' 'unsafe-eval' 'unsafe-inline' 'unsafe-redirect'");
84 | }
85 |
86 | [Fact]
87 | public void When_adding_one_scheme_multiple_times_it_should_only_be_once_in_the_header_value() {
88 | var list = new CspSourceList();
89 | list.AddScheme("http:");
90 | list.AddScheme("http");
91 | list.ToDirectiveValue().Trim().ShouldEqual("http:");
92 | }
93 |
94 | [Fact]
95 | public void When_adding_a_valid_host_source_it_should_succeed() {
96 | var list = new CspSourceList();
97 | list.AddHost("http://*.example.com:80/path/");
98 | }
99 |
100 | [Fact]
101 | public void When_adding_an_invalid_host_source_it_should_fail() {
102 | var list = new CspSourceList();
103 | Assert.Throws(() => list.AddHost("holy crap this will fail!"));
104 | }
105 |
106 | [Fact]
107 | public void When_adding_scheme_host_and_keyword_it_should_create_correct_header_value() {
108 | var list = new CspSourceList();
109 | list.AddScheme("https");
110 | list.AddKeyword(SourceListKeyword.Self);
111 | list.AddHost("https://www.example.com/");
112 | list.ToDirectiveValue().Trim().ShouldEqual("https: https://www.example.com/ 'self'");
113 | }
114 |
115 | [Fact]
116 | public void When_adding_one_host_it_should_create_the_correct_header_value() {
117 | var list = new CspSourceList();
118 | list.AddHost("http://*.example.com:*/path/file.js");
119 | list.ToDirectiveValue().Trim().ShouldEqual("http://*.example.com:*/path/file.js");
120 | }
121 |
122 | [Fact]
123 | public void When_adding_a_valid_uri_as_host_it_should_not_throw_a_exception() {
124 | var list = new CspSourceList();
125 | list.AddHost(new Uri("https://www.example.com/abcd/"));
126 | }
127 |
128 | [Fact]
129 | public void When_adding_keyword_unsafeinline_it_should_create_the_correct_header_value() {
130 | var list = new CspSourceList();
131 | list.AddKeyword(SourceListKeyword.UnsafeInline);
132 | list.ToDirectiveValue().Trim().ShouldEqual("'unsafe-inline'");
133 | }
134 |
135 | [Fact]
136 | public void When_using_a_semicolon_in_a_value_it_should_be_escaped() {
137 | var list = new CspSourceList();
138 | list.AddHost("http://example.com/abcd;/asdas");
139 | list.ToDirectiveValue().Should().Be("http://example.com/abcd%3B/asdas");
140 | }
141 |
142 | [Fact]
143 | public void When_using_a_comma_in_a_value_it_should_be_escaped() {
144 | var list = new CspSourceList();
145 | list.AddHost("http://example.com/abcd,/asdas");
146 | list.ToDirectiveValue().Should().Be("http://example.com/abcd%2C/asdas");
147 | }
148 | }
149 | }
--------------------------------------------------------------------------------
/src/OwinContrib.SecurityHeaders/SecurityHeaders.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Threading.Tasks;
4 | using SecurityHeadersMiddleware.Infrastructure;
5 |
6 | namespace SecurityHeadersMiddleware {
7 | using BuildFunc = Action, Func, Task>, Func, Task>>>>;
8 |
9 | ///
10 | /// OWIN extension methods.
11 | ///
12 | public static class SecurityHeaders {
13 | #region Content Type Options
14 |
15 | ///
16 | /// Adds the "X-Content-Type-Options" header with value "nosniff" to the response.
17 | ///
18 | /// The OWIN builder instance.
19 | /// The OWIN builder instance.
20 | public static BuildFunc ContentTypeOptions(this BuildFunc builder) {
21 | builder(_ => ContenTypeOptionsHeaderMiddleware.ContentTypeOptionsHeader());
22 | return builder;
23 | }
24 |
25 | #endregion
26 |
27 | #region AntiClickjacking
28 |
29 | ///
30 | /// Adds the "X-Frame-Options" header with value DENY to the response.
31 | ///
32 | /// The OWIN builder instance.
33 | /// The OWIN builder instance.
34 | public static BuildFunc AntiClickjackingHeader(this BuildFunc builder) {
35 | return AntiClickjackingHeader(builder, XFrameOption.Deny);
36 | }
37 |
38 | ///
39 | /// Adds the "X-Frame-Options" header with the given option to the response.
40 | ///
41 | /// The OWIN builder instance.
42 | /// The X-Frame option.
43 | /// The OWIN builder instance.
44 | public static BuildFunc AntiClickjackingHeader(this BuildFunc builder, XFrameOption option) {
45 | builder(_ => AntiClickjackingMiddleware.AntiClickjackingHeader(option));
46 | return builder;
47 | }
48 |
49 | ///
50 | /// Adds the "X-Frame-Options" with DENY when the request uri is not provided to the response. Otherwise the request
51 | /// uri with ALLOW-FROM <request uri>.
52 | ///
53 | /// The OWIN builder instance.
54 | /// The allowed uris.
55 | /// The OWIN builder instance.
56 | public static BuildFunc AntiClickjackingHeader(this BuildFunc builder, params string[] origins) {
57 | origins.MustNotNull("origins");
58 | origins.MustHaveAtLeastOneValue("origins");
59 | builder(_ => AntiClickjackingMiddleware.AntiClickjackingHeader(origins));
60 | return builder;
61 | }
62 |
63 | ///
64 | /// Adds the "X-Frame-Options" with DENY when the request uri is not provided to the response. Otherwise the request
65 | /// uri with ALLOW-FROM <request uri>.
66 | ///
67 | /// The OWIN builder instance.
68 | /// The allowed uirs.
69 | /// The OWIN builder instance.
70 | public static BuildFunc AntiClickjackingHeader(this BuildFunc builder, params Uri[] origins) {
71 | origins.MustNotNull("origins");
72 | origins.MustHaveAtLeastOneValue("origins");
73 | builder(_ => AntiClickjackingMiddleware.AntiClickjackingHeader(origins));
74 | return builder;
75 | }
76 |
77 | #endregion
78 |
79 | #region XssProtection
80 |
81 | ///
82 | /// Adds the "X-Xss-Protection" header to the response.
83 | ///
84 | /// The OWIN builder instance.
85 | /// true to set the heade value to "0". false (Default) to set the header value to"1; mode=block".
86 | /// The OWIN builder instance.
87 | public static BuildFunc XssProtectionHeader(this BuildFunc builder, bool disabled = false) {
88 | return builder.XssProtectionHeader(disabled ? XssProtectionOption.Disabled : XssProtectionOption.EnabledWithModeBlock);
89 | }
90 |
91 | ///
92 | /// Adds the "X-Xss-Protection" header to the response.
93 | ///
94 | /// The OWIN builder instance.
95 | /// The Xss-Protection options.
96 | /// The OWIN builder instance.
97 | public static BuildFunc XssProtectionHeader(this BuildFunc builder, XssProtectionOption option) {
98 | builder(_ => XssProtectionHeaderMiddleware.XssProtectionHeader(option));
99 | return builder;
100 | }
101 |
102 | #endregion
103 |
104 | #region Strict Transport Security
105 |
106 | ///
107 | /// Adds the "Strict-Transport-Security" (STS) header to the response.
108 | ///
109 | /// The OWIN builder instance.
110 | /// The Strict-Transport-Security options.
111 | /// The OWIN builder instance.
112 | public static BuildFunc StrictTransportSecurity(this BuildFunc builder, StrictTransportSecurityOptions options = null) {
113 | options = options ?? new StrictTransportSecurityOptions();
114 | builder(_ => StrictTransportSecurityHeaderMiddleware.StrictTransportSecurityHeader(options));
115 | return builder;
116 | }
117 |
118 | #endregion
119 |
120 | #region Content Security Policy
121 |
122 | ///
123 | /// Adds the "Content-Security-Policy" (CSP) header with the given configuration to the response.
124 | ///
125 | /// The OWIN builder instance.
126 | /// The Content-Security-Policy configuration.
127 | /// The OWIN builder instance.
128 | public static BuildFunc ContentSecurityPolicy(this BuildFunc builder, ContentSecurityPolicyConfiguration configuration) {
129 | configuration.MustNotNull("configuration");
130 | builder(_ => ContentSecurityPolicyMiddleware.ContentSecurityPolicyHeader(configuration));
131 | return builder;
132 | }
133 |
134 | ///
135 | /// Adds the "Content-Security-Policy-Report-Only" (CSP) header with the given configuration to the response.
136 | ///
137 | /// The OWIN builder instance.
138 | /// The Content-Security-Policy configuration.
139 | /// The OWIN builder instance.
140 | public static BuildFunc ContentSecurityPolicyReportOnly(this BuildFunc builder, ContentSecurityPolicyConfiguration configuration) {
141 | configuration.MustNotNull("configuration");
142 | builder(_ => ContentSecurityPolicyReportOnlyMiddleware.ContentSecurityPolicyHeader(configuration));
143 | return builder;
144 | }
145 |
146 | #endregion
147 | }
148 | }
--------------------------------------------------------------------------------
/src/OwinContrib.SecurityHeaders/SecurityHeadersMiddleware.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Debug
6 | AnyCPU
7 | {53953109-6B63-4A49-BDA3-EB78E1506FA9}
8 | Library
9 | Properties
10 | SecurityHeadersMiddleware
11 | SecurityHeadersMiddleware
12 | v4.5
13 | 512
14 | 1
15 |
16 |
17 | true
18 | full
19 | false
20 | bin\Debug\
21 | DEBUG;TRACE
22 | prompt
23 | 4
24 | False
25 | False
26 | True
27 | False
28 | False
29 | False
30 | True
31 | True
32 | True
33 | True
34 | True
35 | True
36 | True
37 | False
38 | False
39 | False
40 | False
41 | False
42 | True
43 | True
44 | True
45 | False
46 | False
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 | True
55 | False
56 | True
57 | Full
58 | DoNotBuild
59 | 0
60 | bin\Debug\SecurityHeadersMiddleware.XML
61 |
62 |
63 | pdbonly
64 | true
65 | bin\Release\
66 | TRACE
67 | prompt
68 | 4
69 | bin\Release\SecurityHeadersMiddleware.XML
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 | Properties\SharedAssemblyInfo.cs
81 |
82 |
83 |
84 |
85 |
86 | CspSourceList.cs
87 |
88 |
89 | CspSourceList.cs
90 |
91 |
92 | CspSourceList.cs
93 |
94 |
95 | CspSourceList.cs
96 |
97 |
98 | CspSourceList.cs
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 |
131 |
132 |
133 |
140 |
--------------------------------------------------------------------------------
/src/SecurityHeadersMiddleware.OwinAppBuilder/AppBuilderExtensions.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Threading.Tasks;
4 | using Owin;
5 | using SecurityHeadersMiddleware.Infrastructure;
6 |
7 | namespace SecurityHeadersMiddleware.OwinAppBuilder {
8 | using BuildFunc = Action, Func, Task>, Func, Task>>>>;
9 |
10 | ///
11 | /// Provides extension methods.
12 | ///
13 | public static class AppBuilderExtensions {
14 | internal static BuildFunc UseOwin(this IAppBuilder builder) {
15 | return middleware => builder.Use(middleware(builder.Properties));
16 | }
17 |
18 | #region Content Type Options
19 |
20 | ///
21 | /// Adds the "X-Content-Type-Options" header with value "nosniff" to the response.
22 | ///
23 | /// The IAppBuilder instance.
24 | /// The IAppBuilder instance.
25 | public static IAppBuilder ContentTypeOptions(this IAppBuilder builder) {
26 | builder.MustNotNull("builder");
27 | builder.UseOwin().ContentTypeOptions();
28 | return builder;
29 | }
30 |
31 | #endregion
32 |
33 | #region AntiClickjacking
34 |
35 | ///
36 | /// Adds the "X-Frame-Options" header with value DENY to the response.
37 | ///
38 | /// The IAppBuilder instance.
39 | /// The IAppBuilder instance.
40 | public static IAppBuilder AntiClickjackingHeader(this IAppBuilder builder) {
41 | return AntiClickjackingHeader(builder, XFrameOption.Deny);
42 | }
43 |
44 | ///
45 | /// Adds the "X-Frame-Options" header with the given option to the response.
46 | ///
47 | /// The IAppBuilder instance.
48 | /// The X-Frame option.
49 | /// The IAppBuilder instance.
50 | public static IAppBuilder AntiClickjackingHeader(this IAppBuilder builder, XFrameOption option) {
51 | builder.MustNotNull("builder");
52 | builder.UseOwin().AntiClickjackingHeader(option);
53 | return builder;
54 | }
55 |
56 | ///
57 | /// Adds the "X-Frame-Options" with DENY when the request uri is not provided to the response. Otherwise the request
58 | /// uri with ALLOW-FROM <request uri>.
59 | ///
60 | /// The IAppBuilder instance.
61 | /// The allowed uris.
62 | /// The IAppBuilder instance.
63 | public static IAppBuilder AntiClickjackingHeader(this IAppBuilder builder, params string[] origins) {
64 | builder.MustNotNull("builder");
65 | origins.MustNotNull("origins");
66 | origins.MustHaveAtLeastOneValue("origins");
67 | builder.UseOwin().AntiClickjackingHeader(origins);
68 | return builder;
69 | }
70 |
71 | ///
72 | /// Adds the "X-Frame-Options" with DENY when the request uri is not provided to the response. Otherwise the request
73 | /// uri with ALLOW-FROM <request uri>.
74 | ///
75 | /// The IAppBuilder instance.
76 | /// The allowed uirs.
77 | /// The IAppBuilder instance.
78 | public static IAppBuilder AntiClickjackingHeader(this IAppBuilder builder, params Uri[] origins) {
79 | builder.MustNotNull("builder");
80 | origins.MustNotNull("origins");
81 | origins.MustHaveAtLeastOneValue("origins");
82 | builder.UseOwin().AntiClickjackingHeader(origins);
83 | return builder;
84 | }
85 |
86 | #endregion
87 |
88 | #region XssProtection
89 |
90 | ///
91 | /// Adds the "X-Xss-Protection" header with value "1; mode=block" to the response.
92 | ///
93 | /// The IAppBuilder instance.
94 | /// The IAppBuilder instance.
95 | public static IAppBuilder XssProtectionHeader(this IAppBuilder builder) {
96 | return XssProtectionHeader(builder, false);
97 | }
98 |
99 | ///
100 | /// Adds the "X-Xss-Protection" header depending on to the response.
101 | ///
102 | /// The IAppBuilder instance.
103 | /// true to set the heade value to "0". false to set the header value to"1; mode=block".
104 | /// The IAppBuilder instance.
105 | public static IAppBuilder XssProtectionHeader(this IAppBuilder builder, bool disabled) {
106 | builder.MustNotNull("builder");
107 | builder.UseOwin().XssProtectionHeader(disabled);
108 | return builder;
109 | }
110 |
111 | #endregion
112 |
113 | #region Strict Transport Security
114 |
115 | ///
116 | /// Adds the "Strict-Transport-Security" (STS) header to the response.
117 | ///
118 | /// The IAppBuilder instance.
119 | /// The IAppBuilder instance.
120 | public static IAppBuilder StrictTransportSecurity(this IAppBuilder builder) {
121 | return StrictTransportSecurity(builder, new StrictTransportSecurityOptions());
122 | }
123 |
124 | ///
125 | /// Adds the "Strict-Transport-Security" (STS) header with the given option to the response.
126 | ///
127 | /// The IAppBuilder instance.
128 | /// The Strict-Transport-Security options.
129 | /// The IAppBuilder instance.
130 | public static IAppBuilder StrictTransportSecurity(this IAppBuilder builder, StrictTransportSecurityOptions options) {
131 | builder.MustNotNull("builder");
132 | options.MustNotNull("options");
133 | builder.UseOwin().StrictTransportSecurity(options);
134 | return builder;
135 | }
136 |
137 | #endregion
138 |
139 | #region Content Security Policy
140 |
141 | ///
142 | /// Adds the "Content-Security-Policy" header with the given configuration to the response.
143 | ///
144 | /// The IAppBuilder instance.
145 | /// The Content-Security-Policy configuration.
146 | /// The IAppBuilder instance.
147 | public static IAppBuilder ContentSecurityPolicy(this IAppBuilder builder, ContentSecurityPolicyConfiguration configuration) {
148 | builder.MustNotNull("builder");
149 | builder.UseOwin().ContentSecurityPolicy(configuration);
150 | return builder;
151 | }
152 |
153 | ///
154 | /// Adds the "Content-Security-Policy-Report-Only" header with the given configuration to the response.
155 | ///
156 | /// The IAppBuilder instance.
157 | /// The Content-Security-Policy configuration.
158 | /// The IAppBuilder instance.
159 | public static IAppBuilder ContentSecurityPolicyReportOnly(this IAppBuilder builder, ContentSecurityPolicyConfiguration configuration) {
160 | builder.MustNotNull("builder");
161 | builder.UseOwin().ContentSecurityPolicyReportOnly(configuration);
162 | return builder;
163 | }
164 |
165 | #endregion
166 | }
167 | }
--------------------------------------------------------------------------------
/src/OwinContrib.SecurityHeaders/ContentSecurityPolicyConfiguration.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using SecurityHeadersMiddleware.Infrastructure;
5 |
6 | namespace SecurityHeadersMiddleware {
7 | ///
8 | /// Defines the content-security-policy header values.
9 | ///
10 | public class ContentSecurityPolicyConfiguration {
11 | ///
12 | /// Initializes a new instance of the class.
13 | ///
14 | public ContentSecurityPolicyConfiguration() {
15 | BaseUri = new CspSourceList();
16 | ChildSrc = new CspSourceList();
17 | ConnectSrc = new CspSourceList();
18 | DefaultSrc = new CspSourceList();
19 | FontSrc = new CspSourceList();
20 | FormAction = new CspSourceList();
21 | FrameAncestors = new CspAncestorSourceList();
22 | FrameSrc = new CspSourceList();
23 | ImgSrc = new CspSourceList();
24 | MediaSrc = new CspSourceList();
25 | ObjectSrc = new CspSourceList();
26 | ScriptSrc = new CspSourceList();
27 | StyleSrc = new CspSourceList();
28 | PluginTypes = new CspMediaTypeList();
29 | ReportUri = new CspUriReferenceList();
30 | Sandbox = new CspSandboxTokenList();
31 | }
32 |
33 | ///
34 | /// Gets the base-uri directive source-list.
35 | /// See http://www.w3.org/TR/CSP2/#directive-base-uri
36 | /// Info: Hash and Nonce not implemented yet.
37 | ///
38 | public CspSourceList BaseUri { get; private set; }
39 |
40 | ///
41 | /// Gets the child-src directive source-list.
42 | /// See http://www.w3.org/TR/CSP2/#directive-child-src
43 | /// Info: Hash and Nonce not implemented yet.
44 | ///
45 | public CspSourceList ChildSrc { get; private set; }
46 |
47 | ///
48 | /// Gets the connect-src directive source-list.
49 | /// See http://www.w3.org/TR/CSP2/#directive-connect-src
50 | /// Info: Hash and Nonce not implemented yet.
51 | ///
52 | public CspSourceList ConnectSrc { get; private set; }
53 |
54 | ///
55 | /// Gets the default-src directive source-list.
56 | /// See http://www.w3.org/TR/CSP2/#directive-default-src
57 | /// Info: Hash and Nonce not implemented yet.
58 | ///
59 | public CspSourceList DefaultSrc { get; private set; }
60 |
61 | ///
62 | /// Gets the font-src directive source-list.
63 | /// See http://www.w3.org/TR/CSP2/#directive-font-src
64 | /// Info: Hash and Nonce not implemented yet.
65 | ///
66 | public CspSourceList FontSrc { get; private set; }
67 |
68 | ///
69 | /// Gets the form-action directive source-list.
70 | /// See http://www.w3.org/TR/CSP2/#directive-form-action
71 | /// Info: Hash and Nonce not implemented yet.
72 | ///
73 | public CspSourceList FormAction { get; private set; }
74 |
75 | ///
76 | /// Gets the frame-ancestors directive source-list.
77 | /// See http://www.w3.org/TR/CSP2/#directive-frame-ancestors
78 | /// Info: According to the spec this directive replaces the X-Frame-Options header.
79 | ///
80 | public CspAncestorSourceList FrameAncestors { get; private set; }
81 |
82 | ///
83 | /// Gets the frame-src directive source-list.
84 | /// See http://www.w3.org/TR/CSP2/#directive-frame-src
85 | /// Info: Hash and Nonce not implemented yet.
86 | ///
87 | [Obsolete("\"The frame-src directive is deprecated. Authors who wish to govern nested browsing contexts SHOULD use the child-src directive instead.\" See http://www.w3.org/TR/CSP/#directive-frame-src")]
88 | public CspSourceList FrameSrc { get; private set; }
89 |
90 | ///
91 | /// Gets the img-src directive source-list.
92 | /// See http://www.w3.org/TR/CSP2/#directive-img-src
93 | /// Info: Hash and Nonce not implemented yet.
94 | ///
95 | public CspSourceList ImgSrc { get; private set; }
96 |
97 | ///
98 | /// Gets the media-src directive source-list.
99 | /// See http://www.w3.org/TR/CSP2/#directive-media-src
100 | /// Info: Hash and Nonce not implemented yet.
101 | ///
102 | public CspSourceList MediaSrc { get; private set; }
103 |
104 | ///
105 | /// Gets the object-src directive source-list.
106 | /// See http://www.w3.org/TR/CSP2/#directive-object-src
107 | /// Info: Hash and Nonce not implemented yet.
108 | ///
109 | public CspSourceList ObjectSrc { get; private set; }
110 |
111 | ///
112 | /// Gets the plugin-types directive source-list.
113 | /// See http://www.w3.org/TR/CSP2/#directive-plugin-types
114 | ///
115 | public CspMediaTypeList PluginTypes { get; private set; }
116 |
117 | ///
118 | /// Gets the report-uri directive source-list.
119 | /// See http://www.w3.org/TR/CSP2/#directive-report-uri
120 | ///
121 | public CspUriReferenceList ReportUri { get; private set; }
122 |
123 | ///
124 | /// Gets the sandbox directive source-list.
125 | /// See http://www.w3.org/TR/CSP2/#directive-sandbox
126 | ///
127 | public CspSandboxTokenList Sandbox { get; private set; }
128 |
129 | ///
130 | /// Gets the script-src directive source-list.
131 | /// See http://www.w3.org/TR/CSP2/#directive-script-src
132 | /// Info: Hash and Nonce not implemented yet.
133 | ///
134 | public CspSourceList ScriptSrc { get; private set; }
135 |
136 | ///
137 | /// Gets the style-src directive source-list.
138 | /// See http://www.w3.org/TR/CSP2/#directive-style-src
139 | /// Info: Hash and Nonce not implemented yet.
140 | ///
141 | public CspSourceList StyleSrc { get; set; }
142 |
143 | ///
144 | /// Creates the header values of all set directves.
145 | ///
146 | /// The content-security-policy header value.
147 | internal string ToHeaderValue() {
148 | var values = new List(16) {
149 | BuildDirectiveValue("base-uri", BaseUri),
150 | BuildDirectiveValue("child-src", ChildSrc),
151 | BuildDirectiveValue("connect-src", ConnectSrc),
152 | BuildDirectiveValue("default-src", DefaultSrc),
153 | BuildDirectiveValue("font-src", FontSrc),
154 | BuildDirectiveValue("form-action", FormAction),
155 | BuildDirectiveValue("frame-ancestors", FrameAncestors),
156 | BuildDirectiveValue("frame-src", FrameSrc),
157 | BuildDirectiveValue("img-src", ImgSrc),
158 | BuildDirectiveValue("media-src", MediaSrc),
159 | BuildDirectiveValue("object-src", ObjectSrc),
160 | BuildDirectiveValue("plugin-types", PluginTypes),
161 | BuildDirectiveValue("report-uri", ReportUri),
162 | BuildSandboxDirectiveValue(),
163 | BuildDirectiveValue("script-src", ScriptSrc),
164 | BuildDirectiveValue("style-src", StyleSrc)
165 | };
166 | return string.Join("; ", values.Where(s => !s.IsNullOrWhiteSpace()));
167 | }
168 |
169 | private string BuildSandboxDirectiveValue() {
170 | if (Sandbox.IsEmpty) {
171 | return "sandbox";
172 | }
173 | return BuildDirectiveValue("sandbox", Sandbox);
174 | }
175 |
176 | private static string BuildDirectiveValue(string directiveName, IDirectiveValueBuilder sourceList) {
177 | return BuildDirectiveValue(directiveName, sourceList.ToDirectiveValue());
178 | }
179 |
180 | private static string BuildDirectiveValue(string directiveName, string directiveValue) {
181 | if (directiveValue.IsNullOrWhiteSpace()) {
182 | return "";
183 | }
184 | return "{0} {1}".FormatWith(directiveName, directiveValue);
185 | }
186 | }
187 | }
--------------------------------------------------------------------------------
/src/OwinContrib.SecurityHeaders.Tests/SecurityHeadersMiddleware.Tests.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Debug
7 | AnyCPU
8 | {753D4EEA-13E5-429B-93AC-4A7E7A1FACD3}
9 | Library
10 | Properties
11 | SecurityHeadersMiddleware.Tests
12 | SecurityHeadersMiddleware.Tests
13 | v4.5
14 | 512
15 | 71b155df
16 |
17 |
18 | true
19 | full
20 | false
21 | bin\Debug\
22 | DEBUG;TRACE
23 | prompt
24 | 4
25 |
26 |
27 | pdbonly
28 | true
29 | bin\Release\
30 | TRACE
31 | prompt
32 | 4
33 |
34 |
35 |
36 | False
37 | ..\packages\FluentAssertions.3.3.0\lib\net45\FluentAssertions.dll
38 |
39 |
40 | False
41 | ..\packages\FluentAssertions.3.3.0\lib\net45\FluentAssertions.Core.dll
42 |
43 |
44 | ..\packages\Machine.Specifications.0.9.1\lib\net45\Machine.Specifications.dll
45 | True
46 |
47 |
48 | ..\packages\Machine.Specifications.0.9.1\lib\net45\Machine.Specifications.Clr4.dll
49 | True
50 |
51 |
52 | ..\packages\Machine.Specifications.Should.0.7.2\lib\net45\Machine.Specifications.Should.dll
53 |
54 |
55 | False
56 | ..\packages\Microsoft.Owin.3.0.1\lib\net45\Microsoft.Owin.dll
57 |
58 |
59 | False
60 | ..\packages\Microsoft.Owin.Hosting.3.0.1\lib\net45\Microsoft.Owin.Hosting.dll
61 |
62 |
63 | False
64 | ..\packages\Microsoft.Owin.Testing.3.0.1\lib\net45\Microsoft.Owin.Testing.dll
65 |
66 |
67 | ..\packages\Owin.1.0\lib\net40\Owin.dll
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 | False
79 | ..\packages\xunit.abstractions.2.0.0\lib\net35\xunit.abstractions.dll
80 |
81 |
82 | False
83 | ..\packages\xunit.assert.2.0.0\lib\portable-net45+win+wpa81+wp80+monotouch+monoandroid+Xamarin.iOS\xunit.assert.dll
84 |
85 |
86 | False
87 | ..\packages\xunit.extensibility.core.2.0.0\lib\portable-net45+win+wpa81+wp80+monotouch+monoandroid+Xamarin.iOS\xunit.core.dll
88 |
89 |
90 |
91 |
92 | Properties\SharedAssemblyInfo.cs
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 | {53953109-6B63-4A49-BDA3-EB78E1506FA9}
120 | SecurityHeadersMiddleware
121 |
122 |
123 | {d3381ffc-ffa0-478d-9708-199e2f9789f5}
124 | SecurityHeadersMiddleware.OwinAppBuilder
125 |
126 |
127 |
128 |
129 |
130 | Dieses Projekt verweist auf mindestens ein NuGet-Paket, das auf diesem Computer fehlt. Aktivieren Sie die Wiederherstellung von NuGet-Paketen, um die fehlende Datei herunterzuladen. Weitere Informationen finden Sie unter "http://go.microsoft.com/fwlink/?LinkID=322105". Die fehlende Datei ist "{0}".
131 |
132 |
133 |
134 |
141 |
--------------------------------------------------------------------------------
/src/OwinContrib.SecurityHeaders/HostSource.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Text.RegularExpressions;
4 | using SecurityHeadersMiddleware.Infrastructure;
5 |
6 |
7 | namespace SecurityHeadersMiddleware {
8 | internal class HostSource {
9 | private static readonly Dictionary DefaultPorts = new Dictionary {
10 | {"http://", 80}, {"https://", 443}, {"ftp://", 21}
11 | };
12 |
13 | private string mScheme = "";
14 | private string mHost = "";
15 | private string mPort = "";
16 | private string mPath = "";
17 |
18 | private string Value {
19 | get { return mScheme + mHost + mPort + mPath; }
20 | }
21 |
22 | public HostSource(string host) {
23 | if (host.IsNullOrWhiteSpace()) {
24 | throw new ArgumentException("The host parameter must be not empty, not null and not only whitespaces.");
25 | }
26 | ParseHost(host);
27 | }
28 |
29 | private void ParseHost(string host) {
30 | SplitIntoParts(host);
31 | VerifyParts();
32 | }
33 |
34 | private void SplitIntoParts(string hostsource) {
35 | ExtractScheme(ref hostsource);
36 | ExtractHost(ref hostsource);
37 | ExtractPort(ref hostsource);
38 | ExtractPath(ref hostsource);
39 | }
40 |
41 | private void ExtractScheme(ref string hostsource) {
42 | var index = hostsource.IndexOf("://");
43 | if (index == -1) {
44 | return;
45 | }
46 | mScheme = hostsource.Substring(0, index + 3);
47 | hostsource = hostsource.Substring(mScheme.Length);
48 | }
49 |
50 | private void ExtractHost(ref string hostsource) {
51 | var index = hostsource.IndexOf(":");
52 | if (index == -1) {
53 | index = hostsource.IndexOf("/");
54 | if (index == -1) {
55 | index = hostsource.Length;
56 | }
57 | }
58 |
59 | mHost = hostsource.Substring(0, index);
60 | hostsource = hostsource.Substring(mHost.Length);
61 | }
62 |
63 | private void ExtractPort(ref string hostsource) {
64 | if (hostsource.IsEmpty()) {
65 | return;
66 | }
67 | if (hostsource[0] != ':') {
68 | return;
69 | }
70 | var index = hostsource.IndexOf("/");
71 | if (index == -1) {
72 | index = hostsource.Length;
73 | }
74 | mPort = hostsource.Substring(0, index);
75 | hostsource = hostsource.Substring(mPort.Length);
76 | int defaultPort;
77 | if (DefaultPorts.TryGetValue(mScheme, out defaultPort)) {
78 | // Omit default port for easier comparison
79 | if (mPort == ":" + defaultPort) {
80 | mPort = "";
81 | }
82 | }
83 | }
84 |
85 | private void ExtractPath(ref string hostsource) {
86 | if (hostsource.IsEmpty()) {
87 | return;
88 | }
89 | var index = hostsource.IndexOf("?");
90 | if (index == -1) {
91 | index = hostsource.IndexOf("#");
92 | if (index == -1) {
93 | index = hostsource.Length;
94 | }
95 | }
96 | mPath = hostsource.Substring(0, index);
97 | hostsource = "";
98 | }
99 |
100 | private void VerifyParts() {
101 | VerifyHost();
102 | VerifyScheme();
103 | VerifyPort();
104 | VerifyPath();
105 | }
106 |
107 | private void VerifyScheme() {
108 | if (mScheme.IsEmpty()) {
109 | return;
110 | }
111 | const string schemeRegex = @"^[a-z][a-z0-9+\-.]*://$";
112 | if (RegexVerify(mScheme, schemeRegex)) {
113 | return;
114 | }
115 | const string msg = "The extracted scheme '{0}' does not satisfy the required format.{1}" +
116 | "Valid schemes:{1}ftp:// or a-12.adcd://{1}" +
117 | "First character must be a letter and has to end with ://{1}" +
118 | "For more informatin see: {2} (scheme-part).";
119 | throw new FormatException(msg.FormatWith(mScheme, Environment.NewLine, "http://www.w3.org/TR/CSP2/#host-source"));
120 | }
121 |
122 | private void VerifyHost() {
123 | const string hostRegex = @"^(\*(?!.)|(\*.)?[a-z0-9\-]+(?!\*)(\.[a-z0-9\-]+)*)$";
124 | if (RegexVerify(mHost, hostRegex)) {
125 | return;
126 | }
127 | const string msg = "The extracted host '{0}' does not satisfy the required format.{1}" +
128 | "Valid hosts:{1}* or *.example or example.-com{1}" +
129 | "For more information see: {2} (host-part).";
130 | throw new FormatException(msg.FormatWith(mHost, Environment.NewLine, "http://www.w3.org/TR/CSP2/#host-source"));
131 | }
132 |
133 | private void VerifyPort() {
134 | if (mPort.IsEmpty()) {
135 | return;
136 | }
137 | const string portRegex = @"^:([0-9]+|\*)$";
138 | if (RegexVerify(mPort, portRegex)) {
139 | return;
140 | }
141 | const string msg = "The extracted port '{0}' does not satisfy the required format.{1}" +
142 | "Valid ports:{1}:* or :1234{1}" +
143 | "First charater must be : (colon) followed by only a star or only digits.{1}" +
144 | "For more information see: {2} (port-part).";
145 | throw new FormatException(msg.FormatWith(mPort, Environment.NewLine, "http://www.w3.org/TR/CSP2/#host-source"));
146 | }
147 |
148 | private void VerifyPath() {
149 | if (mPath.IsEmpty()) {
150 | return;
151 | }
152 | const string pathRegex = @" ^
153 | # RFC-3986 URI component: path
154 | (?: # (
155 | (?:/ (?:[A-Za-z0-9\-._~!$&'()*+,;=:@]|%[0-9A-Fa-f]{2})* )* # path-abempty
156 | | / # / path-absolute
157 | (?: (?:[A-Za-z0-9\-._~!$&'()*+,;=:@]|%[0-9A-Fa-f]{2})+
158 | (?:/ (?:[A-Za-z0-9\-._~!$&'()*+,;=:@]|%[0-9A-Fa-f]{2})* )*
159 | )?
160 | | (?:[A-Za-z0-9\-._~!$&'()*+,;=@] |%[0-9A-Fa-f]{2})+ # / path-noscheme
161 | (?:/ (?:[A-Za-z0-9\-._~!$&'()*+,;=:@]|%[0-9A-Fa-f]{2})* )*
162 | | (?:[A-Za-z0-9\-._~!$&'()*+,;=:@]|%[0-9A-Fa-f]{2})+ # / path-rootless
163 | (?:/ (?:[A-Za-z0-9\-._~!$&'()*+,;=:@]|%[0-9A-Fa-f]{2})* )*
164 | | # / path-empty
165 | ) # )
166 | $ ";
167 | if (RegexVerify(mPath, pathRegex, RegexOptions.IgnorePatternWhitespace)) {
168 | return;
169 | }
170 | const string msg = "The extracted path '{0}' does not satisfy the required format.{1}" +
171 | "Valid paths:{1}/ or /path or /path/file.js" +
172 | "For more information see: {2} (path-part).";
173 | throw new FormatException(msg.FormatWith(mPath, Environment.NewLine, "http://www.w3.org/TR/CSP2/#host-source"));
174 | }
175 |
176 | private static bool RegexVerify(string input, string pattern, RegexOptions options = RegexOptions.None) {
177 | return Regex.IsMatch(input, pattern, RegexOptions.IgnoreCase | RegexOptions.ExplicitCapture | options);
178 | }
179 |
180 | public bool Equals(HostSource obj) {
181 | if (obj == null) {
182 | return false;
183 | }
184 |
185 | return Value.Equals(obj.Value, StringComparison.InvariantCultureIgnoreCase);
186 | }
187 |
188 | public override bool Equals(object obj) {
189 | return Equals(obj as HostSource);
190 | }
191 |
192 | public override int GetHashCode() {
193 | return Value.GetHashCode();
194 | }
195 |
196 | public override string ToString() {
197 | return Value;
198 | }
199 | }
200 | }
--------------------------------------------------------------------------------