Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implemented Sha256Support instead of nonce #56

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,11 @@ public CspScriptsBuilder AddNonce()
_options.AddNonce = true;
return this;
}
public CspScriptsBuilder AddSha256()
{
_options.AddSha256 = true;
return this;
}

/// <summary>
/// Allow JavaScript from anywhere, except
Expand Down Expand Up @@ -123,4 +128,4 @@ public CspScriptSrcOptions BuildOptions()
return _options;
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,11 @@ public CspStylesBuilder AddNonce()
_options.AddNonce = true;
return this;
}
public CspStylesBuilder AddSha256()
{
_options.AddSha256 = true;
return this;
}

/// <summary>
/// Allow CSS only over secure connections.
Expand All @@ -96,4 +101,4 @@ public CspStyleSrcOptions BuildOptions()
return _options;
}
}
}
}
171 changes: 92 additions & 79 deletions src/Joonasw.AspNetCore.SecurityHeaders/Csp/CspMiddleware.cs
Original file line number Diff line number Diff line change
@@ -1,79 +1,92 @@
using System;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Options;

namespace Joonasw.AspNetCore.SecurityHeaders.Csp
{
public class CspMiddleware
{
private const string CspHeaderName = "Content-Security-Policy";
private const string CspReportOnlyHeaderName = "Content-Security-Policy-Report-Only";
private readonly RequestDelegate _next;
private readonly CspOptions _options;
private readonly string _headerName;
private readonly string _headerValue;

public CspMiddleware(RequestDelegate next, IOptions<CspOptions> options)
{
if (options == null)
{
throw new ArgumentNullException(nameof(options));
}

_next = next;
_options = options.Value;
// If a nonce is needed to be generated, we can't cache the header value
if (_options.IsNonceNeeded)
{
_headerName = null;
_headerValue = null;
}
else
{
//If nonces are not needed, we can cache them immediately
var (headerName, headerValue) = _options.ToString(null);
_headerName = headerName;
_headerValue = headerValue;
}
}

public async Task Invoke(HttpContext context)
{
// Check if a CSP header has already been added to the response
// This can happen for example if a middleware re-executes the pipeline
if (!ContainsCspHeader(context.Response))
{
var sendingHeaderContext = new CspSendingHeaderContext(context);
//Call the per-request check if CSP should be sent
await _options.OnSendingHeader(sendingHeaderContext);

if (!sendingHeaderContext.ShouldNotSend)
{
string headerName;
string headerValue;
if (_options.IsNonceNeeded)
{
var nonceService = (ICspNonceService)context.RequestServices.GetService(typeof(ICspNonceService));
(headerName, headerValue) = _options.ToString(nonceService);
}
else
{
headerName = _headerName;
headerValue = _headerValue;
}
context.Response.Headers.Add(headerName, headerValue);
}
}

await _next.Invoke(context);
}

private bool ContainsCspHeader(HttpResponse response)
{
return response.Headers.Any(h => h.Key.Equals(CspHeaderName, StringComparison.OrdinalIgnoreCase)
|| h.Key.Equals(CspReportOnlyHeaderName, StringComparison.OrdinalIgnoreCase));
}
}
}
using System;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Options;

namespace Joonasw.AspNetCore.SecurityHeaders.Csp
{
public class CspMiddleware
{
private const string CspHeaderName = "Content-Security-Policy";
private const string CspReportOnlyHeaderName = "Content-Security-Policy-Report-Only";
private readonly RequestDelegate _next;
private readonly CspOptions _options;
private readonly string _headerName;
private readonly string _headerValue;

public CspMiddleware(RequestDelegate next, IOptions<CspOptions> options)
{
if (options == null)
{
throw new ArgumentNullException(nameof(options));
}

_next = next;
_options = options.Value;
// If a nonce is needed to be generated, we can't cache the header value
if (_options.IsNonceNeeded || _options.IsSha256Needed)
{
_headerName = null;
_headerValue = null;
}
else
{
//If nonces are not needed, we can cache them immediately
var (headerName, headerValue) = _options.ToString(null,null);
_headerName = headerName;
_headerValue = headerValue;
}
}

public async Task Invoke(HttpContext context)
{
if (_options.IsSha256Needed)
context.Response.OnStarting(() => OrigInvoke(context));
else
await OrigInvoke(context);
await _next.Invoke(context);

}
public async Task OrigInvoke(HttpContext context)
{
// Check if a CSP header has already been added to the response
// This can happen for example if a middleware re-executes the pipeline
if (!ContainsCspHeader(context.Response))
{
var sendingHeaderContext = new CspSendingHeaderContext(context);
//Call the per-request check if CSP should be sent
await _options.OnSendingHeader(sendingHeaderContext);

if (!sendingHeaderContext.ShouldNotSend)
{
string headerName;
string headerValue;
if (_options.IsSha256Needed)
{
var shaService = (ICspSha256Service)context.RequestServices.GetService(typeof(ICspSha256Service));
(headerName, headerValue) = _options.ToString(null, shaService);
}
else if (_options.IsNonceNeeded)
{
var nonceService = (ICspNonceService)context.RequestServices.GetService(typeof(ICspNonceService));
(headerName, headerValue) = _options.ToString(nonceService,null);
}
else
{
headerName = _headerName;
headerValue = _headerValue;
}
context.Response.Headers.Add(headerName, headerValue);
}
}

}

private bool ContainsCspHeader(HttpResponse response)
{
return response.Headers.Any(h => h.Key.Equals(CspHeaderName, StringComparison.OrdinalIgnoreCase)
|| h.Key.Equals(CspReportOnlyHeaderName, StringComparison.OrdinalIgnoreCase));
}
}
}
34 changes: 34 additions & 0 deletions src/Joonasw.AspNetCore.SecurityHeaders/Csp/CspSha256Service.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
using System;
using System.Collections.Generic;
using System.Security.Cryptography;

