add hpkp
This commit is contained in:
@@ -22,7 +22,7 @@ In short this allows:
|
||||
app.UseIENoOpen(); // don't allow old ie to open files in the context of your site
|
||||
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(
|
||||
app.UseContentSecurityPolicy( // Provide a security policy so only content can come from trusted sources
|
||||
new ContentSecurityPolicyBuilder()
|
||||
.WithDefaultSource(CSPConstants.Self)
|
||||
.WithImageSource("http://images.mysite.com")
|
||||
@@ -30,6 +30,10 @@ In short this allows:
|
||||
.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 =>
|
||||
{
|
||||
|
||||
@@ -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
|
||||
{
|
||||
@@ -58,13 +59,13 @@ namespace HardHat.Example
|
||||
.WithMediaSource(CSPConstants.Schemes.MediaStream)
|
||||
.BuildPolicy()
|
||||
);
|
||||
app.UseStaticFiles();
|
||||
// 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);
|
||||
|
||||
new ContentSecurityPolicyBuilder()
|
||||
.WithFontSource(CSPConstants.Self)
|
||||
.WithImageSource("https://example.com")
|
||||
.WithSandBox(SandboxOption.AllowForms);
|
||||
|
||||
app.UseStaticFiles();
|
||||
|
||||
app.UseMvc(routes =>
|
||||
{
|
||||
|
||||
37
src/HardHat.UnitTests/HpKpBuilderTests.cs
Normal file
37
src/HardHat.UnitTests/HpKpBuilderTests.cs
Normal 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);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
42
src/HardHat/Builders/HpKpHeaderBuilder.cs
Normal file
42
src/HardHat/Builders/HpKpHeaderBuilder.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -20,6 +20,8 @@
|
||||
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 static class Referrers
|
||||
{
|
||||
internal const string NoReferrer = "no-referrer";
|
||||
|
||||
@@ -63,7 +63,9 @@ namespace HardHat
|
||||
|
||||
|
||||
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;
|
||||
|
||||
}
|
||||
|
||||
@@ -183,7 +183,7 @@ namespace HardHat
|
||||
/// <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"></param>
|
||||
/// <param name="mimeTypes">valid mime types</param>
|
||||
/// <returns></returns>
|
||||
public ContentSecurityPolicyBuilder WithPluginTypes(params string[] mimeTypes)
|
||||
{
|
||||
@@ -194,7 +194,11 @@ namespace HardHat
|
||||
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));
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
using HardHat;
|
||||
using HardHat.Middlewares;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Microsoft.AspNetCore.Builder
|
||||
{
|
||||
public static class Extensions
|
||||
@@ -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 site’s context.
|
||||
/// </summary>
|
||||
/// <param name="app"></param>
|
||||
@@ -62,6 +78,18 @@ namespace Microsoft.AspNetCore.Builder
|
||||
/// <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>
|
||||
/// change or remove the server header.
|
||||
/// </summary>
|
||||
|
||||
49
src/HardHat/Middlewares/Hpkp.cs
Normal file
49
src/HardHat/Middlewares/Hpkp.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
31
src/HardHat/Models/PublicKeyPin.cs
Normal file
31
src/HardHat/Models/PublicKeyPin.cs
Normal 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
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user