Merge pull request #2283 from DuendeSoftware/mb/csp

Add tests for wrong CSP hash constants
This commit is contained in:
Maarten Balliauw 2025-12-03 15:10:14 +01:00 committed by Joe DeCock
parent fcfbc9645e
commit 24d6b9c492
4 changed files with 77 additions and 6 deletions

View file

@ -235,7 +235,7 @@ public static class IdentityServerConstants
/// <summary>
/// The hash of the inline script used on the check session endpoint.
/// </summary>
public const string CheckSessionScript = "sha256-fa5rxHhZ799izGRP38+h4ud5QXNT0SFaFlh4eqDumBI=";
public const string CheckSessionScript = "sha256-4Hj97GNFvt0k8A6DbSr2hoRb/RJmCCakAgE+4zuVeHs=";
}
public static class ProtocolRoutePaths

View file

@ -2,6 +2,7 @@
// See LICENSE in the project root for license information.
using System.Text.RegularExpressions;
using Duende.IdentityModel;
using Duende.IdentityServer;
using Duende.IdentityServer.Configuration;
@ -226,6 +227,32 @@ public class AuthorizeResultTests
html.ShouldContain("<input type='hidden' name='state' value='state' />");
}
[Fact]
public async Task csp_hash_should_match_inline_script()
{
_response.Request = new ValidatedAuthorizeRequest
{
ClientId = "client",
ResponseMode = OidcConstants.ResponseModes.FormPost,
RedirectUri = "http://client/callback",
State = "state"
};
await _subject.WriteHttpResponse(new AuthorizeResult(_response), _context);
_context.Response.StatusCode.ShouldBe(200);
_context.Response.ContentType.ShouldStartWith("text/html");
_context.Response.Body.Seek(0, SeekOrigin.Begin);
using var rdr = new StreamReader(_context.Response.Body);
var html = await rdr.ReadToEndAsync();
var match = Regex.Match(html, "<script>(.*?)</script>", RegexOptions.Singleline | RegexOptions.IgnoreCase);
match.Success.ShouldBeTrue();
var scriptSha256 = "sha256-" + match.Groups[1].Value.ToSha256();
IdentityServerConstants.ContentSecurityPolicyHashes.AuthorizeScript.ShouldContain(scriptSha256);
}
[Fact]
public async Task form_post_mode_should_add_unsafe_inline_for_csp_level_1()
{

View file

@ -2,6 +2,8 @@
// See LICENSE in the project root for license information.
using System.Text.RegularExpressions;
using Duende.IdentityModel;
using Duende.IdentityServer;
using Duende.IdentityServer.Configuration;
using Duende.IdentityServer.Endpoints.Results;
@ -46,6 +48,24 @@ public class CheckSessionResultTests
html.ShouldContain("<script id='cookie-name' type='application/json'>foobar</script>");
}
[Fact]
public async Task csp_hash_should_match_inline_script()
{
await _subject.WriteHttpResponse(new CheckSessionResult(), _context);
_context.Response.StatusCode.ShouldBe(200);
_context.Response.ContentType.ShouldStartWith("text/html");
_context.Response.Body.Seek(0, SeekOrigin.Begin);
using var rdr = new StreamReader(_context.Response.Body);
var html = await rdr.ReadToEndAsync();
var match = Regex.Match(html, "<script>(.*?)</script>", RegexOptions.Singleline | RegexOptions.IgnoreCase);
match.Success.ShouldBeTrue();
var scriptSha256 = "sha256-" + match.Groups[1].Value.ToSha256();
IdentityServerConstants.ContentSecurityPolicyHashes.CheckSessionScript.ShouldContain(scriptSha256);
}
[Fact]
public async Task form_post_mode_should_add_unsafe_inline_for_csp_level_1()
{

View file

@ -2,6 +2,9 @@
// See LICENSE in the project root for license information.
using System.Text.RegularExpressions;
using Duende.IdentityModel;
using Duende.IdentityServer;
using Duende.IdentityServer.Configuration;
using Duende.IdentityServer.Endpoints.Results;
using Duende.IdentityServer.Models;
@ -52,10 +55,10 @@ public class EndSessionCallbackResultTests
_context.Response.Headers.CacheControl.First().ShouldContain("no-cache");
_context.Response.Headers.CacheControl.First().ShouldContain("max-age=0");
_context.Response.Headers.ContentSecurityPolicy.First().ShouldContain("default-src 'none';");
_context.Response.Headers.ContentSecurityPolicy.First().ShouldContain("style-src 'sha256-e6FQZewefmod2S/5T11pTXjzE2vn3/8GRwWOs917YE4=';");
_context.Response.Headers.ContentSecurityPolicy.First().ShouldContain($"style-src '{IdentityServerConstants.ContentSecurityPolicyHashes.EndSessionStyle}';");
_context.Response.Headers.ContentSecurityPolicy.First().ShouldContain("frame-src http://foo.com http://bar.com");
_context.Response.Headers["X-Content-Security-Policy"].First().ShouldContain("default-src 'none';");
_context.Response.Headers["X-Content-Security-Policy"].First().ShouldContain("style-src 'sha256-e6FQZewefmod2S/5T11pTXjzE2vn3/8GRwWOs917YE4=';");
_context.Response.Headers["X-Content-Security-Policy"].First().ShouldContain($"style-src '{IdentityServerConstants.ContentSecurityPolicyHashes.EndSessionStyle}';");
_context.Response.Headers["X-Content-Security-Policy"].First().ShouldContain("frame-src http://foo.com http://bar.com");
_context.Response.Body.Seek(0, SeekOrigin.Begin);
using var rdr = new StreamReader(_context.Response.Body);
@ -64,6 +67,27 @@ public class EndSessionCallbackResultTests
html.ShouldContain("<iframe loading='eager' allow='' src='http://bar.com'></iframe>");
}
[Fact]
public async Task csp_hash_should_match_inline_style()
{
_result.IsError = false;
_result.FrontChannelLogoutUrls = new string[] { "http://foo.com", "http://bar.com" };
await _subject.WriteHttpResponse(new EndSessionCallbackResult(_result), _context);
_context.Response.StatusCode.ShouldBe(200);
_context.Response.ContentType.ShouldStartWith("text/html");
_context.Response.Body.Seek(0, SeekOrigin.Begin);
using var rdr = new StreamReader(_context.Response.Body);
var html = await rdr.ReadToEndAsync();
var match = Regex.Match(html, "<style>(.*?)</style>", RegexOptions.Singleline | RegexOptions.IgnoreCase);
match.Success.ShouldBeTrue();
var styleSha256 = "sha256-" + match.Groups[1].Value.ToSha256();
IdentityServerConstants.ContentSecurityPolicyHashes.EndSessionStyle.ShouldContain(styleSha256);
}
[Fact]
public async Task fsuccess_should_add_unsafe_inline_for_csp_level_1()
{
@ -73,8 +97,8 @@ public class EndSessionCallbackResultTests
await _subject.WriteHttpResponse(new EndSessionCallbackResult(_result), _context);
_context.Response.Headers.ContentSecurityPolicy.First().ShouldContain("style-src 'unsafe-inline' 'sha256-e6FQZewefmod2S/5T11pTXjzE2vn3/8GRwWOs917YE4='");
_context.Response.Headers["X-Content-Security-Policy"].First().ShouldContain("style-src 'unsafe-inline' 'sha256-e6FQZewefmod2S/5T11pTXjzE2vn3/8GRwWOs917YE4='");
_context.Response.Headers.ContentSecurityPolicy.First().ShouldContain($"style-src 'unsafe-inline' '{IdentityServerConstants.ContentSecurityPolicyHashes.EndSessionStyle}'");
_context.Response.Headers["X-Content-Security-Policy"].First().ShouldContain($"style-src 'unsafe-inline' '{IdentityServerConstants.ContentSecurityPolicyHashes.EndSessionStyle}'");
}
[Fact]
@ -86,7 +110,7 @@ public class EndSessionCallbackResultTests
await _subject.WriteHttpResponse(new EndSessionCallbackResult(_result), _context);
_context.Response.Headers.ContentSecurityPolicy.First().ShouldContain("style-src 'sha256-e6FQZewefmod2S/5T11pTXjzE2vn3/8GRwWOs917YE4='");
_context.Response.Headers.ContentSecurityPolicy.First().ShouldContain($"style-src '{IdentityServerConstants.ContentSecurityPolicyHashes.EndSessionStyle}'");
_context.Response.Headers["X-Content-Security-Policy"].ShouldBeEmpty();
}
}