32 Commits

Author SHA1 Message Date
Tommy Parnell
1709e44e55 Merge pull request #11 from TerribleDev/2.0
upgrade to 2.0
2018-11-04 09:36:48 -05:00
Tommy Parnell
74f99573a5 upgrade to 2.0 2018-11-04 09:34:31 -05:00
Tommy Parnell
6019640b13 Merge pull request #10 from TerribleDev/expectCt
add expect ct
2017-10-27 23:44:10 -04:00
Tommy Parnell
72c55bd87b src link pkg 2017-10-27 19:10:16 -04:00
Tommy Parnell
e940621548 include source link 2017-10-27 19:06:55 -04:00
Tommy Parnell
aa72521275 Update Readme.md 2017-06-21 13:03:24 -04:00
Tommy Parnell
12414ae258 Update Readme.md 2017-06-21 08:51:58 -04:00
Tommy Parnell
4f4c5d3750 add expect ct 2017-06-17 17:57:13 -04:00
Tommy Parnell
27790bfd7d remove pre 2017-06-17 17:29:07 -04:00
Tommy Parnell
b7a6de3c17 add build badge 2017-06-17 17:28:05 -04:00
Tommy Parnell
bbcb8550a4 Merge pull request #9 from TerribleDev/docs
add docs
2017-06-17 17:22:39 -04:00
Tommy Parnell
3978cfc694 add docs 2017-06-17 17:20:36 -04:00
Tommy Parnell
61ca5caf47 Merge pull request #8 from TerribleDev/hpkp
add hpkp
2017-06-17 16:22:57 -04:00
Tommy Parnell
abb4dc3e82 add hpkp 2017-06-17 16:18:21 -04:00
Tommy Parnell
8d2eb9855c fix readme 2017-06-09 22:46:36 -04:00
Tommy Parnell
a2be4d6d48 Merge branch 'csp' 2017-06-09 22:45:40 -04:00
Tommy Parnell
2a5180a1d0 fix package 2017-06-09 22:45:28 -04:00
Tommy Parnell
7fde1e4ef8 Merge pull request #5 from TerribleDev/csp
add content security policy
2017-06-09 22:01:17 -04:00
Tommy Parnell
ed3ccae75e add content security policy 2017-06-09 21:54:26 -04:00
Tommy Parnell
6b0b56c337 Merge pull request #1 from TerribleDev/updates
Updates
2017-06-04 18:48:27 -04:00
Tommy Parnell
b3ee115d9a fix things up 2017-06-04 18:41:01 -04:00
Tommy Parnell
a6bbb38649 i hate yml 2017-06-04 18:33:22 -04:00
Tommy Parnell
ef94967fda fix yml 2017-06-04 18:32:13 -04:00
Tommy Parnell
b0a5d6e2eb add updates 2017-06-04 18:31:47 -04:00
Tommy Parnell
e944aef177 unit tests, move things around 2017-01-01 14:37:40 -05:00
Tommy Parnell
d564cebf00 icon url fix 2016-12-31 22:24:24 -05:00
Tommy Parnell
6cf86c2a7e add arg null exceptions, fix package 2016-12-31 22:20:16 -05:00
Tommy Parnell
8c8595c47a args null 2016-12-31 22:10:31 -05:00
Tommy Parnell
8e41274f05 readme 2016-12-31 22:00:35 -05:00
Tommy Parnell
0471a80446 license file 2016-12-31 21:58:43 -05:00
Tommy Parnell
48c786c05a mime link 2016-12-31 21:56:57 -05:00
Tommy Parnell
8b74b176d8 typo in type name 2016-12-31 21:53:09 -05:00
52 changed files with 1633 additions and 186 deletions

View File

@@ -1,19 +1,20 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 14
VisualStudioVersion = 14.0.25420.1
# Visual Studio 15
VisualStudioVersion = 15.0.26430.6
MinimumVisualStudioVersion = 10.0.40219.1
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{2213F1A9-1E90-473E-A04F-F089BBC16098}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{597DE320-32E2-4B4E-80E5-33838AA160E5}"
ProjectSection(SolutionItems) = preProject
global.json = global.json
Readme.md = Readme.md
EndProjectSection
EndProject
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "HardHat", "src\HardHat\HardHat.xproj", "{B4448494-6C80-4BDA-890C-D8900B33FE17}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "HardHat", "src\HardHat\HardHat.csproj", "{B4448494-6C80-4BDA-890C-D8900B33FE17}"
EndProject
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "HardHat.Example", "src\HardHat.Example\HardHat.Example.xproj", "{1DEE94FD-A3D3-4E46-868D-1BB9028FEC86}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "HardHat.Example", "src\HardHat.Example\HardHat.Example.csproj", "{1DEE94FD-A3D3-4E46-868D-1BB9028FEC86}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "HardHat.UnitTests", "src\HardHat.UnitTests\HardHat.UnitTests.csproj", "{5FCE3103-91ED-4708-921B-2E881D67AD53}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
@@ -29,6 +30,10 @@ Global
{1DEE94FD-A3D3-4E46-868D-1BB9028FEC86}.Debug|Any CPU.Build.0 = Debug|Any CPU
{1DEE94FD-A3D3-4E46-868D-1BB9028FEC86}.Release|Any CPU.ActiveCfg = Release|Any CPU
{1DEE94FD-A3D3-4E46-868D-1BB9028FEC86}.Release|Any CPU.Build.0 = Release|Any CPU
{5FCE3103-91ED-4708-921B-2E881D67AD53}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{5FCE3103-91ED-4708-921B-2E881D67AD53}.Debug|Any CPU.Build.0 = Debug|Any CPU
{5FCE3103-91ED-4708-921B-2E881D67AD53}.Release|Any CPU.ActiveCfg = Release|Any CPU
{5FCE3103-91ED-4708-921B-2E881D67AD53}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -36,5 +41,6 @@ Global
GlobalSection(NestedProjects) = preSolution
{B4448494-6C80-4BDA-890C-D8900B33FE17} = {2213F1A9-1E90-473E-A04F-F089BBC16098}
{1DEE94FD-A3D3-4E46-868D-1BB9028FEC86} = {2213F1A9-1E90-473E-A04F-F089BBC16098}
{5FCE3103-91ED-4708-921B-2E881D67AD53} = {2213F1A9-1E90-473E-A04F-F089BBC16098}
EndGlobalSection
EndGlobal

7
LICENSE.md Normal file
View File

@@ -0,0 +1,7 @@
Copyright 2016 Tommy Parnell
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View File

