Skip to content
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
16 changes: 15 additions & 1 deletion src/Joonasw.AspNetCore.SecurityHeaders/Csp/CspNonceService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,12 @@ namespace Joonasw.AspNetCore.SecurityHeaders.Csp
public class CspNonceService : ICspNonceService
{
private readonly string _nonce;
private const char Base64PadCharacter = '=';
private const string DoubleBase64PadCharacter = "==";
private const char Base64Character62 = '+';
private const char Base64Character63 = '/';
private const char Base64UrlCharacter62 = '-';
private const char Base64UrlCharacter63 = '_';

public CspNonceService(int nonceByteAmount = 32)
{
Expand All @@ -15,7 +21,15 @@ public CspNonceService(int nonceByteAmount = 32)
rng.GetBytes(nonceBytes);
}

_nonce = Convert.ToBase64String(nonceBytes);
var s = Convert.ToBase64String(nonceBytes);
// a base64String can have elements that aren't URL friendly. In testing chrome appeared to ignore nonces that weren't URL friendly (or the tag helper did).
// the replacement code comes from Microsoft.IdentityModel.Tokens.Base64UrlEncoder
s = s.Split(Base64PadCharacter)[0]; // Remove any trailing padding
s = s.Replace(Base64Character62, Base64UrlCharacter62); // 62nd char of encoding
s = s.Replace(Base64Character63, Base64UrlCharacter63); // 63rd char of encoding

_nonce = s;

}

public string GetNonce()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,11 @@ public override void Process(TagHelperContext context, TagHelperOutput output)
{
// The nonce service is created per request, so we
// get the same nonce here as the CSP header
output.Attributes.Add("nonce", _nonceService.GetNonce());
// when doing this, the nonce wasn't reliably being injected (that might have been due to the nonce have non url friendly chars).
//output.Attributes.Add("nonce", _nonceService.GetNonce());
// this injected the nonce all the time.
var attribute = new TagHelperAttribute("nonce", _nonceService.GetNonce(), HtmlAttributeValueStyle.DoubleQuotes);
output.Attributes.SetAttribute(attribute);
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@

namespace Joonasw.AspNetCore.SecurityHeaders.Tests
{
public class CspScriptsBuilderTests
using Csp;

public class CspScriptsBuilderTests
{
[Fact]
public void FromNowhere_SetsAllowNoneToTrue()
Expand All @@ -30,6 +32,24 @@ public void FromSelf_SetsAllowSelfToTrue()
Assert.True(options.AllowSelf);
}

[Fact]
public void FromSelf_WithNonce_HasValue()
{
var nonceService = new CspNonceService(32);
var nonce = nonceService.GetNonce();

var builder = new CspBuilder();
builder.AllowScripts.FromSelf().AddNonce();

var headerValue = builder.BuildCspOptions().ToString(nonceService).headerValue;

Assert.DoesNotContain("+", nonce);
Assert.DoesNotContain("/", nonce);
Assert.DoesNotContain("=", nonce);

Assert.Equal($"script-src 'self' 'nonce-{nonce}'", headerValue);
}

[Fact]
public void From_AddsUrlToAllowedSources()
{
Expand Down