namespace Joonasw.AspNetCore.SecurityHeaders.Csp
{
public class CspSha256Service : ICspSha256Service
{
List<string> _shaScripts=new List<string>();
List<string> _shaStyles = new List<string>();
static SHA256 sha = SHA256.Create();
public string AddShaStyles(string content)
{
var sha256 = System.Convert.ToBase64String(sha.ComputeHash(System.Text.Encoding.UTF8.GetBytes(content)));
_shaStyles.Add(sha256);
return sha256;
}
public string AddShaScripts(string content)
{
var sha256 = System.Convert.ToBase64String(sha.ComputeHash(System.Text.Encoding.UTF8.GetBytes(content)));
_shaScripts.Add(sha256);
return sha256;
}

public List<string> GetShaStyles()
{
return _shaStyles;
}
public List<string> GetShaScripts()
{
return _shaScripts;
}
}
}
13 changes: 13 additions & 0 deletions src/Joonasw.AspNetCore.SecurityHeaders/Csp/ICspSha256Service.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
using System.Collections.Generic;

namespace Joonasw.AspNetCore.SecurityHeaders.Csp
{
public interface ICspSha256Service
{
string AddShaScripts(string content);
string AddShaStyles(string content);
List<string> GetShaScripts();
List<string> GetShaStyles();

}
}
Original file line number Diff line number Diff line change
@@ -1,53 +1,82 @@
using System;
using System.Collections.Generic;

namespace Joonasw.AspNetCore.SecurityHeaders.Csp.Options
{
public class CspScriptSrcOptions : CspSrcOptionsBase
{
public bool AddNonce { get; set; }
public bool AllowUnsafeEval { get; set; }
public bool AllowUnsafeInline { get; set; }
/// <summary>
/// Allow scripts that have been loaded with
/// a trusted hash/nonce to load additional
/// scripts.
/// This enabled a &quot;strict&quot; mode
/// for scripts, requiring a hash or nonce
/// on all of them.
/// </summary>
public bool StrictDynamic { get; set; }
public CspScriptSrcOptions()
: base("script-src")
{
}

protected override ICollection<string> GetParts(ICspNonceService nonceService)
{
ICollection<string> parts = base.GetParts(nonceService);
if (AddNonce)
{
if(nonceService == null)
{
throw new ArgumentNullException(
nameof(nonceService),
"Nonce service was not found, it needs to be added to the service collection");
}
parts.Add($"'nonce-{nonceService.GetNonce()}'");
}
if (AllowUnsafeEval)
{
parts.Add("'unsafe-eval'");
}
if (AllowUnsafeInline)
{
parts.Add("'unsafe-inline'");
}
if (StrictDynamic)
{
parts.Add("'strict-dynamic'");
}
return parts;
}
}
}
using System;
using System.Collections.Generic;

namespace Joonasw.AspNetCore.SecurityHeaders.Csp.Options
{
public class CspScriptSrcOptions : CspSrcOptionsBase
{
public bool AddNonce { get; set; }
public bool AddSha256 { get; set; }
public bool AllowUnsafeEval { get; set; }
public bool AllowUnsafeInline { get; set; }
/// <summary>
/// Allow scripts that have been loaded with
/// a trusted hash/nonce to load additional
/// scripts.
/// This enabled a &quot;strict&quot; mode
/// for scripts, requiring a hash or nonce
/// on all of them.
/// </summary>
public bool StrictDynamic { get; set; }
public CspScriptSrcOptions()
: base("script-src")
{
}

protected override ICollection<string> GetParts(ICspNonceService nonceService)
{
ICollection<string> parts = base.GetParts(nonceService);
if (AddNonce)
{
if(nonceService == null)
{
throw new ArgumentNullException(
nameof(nonceService),
"Nonce service was not found, it needs to be added to the service collection");
}
parts.Add($"'nonce-{nonceService.GetNonce()}'");
}
if (AllowUnsafeEval)
{
parts.Add("'unsafe-eval'");
}
if (AllowUnsafeInline)
{
parts.Add("'unsafe-inline'");
}
if (StrictDynamic)
{
parts.Add("'strict-dynamic'");
}
return parts;
}
protected override ICollection<string> GetParts(ICspSha256Service sha256Service)
{
ICollection<string> parts = base.GetParts(sha256Service);
if (AddSha256)
{
if (sha256Service == null)
{
throw new ArgumentNullException(
nameof(sha256Service),
"Sha256 service was not found, it needs to be added to the service collection");
}
sha256Service.GetShaScripts().ForEach(s=>
parts.Add($"'sha256-{s}'"));
}
if (AllowUnsafeEval)
{
parts.Add("'unsafe-eval'");
}
if (AllowUnsafeInline)
{
parts.Add("'unsafe-inline'");
}
if (StrictDynamic)
{
parts.Add("'strict-dynamic'");
}
return parts;
}
}
}
Loading