@@ -1,8 +1,10 @@
# Hard Hat
[![Build status](https://ci.appveyor.com/api/projects/status/orm7sjpwxde03pbj/branch/master?svg=true)](https://ci.appveyor.com/project/tparnell8/hardhat/branch/master)
<img src="Hat.png" width="350px"/>
HardHat is a set of .net core middleware that adds various headers to help protect your site from vulnerablities. Inspired by [helmetJS](https://helmetjs.github.io). Currently in beta, Content Security Policy, Unit tests, documentation due before 1.0.0. Netherless this should work fine.
HardHat is a set of .net core middleware that adds various headers to help protect your site from vulnerabilities. Inspired by [helmetJS](https://helmetjs.github.io). We have [some docs](docs/Readme.md) they are still a work in progress, so please feel free to submit changes to them.
In short this allows:
@@ -20,8 +22,20 @@ In short this allows:
app.UseHsts(maxAge: 5000, includeSubDomains: true, preload: false); //tell browsers to always use https for the next 5000 seconds
app.UseReferrerPolicy(ReferrerPolicy.NoReferrer); // do not include the referrer header when linking away from your site to protect your users privacy
app.UseIENoOpen(); // don't allow old ie to open files in the context of your site
app.UseNoMimeSniff(); // prevent MIME sniffing
app.UseNoMimeSniff(); // prevent MIME sniffing https://en.wikipedia.org/wiki/Content_sniffing
app.UseCrossSiteScriptingFilters(); //add headers to have the browsers auto detect and block some xss attacks
app.UseContentSecurityPolicy( // Provide a security policy so only content can come from trusted sources
new ContentSecurityPolicyBuilder()
.WithDefaultSource(CSPConstants.Self)
.WithImageSource("http://images.mysite.com")
.WithFontSource(CSPConstants.Self)
.WithFrameAncestors(CSPConstants.None)
.BuildPolicy()
);
app.UseHpkp(maxAge: 5184000, keys: new List<PublicKeyPin>{ // Prevent man in the middle attacks by providing a hash of your public keys
new PublicKeyPin("cUPcTAZWKaASuYWhhneDttWpY3oBAkE3h2+soZS7sWs=", HpKpCrypto.sha256),
new PublicKeyPin("M8HztCzM3elUxkcjR2S5P4hhyBNf6lHkmjAHKhpGPWE=", HpKpCrypto.sha256)
}, includeSubDomains: true, reportUri: "/report", reportOnly: false);
...
app.UseMvc(routes =>
{
@@ -36,3 +50,8 @@ In short this allows:
```
## Getting started
* Install the nuget package `Install-Package HardHat`
* Add the middleware you desire to your configure block.

25
appveyor.yml Normal file
View File

@@ -0,0 +1,25 @@
image: Visual Studio 2017
version: 1.0.{build}
build_script:
- ps: >-
dotnet restore HardHat.sln --source https://api.nuget.org/v3/index.json
dotnet build HardHat.sln
if($env:APPVEYOR_REPO_TAG -eq "true")
{
dotnet pack src\HardHat\HardHat.csproj --configuration Release --output ..\..\output /p:Version=$env:APPVEYOR_REPO_TAG_NAME
}
else
{
dotnet pack src\HardHat\HardHat.csproj --configuration Release --output ..\..\output /p:Version=0.0.1-build-$env:APPVEYOR_BUILD_NUMBER
}
test_script:
- ps: dotnet test src\HardHat.UnitTests\HardHat.UnitTests.csproj
artifacts:
- path: output\**.nupkg
deploy:
- provider: NuGet
api_key:
secure: //tKHlb2yqAtpxnR6p9IAtXwQNaq8UYYyIFSD0QVF3XnEasIxG2gTWdmWuG87fUX
on:
appveyor_repo_tag: true

View File

@@ -0,0 +1,31 @@
sets the `Content-Security-Policy` header which can help protect against malicious injection of JavaScript, CSS, plugins, and more.
## Attack
When hackers can place content onto your site, they can do bad things! For example, javascript executing can give them someones credit card data. Or they could place a 1x1 transparent gif on your site to collect data.
## The Header
The `Content-Security-Policy` header tells browsers which domains content can come from. This is essentially a white list of domains where content can be loaded. For example, images could only come from your images subdomain.
## Code
Here we are saying images can come from any subdomain of my site. Fonts can come from the current domain.
```csharp
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
app.UseContentSecurityPolicy(
new ContentSecurityPolicyBuilder()
.WithDefaultSource(CSPConstants.Self)
.WithImageSource("http://*.mysite.com")
.WithFontSource(CSPConstants.Self)
.WithFrameAncestors(CSPConstants.None)
.BuildPolicy()
);
}
```

View File

@@ -0,0 +1,21 @@
The Cross Site Scripting filter sets the `X-XSS-Protection` to prevent reflected XSS attacks
## Attack
If someone can run JavaScript on your page, they can attack your users and do a lot of bad things. Sometimes people can inject script tags through query strings, and thus attack your users
## The Header
This middleware simply allows the browsers to detect and combat reflective XSS attacks. This will not save you against all attacks, but its a good start. Note in older versions of IE, this causes more security issues so we turn it off.
## Code
```csharp
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
app.UseCrossSiteScriptingFilters();
//app.UseCrossSiteScriptingFilters(addOldIE: true); if you want older versions of IE to get the header
}
```

21
docs/DnsPrefetch.md Normal file
View File

@@ -0,0 +1,21 @@
DNS Prefetch sets the `X-DNS-Prefetch-Control`
## Attack
When you visit a URL, the browser prefetches dns for a given link. This is a performance improvement, but can expose your users privacy by having them visit sites, they have never visited.
## The Header
The `X-DNS-Prefetch-Control` header tells browsers whether they should do DNS prefetching.
## Code
```csharp
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
app.UseDnsPrefetch(allow: false);
}
```

23
docs/Frameguard.md Normal file
View File

@@ -0,0 +1,23 @@
Frameguard mitigates clickjacking attacks by setting the `X-Frame-Options` header.
## Attack
Attackers get you to click on something you do not wish to click on. Often they do this by iframing other website, buttons but with a different context.
For example lets say your button says you are only going to vote democrat, hackers may make a website that says you will only vote republican and iFrame your button in their website.
## The Header
The `X-Frame-Options` header tells browsers to prevent your webpage from being put in an iframe. This can also control which domains are allowed to load iframes from your site.
## Code
```csharp
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
app.UseFrameGuard(new FrameGuardOptions("http://amazon.com"));
}
```

30
docs/Hpkp.md Normal file
View File

@@ -0,0 +1,30 @@
the HTTP Public Key Pinning module helps you set the `Public-Key-Pins` header to prevent person-in-the-middle attacks.
## Attack
If hackers can intercept secure requests to your website, they can gain credit card information, or passwords of your customers.
## The Header
The `Public-Key-Pins` header gives the browsers a hash of your public keys. This verifies to the browser if they are actually talking to your website
## Code
You can set the max age of the cache in seconds. You provide base64 encoded keys, and
```csharp
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
app.UseHpkp(maxAge: 5184000,
keys: new List<PublicKeyPin>{
new PublicKeyPin("cUPcTAZWKaASuYWhhneDttWpY3oBAkE3h2+soZS7sWs=", HpKpCrypto.sha256),
new PublicKeyPin("M8HztCzM3elUxkcjR2S5P4hhyBNf6lHkmjAHKhpGPWE=", HpKpCrypto.sha256)
},
includeSubDomains: true,
reportUri: "/report",
reportOnly: false);
}
```

25
docs/Hsts.md Normal file
View File

@@ -0,0 +1,25 @@
Set the `Strict-Transport-Security` header which tells the browser to use https
## Attack
Most websites only want to server over https, but often a http request is made and a user is redirected to https. This middleware caches the knowledge to use https, so future http requests cannot be man in the middle attacked
## The Header
the `Strict-Transport-Security` header controls the browsers behavior to default to https
## Code
Apart from setting a max age, you can include your subdomains. Ontop of all, you can include a preload header, which is required if you submit your [site to google](https://hstspreload.org/). Submitting your site to google will mean that the hsts header will be cached in the browsers ahead of time.
```csharp
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
app.UseHsts(maxAge: 5000, includeSubDomains: true, preload: false);
}
```

21
docs/IENoOpen.md Normal file
View File

@@ -0,0 +1,21 @@
Prevent IE from opening websites in the context of your site
## Attack
In old versions of IE, IE would open html files in the context of your site. Lets say you uploaded a html file as your image for your profile picture in a social media site. Old versions of IE would actually render the html out!
## The Header
The `X-Download-Options` header can be set to noopen.
## Code
```csharp
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
app.UseIENoOpen();
}
```

21
docs/MimeSniff.md Normal file
View File

@@ -0,0 +1,21 @@
the Dont Sniff Mimetype middleware, noSniff, helps prevent browsers from trying to guess ("sniff") the MIME type.
## Attack
Some browsers will detect what the mime type of a file is, even if the webserver says something else. Lets say someone uploads a script file to your website as their profile. Even though the webserver could say the mime type is one thing, the browser could interpret it as javascript and execute it!
## The Header
The `X-Content-Type-Options` header can be set to `nosniff` to prevent mime sniffing.
## Code
```csharp
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
app.UseNoMimeSniff();
}
```

16
docs/Readme.md Normal file
View File

@@ -0,0 +1,16 @@
## Index
Hard hat is essentially a group of individual middleware that help you improve the security of your aspnet core based applications.
Each middleware has a seperate readme file. These docs were inspired by helmetjs.
* [DnsPrefetch](DnsPrefetch.md) controls browser DNS prefetching
* [Cross Site Scripting](CrossSiteScripting.md) prevent reflective xss attacks
* [Content Security Policy](ContentSecurityPolicy.md)
* [FrameGuard](Frameguard.md) prevent clickjacking
* [HpKp](Hpkp.md) for http public key pinning
* [Hsts](Hsts.md) for telling the browsers to always use https
* [IENoOpen](IENoOpen.md) Prevents IE from opening websites in the context of your browsers
* [MimeSniff](MimeSniff.md) Prevents the browsers from sniffing the mime type of a file
* [ReferrerPolicy](ReferrerPolicy.md) Remove the referral header on link clicks

21
docs/ReferrerPolicy.md Normal file
View File

@@ -0,0 +1,21 @@
the Referrer Policy middleware can control the behavior of the `Referer header` by setting the `Referrer-Policy` header.
## Attack
The referrer header is usually set by the browsers to tell websites where users are coming from. This causes privacy issues for your users by telling other sites where they are coming from.
## The Header
The `Referrer-Header` header can be set to `no-referrer` to prevent such behaviors. The header can also be set to `same-origin` to track users between your own site
## Code
```csharp
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
app.UseReferrerPolicy(ReferrerPolicy.NoReferrer);
}
```

View File

@@ -1,6 +0,0 @@
{
"projects": [ "src", "test" ],
"sdk": {
"version": "1.0.0-preview2-003131"
}
}

View File

@@ -0,0 +1,34 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>netcoreapp2.1</TargetFramework>
<PreserveCompilationContext>true</PreserveCompilationContext>
<AssemblyName>HardHat.Example</AssemblyName>
<OutputType>Exe</OutputType>
<PackageId>HardHat.Example</PackageId>
</PropertyGroup>
<ItemGroup>
<None Update="wwwroot\**\*">
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
</None>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\HardHat\HardHat.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.App" />
</ItemGroup>
<Target Name="PrepublishScript" BeforeTargets="PrepareForPublish">
<Exec Command="bower install" />
<Exec Command="dotnet bundle" />
</Target>
<ItemGroup>
<DotNetCliToolReference Include="BundlerMinifier.Core" Version="2.2.301" />
</ItemGroup>
</Project>

View File

@@ -1,23 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">14.0</VisualStudioVersion>
<VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>
</PropertyGroup>
<Import Project="$(VSToolsPath)\DotNet\Microsoft.DotNet.Props" Condition="'$(VSToolsPath)' != ''" />
<PropertyGroup Label="Globals">
<ProjectGuid>1dee94fd-a3d3-4e46-868d-1bb9028fec86</ProjectGuid>
<RootNamespace>HardHat.Example</RootNamespace>
<BaseIntermediateOutputPath Condition="'$(BaseIntermediateOutputPath)'=='' ">.\obj</BaseIntermediateOutputPath>
<OutputPath Condition="'$(OutputPath)'=='' ">.\bin\</OutputPath>
<TargetFrameworkVersion>v4.6.2</TargetFrameworkVersion>
</PropertyGroup>
<PropertyGroup>
<SchemaVersion>2.0</SchemaVersion>
</PropertyGroup>
<ItemGroup>
<DnxInvisibleContent Include="bower.json" />
<DnxInvisibleContent Include=".bowerrc" />
</ItemGroup>
<Import Project="$(VSToolsPath)\DotNet.Web\Microsoft.DotNet.Web.targets" Condition="'$(VSToolsPath)' != ''" />
</Project>

View File

@@ -3,6 +3,7 @@ using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using System.Collections.Generic;
namespace HardHat.Example
{
@@ -36,21 +37,35 @@ namespace HardHat.Example
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseBrowserLink();
}
else
{
app.UseExceptionHandler("/Home/Error");
}
app.UseDnsPrefetch(allow: false);
app.UseFrameGuard(new FrameGuardOptions(FrameGuardOptions.FrameGuard.SAMEORIGIN));
app.UseFrameGuard(new FrameGuardOptions("http://amazon.com"));
app.UseHsts(maxAge: 5000, includeSubDomains: true, preload: false);
app.UseReferrerPolicy(ReferrerPolicy.NoReferrer);
app.UseIENoOpen();
app.UseNoMimeSniff();
app.UseCrossSiteScriptingFilters();
app.UseContentSecurityPolicy(
new ContentSecurityPolicyBuilder()
.WithDefaultSource(CSPConstants.Self)
.WithImageSource("http://images.mysite.com")
.WithFontSource(CSPConstants.Self)
.WithFrameAncestors(CSPConstants.None)
.WithMediaSource(CSPConstants.Schemes.MediaStream)
.BuildPolicy()
);
// use public key pinning
app.UseHpkp(maxAge: 5184000, keys: new List<PublicKeyPin>{
new PublicKeyPin("cUPcTAZWKaASuYWhhneDttWpY3oBAkE3h2+soZS7sWs=", HpKpCrypto.sha256),
new PublicKeyPin("M8HztCzM3elUxkcjR2S5P4hhyBNf6lHkmjAHKhpGPWE=", HpKpCrypto.sha256)
}, includeSubDomains: true, reportUri: "/report", reportOnly: false);
app.UseStaticFiles();
app.UseMvc(routes =>
{
routes.MapRoute(

View File

@@ -1,66 +0,0 @@
{
"dependencies": {
"Microsoft.NETCore.App": {
"version": "1.0.1",
"type": "platform"
},
"Microsoft.AspNetCore.Diagnostics": "1.0.0",
"Microsoft.AspNetCore.Mvc": "1.0.1",
"Microsoft.AspNetCore.Razor.Tools": {
"version": "1.0.0-preview2-final",
"type": "build"
},
"Microsoft.AspNetCore.Routing": "1.0.1",
"Microsoft.AspNetCore.Server.IISIntegration": "1.0.0",
"Microsoft.AspNetCore.Server.Kestrel": "1.0.1",
"Microsoft.AspNetCore.StaticFiles": "1.0.0",
"Microsoft.Extensions.Configuration.EnvironmentVariables": "1.0.0",
"Microsoft.Extensions.Configuration.Json": "1.0.0",
"Microsoft.Extensions.Logging": "1.0.0",
"Microsoft.Extensions.Logging.Console": "1.0.0",
"Microsoft.Extensions.Logging.Debug": "1.0.0",
"Microsoft.Extensions.Options.ConfigurationExtensions": "1.0.0",
"Microsoft.VisualStudio.Web.BrowserLink.Loader": "14.0.0",
"HardHat": "1.0.0"
},
"tools": {
"BundlerMinifier.Core": "2.0.238",
"Microsoft.AspNetCore.Razor.Tools": "1.0.0-preview2-final",
"Microsoft.AspNetCore.Server.IISIntegration.Tools": "1.0.0-preview2-final"
},
"frameworks": {
"netcoreapp1.0": {
"imports": [
"dotnet5.6",
"portable-net45+win8"
]
}
},
"buildOptions": {
"emitEntryPoint": true,
"preserveCompilationContext": true
},
"runtimeOptions": {
"configProperties": {
"System.GC.Server": true
}
},
"publishOptions": {
"include": [
"wwwroot",
"**/*.cshtml",
"appsettings.json",
"web.config"
]
},
"scripts": {
"prepublish": [ "bower install", "dotnet bundle" ],
"postpublish": [ "dotnet publish-iis --publish-folder %publish:OutputPath% --framework %publish:FullTargetFramework%" ]
}
}

View File

@@ -0,0 +1,20 @@
using HardHat.Middlewares;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Xunit;
namespace HardHat.UnitTests
{
public class ArgumentNulls
{
[Fact]
public void ArgumentNullsTests()
{
Assert.Throws<ArgumentNullException>(() => new FrameGuard(null, null));
Assert.Throws<ArgumentNullException>(() => new ReferrerPolicyMiddleware(null, null));
Assert.Throws<ArgumentNullException>(() => new FrameGuardOptions(string.Empty));
Assert.Throws<ArgumentNullException>(() => new ContentSecurityPolicyMiddleware(null, null));
}
}
}

View File

@@ -0,0 +1,52 @@
using HardHat.Builders;
using System;
using System.Collections.Generic;
using System.Text;
using Xunit;
namespace HardHat.UnitTests
{
public class CSPBuilderTests
{
[Fact]
public void DefaultBuilderTests()
{
//"default-src 'self' http://*.example.com; "
var builder = ContentSecurityHeaderBuilder.Build(new ContentSecurityPolicy() {
DefaultSrc = new HashSet<string>() { CSPConstants.Self, CSPConstants.None, "http://*.example.com" },
ScriptSrc = new HashSet<string>() { "http://*.example.com" },
StyleSrc = new HashSet<string>() { "http://*.example.com" },
ImgSrc = new HashSet<string>() { "http://*.example.com" },
ConnectSrc = new HashSet<string>() { "http://*.example.com" },
FontSrc = new HashSet<string>() { "http://*.example.com" },
ObjectSrc = new HashSet<string>() { "http://*.example.com" },
MediaSrc = new HashSet<string>() { "http://*.example.com" },
ChildSrc = new HashSet<string>() { "http://*.example.com" },
FormAction = new HashSet<string>() { "http://*.example.com" },
FrameAncestors = new HashSet<string>() { "http://*.example.com" },
PluginTypes = new HashSet<string>() { "http://*.example.com" },
Sandbox = SandboxOption.AllowPointerLock
});
Assert.Equal<string>(@"default-src 'self' 'none' http://*.example.com; script-src http://*.example.com; style-src http://*.example.com; img-src http://*.example.com; connect-src http://*.example.com; font-src http://*.example.com; object-src http://*.example.com; media-src http://*.example.com; child-src http://*.example.com; form-action http://*.example.com; frame-ancestors http://*.example.com; sandbox allow-pointer-lock; plugin-types http://*.example.com;", builder);
}
[Fact]
public void IsReportHeaderSetProperly()
{
var result = ContentSecurityHeaderBuilder.Build(new ContentSecurityPolicy()
{
ReportUri = "/yo",
OnlySendReport = false
});
Assert.Equal("report-uri /yo;", result);
var result2 = ContentSecurityHeaderBuilder.Build(new ContentSecurityPolicy()
{
ReportUri = "/yo",
OnlySendReport = true
});
Assert.Equal("report-uri-Report-Only /yo;", result2);
}
}
}

View File

@@ -0,0 +1,33 @@
using HardHat.Builders;
using System;
using System.Collections.Generic;
using System.Text;
using Xunit;
namespace HardHat.UnitTests
{
public class ExpectCtHeaderTests
{
[Fact]
public void TestExceptions()
{
Assert.Throws<ArgumentNullException>(() => ExpectCtHeaderBuilder.Build(0, string.Empty));
}
[Fact]
public void TestHeader()
{
var result = ExpectCtHeaderBuilder.Build(0, "/awesome");
Assert.Equal("max-age=0; report-uri=\"/awesome\"", result);
}
[Fact]
public void TestHeaderWithEnforce()
{
var result = ExpectCtHeaderBuilder.Build(0, "/awesome", true);
Assert.Equal("max-age=0; report-uri=\"/awesome\"; enforce", result);
}
}
}

View File

@@ -0,0 +1,18 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netcoreapp2.1</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Http.Abstractions" Version="2.1.1" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.0.0" />
<PackageReference Include="Moq" Version="4.7.25" />
<PackageReference Include="xunit" Version="2.2.0" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.2.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\HardHat\HardHat.csproj" />
</ItemGroup>
<ItemGroup>
<Service Include="{82a7f48d-3b50-4b1e-b82e-3ada8210c358}" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,91 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Moq;
using Microsoft.AspNetCore.Http;
using Xunit;
using HardHat.Middlewares;
namespace HardHat.UnitTests
{
public class HeaderExistance
{
Mock<HttpContext> httpContextMock = new Mock<HttpContext>();
Mock<HttpResponse> httpResponse = new Mock<HttpResponse>();
Mock<IHeaderDictionary> httpDictionary = new Mock<IHeaderDictionary>();
public HeaderExistance()
{
httpContextMock.SetupGet(a => a.Response).Returns(httpResponse.Object);
httpResponse.SetupGet(a => a.Headers).Returns(httpDictionary.Object);
}
[Fact]
public void NoIe()
{
httpDictionary.SetupSet(a => a[Constants.DowloadOptions] = Constants.NoOpen).Verifiable();
new IENoOpen(null).Invoke(httpContextMock.Object);
httpDictionary.Verify();
}
[Fact]
public void DnsPrefetch()
{
httpDictionary.SetupSet(a => a[Constants.DnsControlHeader] = "on").Verifiable();
new DnsPrefetch(null, true).Invoke(httpContextMock.Object);
httpDictionary.Verify();
httpDictionary.SetupSet(a => a[Constants.DnsControlHeader] = "off").Verifiable();
new DnsPrefetch(null, false).Invoke(httpContextMock.Object);
httpDictionary.Verify();
}
[Fact]
public void FrameGuard()
{
httpDictionary.SetupSet(a => a[Constants.FrameGuardHeader] = "awesome.com").Verifiable();
new FrameGuard(null, new FrameGuardOptions("awesome.com")).Invoke(httpContextMock.Object);
httpDictionary.Verify();
httpDictionary.SetupSet(a => a[Constants.FrameGuardHeader] = "SAMEORIGIN").Verifiable();
new FrameGuard(null, new FrameGuardOptions(FrameGuardOptions.FrameGuard.SAMEORIGIN)).Invoke(httpContextMock.Object);
httpDictionary.Verify();
httpDictionary.SetupSet(a => a[Constants.FrameGuardHeader] = "DENY").Verifiable();
new FrameGuard(null, new FrameGuardOptions(FrameGuardOptions.FrameGuard.DENY)).Invoke(httpContextMock.Object);
httpDictionary.Verify();
}
[Fact]
public void Hsts()
{
httpDictionary.SetupSet(a => a[Constants.StrictTransportSecurity] = "max-age=5000").Verifiable();
new Hsts(null, 5000).Invoke(httpContextMock.Object);
httpDictionary.Verify();
httpDictionary.SetupSet(a => a[Constants.StrictTransportSecurity] = "max-age=5000; includeSubDomains").Verifiable();
new Hsts(null, 5000, true).Invoke(httpContextMock.Object);
httpDictionary.Verify();
httpDictionary.SetupSet(a => a[Constants.StrictTransportSecurity] = "max-age=5000; preload").Verifiable();
new Hsts(null, 5000, false, true).Invoke(httpContextMock.Object);
httpDictionary.Verify();
httpDictionary.SetupSet(a => a[Constants.StrictTransportSecurity] = "max-age=5000; includeSubDomains; preload").Verifiable();
new Hsts(null, 5000, true, true).Invoke(httpContextMock.Object);
httpDictionary.Verify();
}
[Fact]
public void NoSniff()
{
httpDictionary.SetupSet(a => a[Constants.XContentTypeOptions] = Constants.NoSniff).Verifiable();
new NoSniff(null).Invoke(httpContextMock.Object);
httpDictionary.Verify();
}
[Fact]
public void ReferrerPolicy()
{
httpDictionary.SetupSet(a => a[Constants.ReferrerPolicy] = "origin").Verifiable();
new ReferrerPolicyMiddleware(null, HardHat.ReferrerPolicy.Origin).Invoke(httpContextMock.Object);
httpDictionary.Verify();
}
}
}

View File

@@ -0,0 +1,37 @@
using System;
using System.Collections.Generic;
using System.Text;
using HardHat.Builders;
using Xunit;
namespace HardHat.UnitTests
{
public class HpKpBuilderTests
{
[Fact]
public void ThrowsExceptions()
{
Assert.Throws<ArgumentOutOfRangeException>(() =>
{
HpKpHeaderBuilder.Build(0, null);
});
Assert.Throws<ArgumentNullException>(() =>
{
HpKpHeaderBuilder.Build(2, null);
});
Assert.Throws<ArgumentException>(() =>
{
HpKpHeaderBuilder.Build(2, new List<PublicKeyPin>());
});
var results = HpKpHeaderBuilder.Build(2, new List<PublicKeyPin>()
{
new PublicKeyPin("yo", HpKpCrypto.sha256),
new PublicKeyPin("dawg", HpKpCrypto.sha256)
}, true, "/awesome");
Assert.Equal<string>("pin-sha256=\"yo\"; pin-sha256=\"dawg\"; max-age=2; includeSubDomains; report-uri=\"/awesome\"", results);
}
}
}

View File

@@ -0,0 +1,16 @@
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTrademark("")]
// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]
// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("5fce3103-91ed-4708-921b-2e881d67ad53")]

View File

@@ -0,0 +1,97 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace HardHat.Builders
{
internal class ContentSecurityHeaderBuilder
{
public static string Build(ContentSecurityPolicy policy)
{
var stringBuilder = new StringBuilder();
if (policy == null)
{
throw new ArgumentNullException(nameof(policy));
}
if (policy.DefaultSrc != null && policy.DefaultSrc.Count > 0)
{
stringBuilder.Append(Constants.CSPDirectives.DefaultSrc);
stringBuilder.Append($" {string.Join(" ", policy.DefaultSrc)}; ");
}
if (policy.ScriptSrc != null && policy.ScriptSrc.Count > 0)
{
stringBuilder.Append(Constants.CSPDirectives.ScriptSrc);
stringBuilder.Append($" {string.Join(" ", policy.ScriptSrc)}; ");
}
if (policy.StyleSrc != null && policy.StyleSrc.Count > 0)
{
stringBuilder.Append(Constants.CSPDirectives.StyleSrc);
stringBuilder.Append($" {string.Join(" ", policy.StyleSrc)}; ");
}
if (policy.ImgSrc != null && policy.ImgSrc.Count > 0)
{
stringBuilder.Append(Constants.CSPDirectives.ImgSrc);
stringBuilder.Append($" {string.Join(" ", policy.ImgSrc)}; ");
}
if (policy.ConnectSrc != null && policy.ConnectSrc.Count > 0)
{
stringBuilder.Append(Constants.CSPDirectives.ConnectSrc);
stringBuilder.Append($" {string.Join(" ", policy.ConnectSrc)}; ");
}
if (policy.FontSrc != null && policy.FontSrc.Count > 0)
{
stringBuilder.Append(Constants.CSPDirectives.FontSrc);
stringBuilder.Append($" {string.Join(" ", policy.FontSrc)}; ");
}
if (policy.ObjectSrc != null && policy.ObjectSrc.Count > 0)
{
stringBuilder.Append(Constants.CSPDirectives.ObjectSrc);
stringBuilder.Append($" {string.Join(" ", policy.ObjectSrc)}; ");
}
if (policy.MediaSrc != null && policy.MediaSrc.Count > 0)
{
stringBuilder.Append(Constants.CSPDirectives.MediaSrc);
stringBuilder.Append($" {string.Join(" ", policy.MediaSrc)}; ");
}
if (policy.ChildSrc != null && policy.ChildSrc.Count > 0)
{
stringBuilder.Append(Constants.CSPDirectives.ChildSrc);
stringBuilder.Append($" {string.Join(" ", policy.ChildSrc)}; ");
}
if (policy.FormAction != null && policy.FormAction.Count > 0)
{
stringBuilder.Append(Constants.CSPDirectives.FormAction);
stringBuilder.Append($" {string.Join(" ", policy.FormAction)}; ");
}
if (policy.FrameAncestors != null && policy.FrameAncestors.Count > 0)
{
stringBuilder.Append(Constants.CSPDirectives.FrameAncestors);
stringBuilder.Append($" {string.Join(" ", policy.FrameAncestors)}; ");
}
if (policy.Sandbox != null)
{
stringBuilder.Append(Constants.CSPDirectives.Sandbox);
stringBuilder.Append($" {policy.Sandbox.Value}; ");
}
if (!string.IsNullOrWhiteSpace(policy.ReportUri))
{
if (policy.OnlySendReport)
{
stringBuilder.Append(Constants.CSPDirectives.ReportUriReportOnly);
}
else
{
stringBuilder.Append(Constants.CSPDirectives.ReportUri);
}
stringBuilder.Append($" {policy.ReportUri}; ");
}
if(policy.PluginTypes != null && policy.PluginTypes.Count > 0)
{
stringBuilder.Append(Constants.CSPDirectives.PluginTypes);
stringBuilder.Append($" {string.Join(" ", policy.PluginTypes)}; ");
}
return stringBuilder.ToString().TrimEnd();
}
}
}

View File

@@ -0,0 +1,23 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace HardHat.Builders
{
internal static class ExpectCtHeaderBuilder
{
internal static string Build(ulong maxAge, string reportUri, bool enforce = false)
{
if (string.IsNullOrWhiteSpace(reportUri))
{
throw new ArgumentNullException(nameof(reportUri), "Report URI must have a value");
}
var builder = new StringBuilder($"max-age={maxAge}; report-uri=\"{reportUri}\"");
if (enforce)
{
builder.Append("; enforce");
}
return builder.ToString();
}
}
}

View File

@@ -0,0 +1,42 @@
using HardHat;
using System;
using System.Collections.Generic;
using System.Text;
namespace HardHat.Builders
{
internal static class HpKpHeaderBuilder
{
internal static string Build(ulong maxAge, ICollection<PublicKeyPin> keys, bool includeSubDomains = false, string reportUri = "")
{
if (maxAge < 1)
{
throw new ArgumentOutOfRangeException(nameof(maxAge));
}
if (keys == null)
{
throw new ArgumentNullException(nameof(keys));
}
if (keys.Count < 2)
{
throw new ArgumentException(" The current specification requires including a second pin for a backup key which isn't yet used in production. This allows for changing the server's public key without breaking accessibility for clients that have already noted the pins. This is important for example when the former key gets compromised.", nameof(keys));
}
var builder = new StringBuilder();
foreach(var key in keys)
{
// pin-sha256="base64==";
builder.Append($"pin-{Enum.GetName(typeof(HpKpCrypto), key.cryptoType)}=\"{key.fingerprint}\"; ");
}
builder.Append($"max-age={maxAge}");
if(includeSubDomains)
{
builder.Append($"; includeSubDomains");
}
if(!string.IsNullOrWhiteSpace(reportUri))
{
builder.Append($"; report-uri=\"{reportUri}\"");
}
return builder.ToString();
}
}
}

View File

@@ -0,0 +1,87 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace HardHat
{
public static class CSPConstants
{
internal static string NonceHyphen = "nonce-";
internal static string sha256 = "sha256-";
internal static string sha384 = "sha384-";
internal static string sha512 = "sha512-";
/// <summary>
/// Refers to the origin from which the protected document is being served, including the same URL scheme and port number. You must include the single quotes. Some browsers specifically exclude blob and filesystem from source directives. Sites needing to allow these content types can specify them using the Data attribute. This can be found at Sources.Scheme.*
/// </summary>
public const string Self = @"'self'";
/// <summary>
/// Allows the use of inline resources, such as inline <script> elements, javascript: URLs, inline event handlers, and inline <style> elements. You must include the single quotes.
/// </summary>
public const string UnsafeInline = "unsafe-inline";
/// <summary>
/// Allows the use of eval() and similar methods for creating code from strings. You must include the single quotes.
/// </summary>
public const string UnsafeEval = "unsafe-eval";
/// <summary>
/// Refers to the empty set; that is, no URLs match. The single quotes are required.
/// </summary>
public const string None = @"'none'";
/// <summary>
/// The strict-dynamic source expression specifies that the trust explicitly given to a script present in the markup, by accompanying it with a nonce or a hash, shall be propagated to all the scripts loaded by that root script. At the same time, any whitelist or source expressions such as 'self' or 'unsafe-inline' will be ignored.
/// </summary>
public const string StrictDynamic = "strict-dynamic";
/// <summary>
/// A whitelist for specific inline scripts using a cryptographic nonce (number used once). The server must generate a unique nonce value each time it transmits a policy. It is critical to provide an unguessable nonce, as bypassing a resources policy is otherwise trivial. See unsafe inline script for an example.
/// </summary>
/// <param name="base64Value"></param>
/// <returns></returns>
public static string Nonce(string base64Value) => CSPConstants.NonceHyphen + base64Value;
/// <summary>
/// A sha256, of inline scripts or styles. When generating the hash, don't include the <script> or <style> tags and note that capitalization and whitespace matter, including leading or trailing whitespace.
/// </summary>
/// <param name="hash"></param>
/// <returns></returns>
public static string Sha256(string hash) => CSPConstants.sha256 + hash;
/// <summary>
/// A sha384 of inline scripts or styles. When generating the hash, don't include the <script> or <style> tags and note that capitalization and whitespace matter, including leading or trailing whitespace.
/// </summary>
/// <param name="hash"></param>
/// <returns></returns>
public static string Sha384(string hash) => CSPConstants.sha384 + hash;
/// <summary>
/// A sha512 of inline scripts or styles. When generating the hash, don't include the <script> or <style> tags and note that capitalization and whitespace matter, including leading or trailing whitespace.
/// </summary>
/// <param name="hash"></param>
/// <returns></returns>
public static string Sha512(string hash) => CSPConstants.sha512 + hash;
public static class Schemes
{
/// <summary>
/// Allows data: URIs to be used as a content source. This is insecure; an attacker can also inject arbitrary data: URIs. Use this sparingly and definitely not for scripts.
/// </summary>
public const string Data = "data:";
/// <summary>
/// Allows mediastream: URIs to be used as a content source.
/// </summary>
public const string MediaStream = "mediastream:";
/// <summary>
/// Allows blob: URIs to be used as a content source.
/// </summary>
public const string Blob = "blob:";
/// <summary>
/// Allows filesystem: URIs to be used as a content source.
/// </summary>
public const string filesystem = "filesystem:";
public const string Http = "http:";
public const string Https = "https:";
}
}
}

View File

@@ -2,6 +2,7 @@
{
internal static class Constants
{
internal const string ContentSecurityPolicyHeader = "Content-Security-Policy";
internal const string DnsControlHeader = "X-DNS-Prefetch-Control";
internal const string FrameGuardHeader = "X-Frame-Options";
internal const string DowloadOptions = "X-Download-Options";
@@ -17,8 +18,11 @@
internal const string OneModeEqualsBlock = "1; mode=block";
internal const string Zero = "0";
internal const string UserAgent = "User-Agent";
internal const string ServerHeader = "Server";
internal const string semicolon = ";";
internal const string HpKpHeader = "Public-Key-Pins";
internal const string HpKpHeaderReportOnly = "Public-Key-Pins-Report-Only";
internal const string ExpectCt = "Expect-CT";
internal static class Referrers
{
internal const string NoReferrer = "no-referrer";
@@ -30,5 +34,24 @@
internal const string StrictOriginWhenCrossOrigin = "strict-origin-when-cross-origin";
internal const string UnsafeUrl = "unsafe-url";
}
internal static class CSPDirectives
{
internal const string DefaultSrc = "default-src";
internal const string ScriptSrc = "script-src";
internal const string StyleSrc = "style-src";
internal const string ImgSrc = "img-src";
internal const string ConnectSrc = "connect-src";
internal const string FontSrc = "font-src";
internal const string ObjectSrc = "object-src";
internal const string MediaSrc = "media-src";
internal const string Sandbox = "sandbox";
internal const string ReportUri = "report-uri";
internal const string ReportUriReportOnly = "report-uri-Report-Only";
internal const string ChildSrc = "child-src";
internal const string FormAction = "form-action";
internal const string FrameAncestors = "frame-ancestors";
internal const string PluginTypes = "plugin-types";
}
}
}

View File

@@ -0,0 +1,72 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace HardHat
{
public class ContentSecurityPolicy
{
/// <summary>
/// The default-src is the default policy for loading content such as JavaScript, Images, CSS, Font's, AJAX requests, Frames, HTML5 Media.
/// </summary>
public HashSet<string> DefaultSrc { get; set; } = new HashSet<string>();
/// <summary>
/// Defines valid sources of JavaScript.
/// </summary>
public HashSet<string> ScriptSrc { get; set; } = new HashSet<string>();
/// <summary>
/// Defines valid sources of stylesheets.
/// </summary>
public HashSet<string> StyleSrc { get; set; } = new HashSet<string>();
/// <summary>
/// Defines valid sources of images.
/// </summary>
public HashSet<string> ImgSrc { get; set; } = new HashSet<string>();
/// <summary>
/// Applies to XMLHttpRequest (AJAX), WebSocket or EventSource. If not allowed the browser emulates a 400 HTTP status code.
/// </summary>
public HashSet<string> ConnectSrc { get; set; } = new HashSet<string>();
/// <summary>
/// Defines valid sources of fonts.
/// </summary>
public HashSet<string> FontSrc { get; set; } = new HashSet<string>();
/// <summary>
/// Defines valid sources of plugins, eg <object>, <embed> or <applet>.
/// </summary>
public HashSet<string> ObjectSrc { get; set; } = new HashSet<string>();
/// <summary>
/// Defines valid sources of audio and video, eg HTML5 <audio>, <video> elements.
/// </summary>
public HashSet<string> MediaSrc { get; set; } = new HashSet<string>();
/// <summary>
/// Defines valid sources for web workers and nested browsing contexts loaded using elements such as <frame> and <iframe>
/// </summary>
public HashSet<string> ChildSrc { get; set; } = new HashSet<string>();
/// <summary>
/// Defines valid sources that can be used as a HTML <form> action.
/// </summary>
public HashSet<string> FormAction { get; set; } = new HashSet<string>();
/// <summary>
/// Defines valid sources for embedding the resource using <frame> <iframe> <object> <embed> <applet>. Setting this directive to 'none' should be roughly equivalent to X-Frame-Options: DENY
/// </summary>
public HashSet<string> FrameAncestors { get; set; } = new HashSet<string>();
/// <summary>
/// Defines valid MIME types for plugins invoked via <object> and <embed>. To load an <applet> you must specify application/x-java-applet.
/// </summary>
public HashSet<string> PluginTypes { get; set; } = new HashSet<string>();
/// <summary>
/// The HTTP Content-Security-Policy (CSP) sandbox directive enables a sandbox for the requested resource similar to the <iframe> sandbox attribute. It applies restrictions to a page's actions including preventing popups, preventing the execution of plugins and scripts, and enforcing a same-origin policy.
/// </summary>
public BaseSandboxOption Sandbox { get; set; } = null;
public string ReportUri = string.Empty;
/// <summary>
/// Reports violations that would have occurred. Does not actively enforce the Content Policy
/// </summary>
public bool OnlySendReport { get; set; } = false;
}
}

View File

@@ -0,0 +1,210 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace HardHat
{
public class ContentSecurityPolicyBuilder
{
internal readonly ContentSecurityPolicy Policy = new ContentSecurityPolicy();
/// <summary>
/// The default-src is the default policy for loading content such as JavaScript, Images, CSS, Font's, AJAX requests, Frames, HTML5 Media.
/// </summary>
/// <param name="includeSelf"></param>
/// <param name=""></param>
/// <returns></returns>
public ContentSecurityPolicyBuilder WithDefaultSource(params string[] sources)
{
if(sources == null || sources.Length < 1)
{
throw new ArgumentException(nameof(sources));
}
Policy.DefaultSrc.UnionWith(sources);
return this;
}
/// <summary>
/// Defines valid sources of JavaScript.
/// </summary>
/// <param name="sources"></param>
/// <returns></returns>
public ContentSecurityPolicyBuilder WithScriptSource(params string[] sources)
{
if (sources == null || sources.Length < 1)
{
throw new ArgumentException(nameof(sources));
}
Policy.ScriptSrc.UnionWith(sources);
return this;
}
/// <summary>
/// Defines valid sources of stylesheets.
/// </summary>
/// <param name="souces"></param>
/// <returns></returns>
public ContentSecurityPolicyBuilder WithStyleSource(params string[] sources)
{
if (sources == null || sources.Length < 1)
{
throw new ArgumentException(nameof(sources));
}
Policy.StyleSrc.UnionWith(sources);
return this;
}
/// <summary>
/// Defines valid sources of images.
/// </summary>
/// <param name="sources"></param>
/// <returns></returns>
public ContentSecurityPolicyBuilder WithImageSource(params string[] sources)
{
if (sources == null || sources.Length < 1)
{
throw new ArgumentException(nameof(sources));
}
Policy.ImgSrc.UnionWith(sources);
return this;
}
/// <summary>
/// Applies to XMLHttpRequest (AJAX), WebSocket or EventSource. If not allowed the browser emulates a 400 HTTP status code.
/// </summary>
/// <param name="sources"></param>
/// <returns></returns>
public ContentSecurityPolicyBuilder WithConnectSource(params string[] sources)
{
if (sources == null || sources.Length < 1)
{
throw new ArgumentException(nameof(sources));
}
Policy.ConnectSrc.UnionWith(sources);
return this;
}
/// <summary>
/// Defines valid sources of fonts.
/// </summary>
/// <param name="sources"></param>
/// <returns></returns>
public ContentSecurityPolicyBuilder WithFontSource(params string[] sources)
{
if (sources == null || sources.Length < 1)
{
throw new ArgumentException(nameof(sources));
}
Policy.FontSrc.UnionWith(sources);
return this;
}
/// <summary>
/// Defines valid sources of plugins, eg <object>, <embed> or <applet>.
/// </summary>
/// <param name="sources"></param>
/// <returns></returns>
public ContentSecurityPolicyBuilder WithObjectSource(params string[] sources)
{
if (sources == null || sources.Length < 1)
{
throw new ArgumentException(nameof(sources));
}
Policy.ObjectSrc.UnionWith(sources);
return this;
}
/// <summary>
/// Defines valid sources of audio and video, eg HTML5 <audio>, <video> elements.
/// </summary>
/// <param name="sources"></param>
/// <returns></returns>
public ContentSecurityPolicyBuilder WithMediaSource(params string[] sources)
{
if (sources == null || sources.Length < 1)
{
throw new ArgumentException(nameof(sources));
}
Policy.MediaSrc.UnionWith(sources);
return this;
}
/// <summary>
/// Instructs the browser to POST reports of policy failures to this URI. You can also append -Report-Only to the HTTP header name to instruct the browser to only send reports (does not block anything).
/// </summary>
/// <param name="uri"></param>
/// <returns></returns>
public ContentSecurityPolicyBuilder WithReportUri(string uri, bool OnlySendReport = false)
{
if(string.IsNullOrWhiteSpace(uri))
{
throw new ArgumentNullException(nameof(uri));
}
Policy.ReportUri = uri;
Policy.OnlySendReport = OnlySendReport;
return this;
}
/// <summary>
/// Defines valid sources for web workers and nested browsing contexts loaded using elements such as <frame> and <iframe>
/// </summary>
/// <returns></returns>
public ContentSecurityPolicyBuilder WithChildSource(params string[] sources)
{
if(sources == null || sources.Length < 1)
{
throw new ArgumentException(nameof(sources));
}
Policy.ChildSrc.UnionWith(sources);
return this;
}
/// <summary>
/// Defines valid sources that can be used as a HTML <form> action.
/// </summary>
/// <param name="sources"></param>
/// <returns></returns>
public ContentSecurityPolicyBuilder WithFormAction(params string[] sources)
{
if (sources == null || sources.Length < 1)
{
throw new ArgumentException(nameof(sources));
}
Policy.FormAction.UnionWith(sources);
return this;
}
/// <summary>
/// Defines valid sources for embedding the resource using <frame> <iframe> <object> <embed> <applet>. Setting this directive to 'none' should be roughly equivalent to X-Frame-Options: DENY
/// </summary>
/// <param name="sources"></param>
/// <returns></returns>
public ContentSecurityPolicyBuilder WithFrameAncestors(params string[] sources)
{
if (sources == null || sources.Length < 1)
{
throw new ArgumentException(nameof(sources));
}
Policy.FrameAncestors.UnionWith(sources);
return this;
}
// plugin-types plugin-types application/x-shockwave-flash
/// <summary>
/// Defines valid MIME types for plugins invoked via <object> and <embed>. To load an <applet> you must specify application/x-java-applet.
/// </summary>
/// <param name="mimeTypes">valid mime types</param>
/// <returns></returns>
public ContentSecurityPolicyBuilder WithPluginTypes(params string[] mimeTypes)
{
if (mimeTypes == null || mimeTypes.Length < 1)
{
throw new ArgumentException(nameof(mimeTypes));
}
Policy.PluginTypes.UnionWith(mimeTypes);
return this;
}
/// <summary>
/// sandbox directive enables a sandbox for the requested resource similar to the <iframe> sandbox attribute. It applies restrictions to a page's actions including preventing popups, preventing the execution of plugins and scripts, and enforcing a same-origin policy.
/// </summary>
/// <param name="sandboxOption"></param>
/// <returns></returns>
public ContentSecurityPolicyBuilder WithSandBox(BaseSandboxOption sandboxOption)
{
Policy.Sandbox = sandboxOption ?? throw new ArgumentNullException(nameof(sandboxOption));
return this;
}
public ContentSecurityPolicy BuildPolicy() => Policy;
}
}

View File

@@ -1,4 +1,7 @@
using HardHat;
using HardHat.Middlewares;
using System;
using System.Collections.Generic;
namespace Microsoft.AspNetCore.Builder
{
@@ -28,6 +31,19 @@ namespace Microsoft.AspNetCore.Builder
/// <returns></returns>
public static IApplicationBuilder UseHsts(this IApplicationBuilder app, ulong maxAge, bool includeSubDomains = false, bool preload = false) => app.UseMiddleware<Hsts>(maxAge, includeSubDomains, preload);
/// <summary>
/// The Strict-Transport-Security HTTP header tells browsers to stick with HTTPS and never visit the insecure HTTP version.
/// </summary>
/// <param name="app"></param>
/// <param name="maxAge"></param>
/// <param name="includeSubDomains"></param>
/// <param name="preload"></param>
/// <returns></returns>
public static IApplicationBuilder UseHsts(this IApplicationBuilder app, TimeSpan maxAge, bool includeSubDomains = false, bool preload = false)
{
app.UseMiddleware<Hsts>(Convert.ToUInt64(maxAge.TotalSeconds), includeSubDomains, preload);
return app;
}
/// <summary>
/// This middleware sets the X-Download-Options to prevent Internet Explorer from executing downloads in your sites context.
/// </summary>
/// <param name="app"></param>
@@ -45,7 +61,7 @@ namespace Microsoft.AspNetCore.Builder
/// <param name="app"></param>
/// <param name="policy">A referrer policy. You can read more about them here: https://www.w3.org/TR/referrer-policy/#referrer-policies </param>
/// <returns></returns>
public static IApplicationBuilder UseReferrerPolicy(this IApplicationBuilder app, ReferrerPolicy policy) => app.UseMiddleware<ReferrerPolicyMiddlewear>(policy);
public static IApplicationBuilder UseReferrerPolicy(this IApplicationBuilder app, ReferrerPolicy policy) => app.UseMiddleware<ReferrerPolicyMiddleware>(policy);
/// <summary>
/// This adds the X-XSS-Protection header which allows the browser to detect and block some xss attacks
/// </summary>
@@ -53,5 +69,51 @@ namespace Microsoft.AspNetCore.Builder
/// <param name="addOldIE">turning this on for ie8 and 9 can actually cause worse vulnerabilities. By default we do not add old IE, but you can override this behavior</param>
/// <returns></returns>
public static IApplicationBuilder UseCrossSiteScriptingFilters(this IApplicationBuilder app, bool addOldIE = false) => app.UseMiddleware<XSSProtection>(addOldIE);
/// <summary>
/// The HTTP Content-Security-Policy response header allows web site administrators to control resources the user agent is allowed to load for a given page. With a few exceptions, policies mostly involve specifying server origins and script endpoints. This helps guard against cross-site scripting attacks
/// </summary>
/// <param name="app"></param>
/// <param name="policy"></param>
/// <returns></returns>
public static IApplicationBuilder UseContentSecurityPolicy(this IApplicationBuilder app, ContentSecurityPolicy policy) => app.UseMiddleware<ContentSecurityPolicyMiddleware>(policy);
/// <summary>
/// To ensure the authenticity of a server's public key used in TLS sessions, this public key is wrapped into a X.509 certificate which is usually signed by a certificate authority (CA). Web clients such as browsers trust a lot of these CAs, which can all create certificates for arbitrary domain names. If an attacker is able to compromise a single CA, they can perform MITM attacks on various TLS connections. HPKP can circumvent this threat for the HTTPS protocol by telling the client which public key belongs to a certain web server.
/// </summary>
/// <param name="app"></param>
/// <param name="maxAge">The time, in seconds, that the browser should remember that this site is only to be accessed using one of the defined keys.</param>
/// <param name="keys"></param>
/// <param name="includeSubDomains">If this optional parameter is specified, this rule applies to all of the site's subdomains as well.</param>
/// <param name="reportUri">If this optional parameter is specified, pin validation failures are reported to the given URL.</param>
/// <param name="reportOnly">Instead of using a Public-Key-Pins header you can also use a Public-Key-Pins-Report-Only header. This header only sends reports to the report-uri specified in the header and does still allow browsers to connect to the webserver even if the pinning is violated.</param>
/// <returns></returns>
public static IApplicationBuilder UseHpkp(this IApplicationBuilder app, ulong maxAge, ICollection<PublicKeyPin> keys, bool includeSubDomains = false, string reportUri = "", bool reportOnly = false) => app.UseMiddleware<Hpkp>(maxAge, keys, includeSubDomains, reportUri, reportOnly);
/// <summary>
/// NOTE: This is still in draft spec, browser support maybe very limited
/// <para>Certificate Transparency is an open framework for monitoring and auditing the certificates issued by Certificate Authorities in near real-time. By requiring a CA to log all certificates they generate, site owners can quickly identify mis-issued certificates and it becomes much easier to detect a rogue CA. <see href="https://scotthelme.co.uk/a-new-security-header-expect-ct/"/></para>
/// </summary>
/// <param name="app"></param>
/// <param name="maxAge"> specifies the number of seconds that the browser should cache and apply the received policy for, whether enforced or report-only.</param>
/// <param name="reportUri"> specifies where the browser should send reports if it does not receive valid CT information. This is specified as an absolute URI.</param>
/// <param name="enforce">controls whether the browser should enforce the policy or treat it as report-only mode. The directive has no value so you simply include it or not depending on whether or not you want the browser to enforce the policy or just report on it.</param>
/// <returns></returns>
public static IApplicationBuilder UseCertificateTransparency(this IApplicationBuilder app, ulong maxAge, string reportUri, bool enforce = false) => app.UseMiddleware<ExpectCt>(maxAge, reportUri, enforce);
/// <summary>
/// change or remove the server header.
/// </summary>
/// <param name="app"></param>
/// <param name="header">Set this to null or empty string to remove the header entirely</param>
/// <returns></returns>
public static IApplicationBuilder UseServerHeader(this IApplicationBuilder app, string header = "")
{
if (string.IsNullOrWhiteSpace(header))
{
return app.UseMiddleware<ServerHeader>(string.Empty);
}
return app.UseMiddleware<ServerHeader>(header);
}
}
}

View File

@@ -0,0 +1,30 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<Description>.Net core Middleware, Add various headers to help secure your site. Disable XSS attacks with Content Security Policies, and make sure browsers do not mime sniff</Description>
<Authors>Tommy Parnell</Authors>
<TargetFramework>netstandard2.0</TargetFramework>
<AssemblyName>HardHat</AssemblyName>
<PackageId>HardHat</PackageId>
<PackageTags>xss;clickjack;clickjacking;security;.net core;Middleware;core;Content Security Policy;CSP</PackageTags>
<PackageIconUrl>https://media.githubusercontent.com/media/TerribleDev/HardHat/master/Hat.png</PackageIconUrl>
<PackageProjectUrl>https://github.com/TerribleDev/HardHat</PackageProjectUrl>
<PackageLicenseUrl>https://opensource.org/licenses/MIT</PackageLicenseUrl>
<SourceLinkServerType>GitHub</SourceLinkServerType>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Http.Abstractions" Version="2.1.1" />
<PackageReference Include="SourceLink.Create.CommandLine" Version="2.4.0" PrivateAssets="All" />
</ItemGroup>
<PropertyGroup>
<TargetsForTfmSpecificContentInPackage>$(TargetsForTfmSpecificContentInPackage);IncludePDBsInPackage</TargetsForTfmSpecificContentInPackage>
</PropertyGroup>
<Target Name="IncludePDBsInPackage" Condition="'$(IncludeBuildOutput)' != 'false'">
<ItemGroup>
<TfmSpecificPackageFile Include="$(OutputPath)\$(AssemblyName).pdb" PackagePath="lib\$(TargetFramework)" />
</ItemGroup>
</Target>
</Project>

View File

@@ -1,21 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">14.0</VisualStudioVersion>
<VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>
</PropertyGroup>
<Import Project="$(VSToolsPath)\DotNet\Microsoft.DotNet.Props" Condition="'$(VSToolsPath)' != ''" />
<PropertyGroup Label="Globals">
<ProjectGuid>b4448494-6c80-4bda-890c-d8900b33fe17</ProjectGuid>
<RootNamespace>HardHat</RootNamespace>
<BaseIntermediateOutputPath Condition="'$(BaseIntermediateOutputPath)'=='' ">.\obj</BaseIntermediateOutputPath>
<OutputPath Condition="'$(OutputPath)'=='' ">.\bin\</OutputPath>
<TargetFrameworkVersion>v4.6.2</TargetFrameworkVersion>
</PropertyGroup>
<PropertyGroup>
<SchemaVersion>2.0</SchemaVersion>
</PropertyGroup>
<Import Project="$(VSToolsPath)\DotNet\Microsoft.DotNet.targets" Condition="'$(VSToolsPath)' != ''" />
</Project>

View File

@@ -0,0 +1,30 @@
using HardHat.Builders;
using Microsoft.AspNetCore.Http;
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading.Tasks;
namespace HardHat.Middlewares
{
public class ContentSecurityPolicyMiddleware
{
private readonly RequestDelegate _next;
private readonly string _policy;
public ContentSecurityPolicyMiddleware(RequestDelegate next, ContentSecurityPolicy policy)
{
if(policy == null)
{
throw new ArgumentNullException(nameof(policy));
}
_policy = ContentSecurityHeaderBuilder.Build(policy);
_next = next;
}
public Task Invoke(HttpContext context)
{
context.Response.Headers[Constants.ContentSecurityPolicyHeader] = _policy;
return _next?.Invoke(context);
}
}
}

View File

@@ -1,7 +1,7 @@
using Microsoft.AspNetCore.Http;
using System.Threading.Tasks;
namespace HardHat
namespace HardHat.Middlewares
{
public class DnsPrefetch
{
@@ -16,7 +16,7 @@ namespace HardHat
public Task Invoke(HttpContext context)
{
context.Response.Headers[Constants.DnsControlHeader] = headerValue;
return _next.Invoke(context);
return _next?.Invoke(context);
}
}
}

View File

@@ -0,0 +1,30 @@
using HardHat.Builders;
using Microsoft.AspNetCore.Http;
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading.Tasks;
namespace HardHat.Middlewares
{
public class ExpectCt
{
private readonly RequestDelegate _next;
private readonly string headerValue;
public ExpectCt(RequestDelegate next, ulong maxAge, string reportUri, bool enforce = false)
{
this._next = next;
if(string.IsNullOrWhiteSpace(reportUri))
{
throw new ArgumentNullException(nameof(reportUri), "Report URI must have a value");
}
headerValue = ExpectCtHeaderBuilder.Build(maxAge, reportUri, enforce);
}
public Task Invoke(HttpContext context)
{
context.Response.Headers[Constants.ExpectCt] = headerValue;
return _next?.Invoke(context);
}
}
}

View File

@@ -2,7 +2,7 @@
using System;
using System.Threading.Tasks;
namespace HardHat
namespace HardHat.Middlewares
{
public class FrameGuard
{
@@ -12,6 +12,10 @@ namespace HardHat
{
this.next = next;
this.options = options;
if(options == null)
{
throw new ArgumentNullException(nameof(options));
}
}
public Task Invoke(HttpContext context)
@@ -24,11 +28,7 @@ namespace HardHat
{
context.Response.Headers[Constants.FrameGuardHeader] = options.domain;
}
else
{
throw new ArgumentException("Frameguard, or domain not set");
}
return next.Invoke(context);
return next?.Invoke(context);
}
}
}

View File

@@ -0,0 +1,49 @@
using HardHat.Builders;
using HardHat;
using Microsoft.AspNetCore.Http;
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading.Tasks;
namespace HardHat.Middlewares
{
public class Hpkp
{
private readonly bool reportOnly;
private readonly RequestDelegate _next;
private readonly string _header;
public Hpkp(RequestDelegate next, ulong maxAge, ICollection<PublicKeyPin> keys, bool includeSubDomains = false, string reportUri = "", bool reportOnly = false)
{
if(maxAge < 1)
{
throw new ArgumentOutOfRangeException(nameof(maxAge));
}
if (keys == null)
{
throw new ArgumentNullException(nameof(keys));
}
if (keys.Count < 2)
{
throw new ArgumentException(" The current specification requires including a second pin for a backup key which isn't yet used in production. This allows for changing the server's public key without breaking accessibility for clients that have already noted the pins. This is important for example when the former key gets compromised.", nameof(keys));
}
_header = HpKpHeaderBuilder.Build(maxAge, keys, includeSubDomains, reportUri);
_next = next;
this.reportOnly = reportOnly;
}
public Task Invoke(HttpContext context)
{
if(reportOnly)
{
context.Response.Headers[Constants.HpKpHeaderReportOnly] = _header;
}
else
{
context.Response.Headers[Constants.HpKpHeader] = _header;
}
return _next.Invoke(context);
}
}
}

View File

@@ -2,7 +2,7 @@
using System.Text;
using System.Threading.Tasks;
namespace HardHat
namespace HardHat.Middlewares
{
public class Hsts
{
@@ -30,7 +30,7 @@ namespace HardHat
public Task Invoke(HttpContext context)
{
context.Response.Headers[Constants.StrictTransportSecurity] = value;
return _next.Invoke(context);
return _next?.Invoke(context);
}
}
}

View File

@@ -1,7 +1,7 @@
using Microsoft.AspNetCore.Http;
using System.Threading.Tasks;
namespace HardHat
namespace HardHat.Middlewares
{
public class IENoOpen
{
@@ -14,7 +14,7 @@ namespace HardHat
public Task Invoke(HttpContext context)
{
context.Response.Headers[Constants.DowloadOptions] = Constants.NoOpen;
return _next.Invoke(context);
return _next?.Invoke(context);
}
}
}

View File

@@ -1,7 +1,7 @@
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
namespace HardHat
namespace HardHat.Middlewares
{
public class NoSniff
{
@@ -14,7 +14,7 @@ namespace HardHat
public Task Invoke(HttpContext context)
{
context.Response.Headers[Constants.XContentTypeOptions] = Constants.NoSniff;
return _next.Invoke(context);
return _next?.Invoke(context);
}
}
}

View File

@@ -0,0 +1,29 @@
using Microsoft.AspNetCore.Http;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace HardHat.Middlewares
{
public class ReferrerPolicyMiddleware
{
private readonly RequestDelegate _next;
private readonly ReferrerPolicy policy;
public ReferrerPolicyMiddleware(RequestDelegate next, ReferrerPolicy policy)
{
this.policy = policy;
_next = next;
if (policy == null)
{
throw new ArgumentNullException(nameof(policy));
}
}
public Task Invoke(HttpContext context)
{
context.Response.Headers[Constants.ReferrerPolicy] = this.policy.Policy;
return _next?.Invoke(context);
}
}
}

View File

@@ -5,7 +5,7 @@ using System.Text.RegularExpressions;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
namespace HardHat
namespace HardHat.Middlewares
{
public class XSSProtection
{
@@ -32,7 +32,7 @@ namespace HardHat
}
}
context.Response.Headers[Constants.XXSProtection] = result;
return _next.Invoke(context);
return _next?.Invoke(context);
}
}
}

View File

@@ -0,0 +1,31 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace HardHat
{
public class PublicKeyPin
{
internal readonly HpKpCrypto cryptoType;
internal readonly string fingerprint;
/// <summary>
///
/// </summary>
/// <param name="fingerprint"> the Base64 encoded Subject Public Key Information (SPKI) fingerprint. It is possible to specify multiple pins for different public keys.</param>
/// <param name="cryptoType"></param>
public PublicKeyPin(string fingerprint, HpKpCrypto cryptoType)
{
if(string.IsNullOrWhiteSpace(fingerprint))
{
throw new ArgumentNullException(nameof(fingerprint));
}
this.fingerprint = fingerprint;
this.cryptoType = cryptoType;
}
}
public enum HpKpCrypto
{
sha256
}
}

View File

@@ -0,0 +1,116 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace HardHat
{
public abstract class BaseSandboxOption
{
internal BaseSandboxOption() { }
public abstract string Value { get; }
}
public class AllowForms : BaseSandboxOption
{
internal AllowForms() { }
public override string Value => "allow-forms";
}
public class AllowModals : BaseSandboxOption
{
internal AllowModals() { }
public override string Value => "allow-modals";
}
public class AllowOrientationLock : BaseSandboxOption
{
internal AllowOrientationLock() { }
public override string Value => "allow-orientation-lock";
}
public class AllowPointerLock : BaseSandboxOption
{
internal AllowPointerLock() { }
public override string Value => "allow-pointer-lock";
}
public class AllowPopups : BaseSandboxOption
{
internal AllowPopups() { }
public override string Value => "allow-popups";
}
public class AllowPopupsToEscapeSandbox : BaseSandboxOption
{
internal AllowPopupsToEscapeSandbox() { }
public override string Value => "allow-popups-to-escape-sandbox";
}
public class AllowPresentation : BaseSandboxOption
{
internal AllowPresentation() { }
public override string Value => "allow-presentation";
}
public class AllowSameOrigin : BaseSandboxOption
{
internal AllowSameOrigin() { }
public override string Value => "allow-same-origin";
}
public class AllowScripts : BaseSandboxOption
{
internal AllowScripts() { }
public override string Value => "allow-scripts";
}
public class AllowTopNaviation : BaseSandboxOption
{
internal AllowTopNaviation() { }
public override string Value => "allow-top-navigation";
}
public static class SandboxOption
{
/// <summary>
/// Allows the embedded browsing context to submit forms. If this keyword is not used, this operation is not allowed.
/// </summary>
public static AllowForms AllowForms = new AllowForms();
/// <summary>
/// Allows the embedded browsing context to open modal windows.
/// </summary>
public static AllowModals AllowModals = new AllowModals();
/// <summary>
/// Allows the embedded browsing context to disable the ability to lock the screen orientation.
/// </summary>
public static AllowOrientationLock AllowOrientationLock = new AllowOrientationLock();
/// <summary>
/// Allows the embedded browsing context to use the <a href="https://developer.mozilla.org/en-US/docs/Web/API/Pointer_Lock_API">Pointer Lock API</a>
/// </summary>
public static AllowPointerLock AllowPointerLock = new AllowPointerLock();
/// <summary>
/// Allows popups (like from window.open, target="_blank", showModalDialog). If this keyword is not used, that functionality will silently fail.
/// </summary>
public static AllowPopups AllowPopups = new AllowPopups();
/// <summary>
/// Allows a sandboxed document to open new windows without forcing the sandboxing flags upon them. This will allow, for example, a third-party advertisement to be safely sandboxed without forcing the same restrictions upon a landing page.
/// </summary>
public static AllowPopupsToEscapeSandbox AllowPopupsToEscapeSandbox = new AllowPopupsToEscapeSandbox();
/// <summary>
/// Allows embedders to have control over whether an iframe can start a presentation session.
/// </summary>
public static AllowPresentation AllowPresentation = new AllowPresentation();
/// <summary>
/// Allows the content to be treated as being from its normal origin. If this keyword is not used, the embedded content is treated as being from a unique origin.
/// </summary>
public static AllowSameOrigin AllowSameOrigin = new AllowSameOrigin();
/// <summary>
/// Allows the embedded browsing context to run scripts (but not create pop-up windows). If this keyword is not used, this operation is not allowed.
/// </summary>
public static AllowScripts AllowScripts = new AllowScripts();
/// <summary>
/// Allows the embedded browsing context to navigate (load) content to the top-level browsing context. If this keyword is not used, this operation is not allowed.
/// </summary>
public static AllowTopNaviation AllowTopNaviation = new AllowTopNaviation();
}
}

View File

@@ -1,12 +1,10 @@
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("HardHat")]
[assembly: AssemblyTrademark("")]
// Setting ComVisible to false makes the types in this assembly not visible
@@ -16,3 +14,4 @@ using System.Runtime.InteropServices;
// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("b4448494-6c80-4bda-890c-d8900b33fe17")]
[assembly: InternalsVisibleTo("HardHat.UnitTests")]

View File

@@ -1,41 +1,58 @@
using Microsoft.AspNetCore.Http;
using System.Threading.Tasks;
using System;
namespace HardHat
{
public class ReferrerPolicyMiddlewear
{
private readonly RequestDelegate _next;
private readonly ReferrerPolicy policy;
public ReferrerPolicyMiddlewear(RequestDelegate next, ReferrerPolicy policy)
{
this.policy = policy;
_next = next;
}
public Task Invoke(HttpContext context)
{
context.Response.Headers[Constants.ReferrerPolicy] = this.policy.Policy;
return _next.Invoke(context);
}
}
public struct ReferrerPolicy
public class ReferrerPolicy
{
internal readonly string Policy;
internal ReferrerPolicy(string policy)
{
this.Policy = policy;
}
//todo: document https://www.w3.org/TR/referrer-policy/#referrer-policies
/// <summary>
/// The empty string "" corresponds to no referrer policy, causing a fallback to a referrer policy defined elsewhere, or in the case where no such higher-level policy is available, defaulting to "no-referrer-when-downgrade". This defaulting happens in the §8.3 Determine requests Referrer algorithm.
/// </summary>
public static readonly ReferrerPolicy Empty = new ReferrerPolicy(string.Empty);
/// <summary>
/// The simplest policy is "no-referrer", which specifies that no referrer information is to be sent along with requests made from a particular request client to any origin. The header will be omitted entirely.
/// </summary>
public static readonly ReferrerPolicy NoReferrer = new ReferrerPolicy(Constants.Referrers.NoReferrer);
/// <summary>
/// The "no-referrer-when-downgrade" policy sends a full URL along with requests from a TLS-protected environment settings object to a potentially trustworthy URL, and requests from clients which are not TLS-protected to any origin.
///Requests from TLS-protected clients to non- potentially trustworthy URLs, on the other hand, will contain no referrer information.A Referer HTTP header will not be sent.
/// </summary>
public static readonly ReferrerPolicy NoReferrerWhenDowngrade = new ReferrerPolicy(Constants.Referrers.NoReferrerWhenDowngrade);
/// <summary>
/// The "same-origin" policy specifies that a full URL, stripped for use as a referrer, is sent as referrer information when making same-origin requests from a particular client.
///Cross-origin requests, on the other hand, will contain no referrer information.A Referer HTTP header will not be sent.
/// </summary>
public static readonly ReferrerPolicy SameOrigin = new ReferrerPolicy(Constants.Referrers.SameOrigin);
/// <summary>
/// The "origin" policy specifies that only the ASCII serialization of the origin of the request client is sent as referrer information when making both same-origin requests and cross-origin requests from a particular client.
/// </summary>
public static readonly ReferrerPolicy Origin = new ReferrerPolicy(Constants.Referrers.Origin);
/// <summary>
/// The "strict-origin" policy sends the ASCII serialization of the origin of the request client when making requests:
/// <para/>
///from a TLS-protected environment settings object to a potentially trustworthy URL, and from non-TLS-protected environment settings objects to any origin.
///Requests from TLS-protected request clients to non- potentially trustworthy URLs, on the other hand, will contain no referrer information.A Referer HTTP header will not be sent.
/// </summary>
public static readonly ReferrerPolicy StrictOrigin = new ReferrerPolicy(Constants.Referrers.StrictOrigin);
/// <summary>
/// The "origin-when-cross-origin" policy specifies that a full URL, stripped for use as a referrer, is sent as referrer information when making same-origin requests from a particular request client, and only the ASCII serialization of the origin of the request client is sent as referrer information when making cross-origin requests from a particular client.
/// </summary>
public static readonly ReferrerPolicy OriginWhenCrossOrigin = new ReferrerPolicy(Constants.Referrers.OriginWhenCrossOrigin);
/// <summary>
/// The "strict-origin-when-cross-origin" policy specifies that a full URL, stripped for use as a referrer, is sent as referrer information when making same-origin requests from a particular request client, and only the ASCII serialization of the origin of the request client when making cross-origin requests:
/// <para />
/// from a TLS-protected environment settings object to a potentially trustworthy URL, and from non-TLS-protected environment settings objects to any origin.
/// </summary>
public static readonly ReferrerPolicy StrictOriginWhenCrossOrigin = new ReferrerPolicy(Constants.Referrers.StrictOriginWhenCrossOrigin);
/// <summary>
/// The "unsafe-url" policy specifies that a full URL, stripped for use as a referrer, is sent along with both cross-origin requests and same-origin requests made from a particular client.
/// </summary>
public static readonly ReferrerPolicy UnsafeUrl = new ReferrerPolicy(Constants.Referrers.UnsafeUrl);
}
}

View File

@@ -0,0 +1,33 @@
using Microsoft.AspNetCore.Http;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace HardHat
{
public class ServerHeader
{
private readonly string header;
private readonly RequestDelegate _next;
public ServerHeader(RequestDelegate next, string header)
{
_next = next;
this.header = header;
}
public Task Invoke(HttpContext context)
{
if (string.IsNullOrWhiteSpace(header))
{
context.Response.Headers.Remove(Constants.ServerHeader);
}
else
{
context.Response.Headers[Constants.ServerHeader] = header;
}
return _next?.Invoke(context);
}
}
}

View File

@@ -1,20 +0,0 @@
{
"version": "1.0.0-beta-1",
"description": ".Net core Middleware, Add various headers to help secure your site",
"authors": [ "Tommy Parnell" ],
"packOptions": {
"tags": [ "xss", "clickjack", "clickjacking", "security", ".net core", "Middleware", "core" ],
"projectUrl": "https://github.com/tparnell8/TurboLinks.Net",
"licenseUrl": "https://opensource.org/licenses/MIT"
},
"dependencies": {
"NETStandard.Library": "1.6.0",
"Microsoft.AspNetCore.Http.Abstractions": "1.0.0"
},
"frameworks": {
"netstandard1.6": {
"imports": "dnxcore50"
}
}
}