├── 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 | [![Build Status](https://ci.appveyor.com/api/projects/status/6n9xkyyvox9uw2up)](https://ci.appveyor.com/project/StefanOssendorf/securityheadersmiddleware) [![NuGet Status](http://img.shields.io/nuget/v/SecurityHeadersMiddleware.svg?style=flat)](https://www.nuget.org/packages/SecurityHeadersMiddleware/) [![NuGet Status](https://img.shields.io/nuget/v/SecurityHeadersMiddleware.AppBuilder.svg?style=flat)](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 | } --------------------------------------------------------------------------------