From 1a63d49182053c1f770f398315da25287f692573 Mon Sep 17 00:00:00 2001 From: James Britton Date: Thu, 9 Jul 2026 14:53:01 +0100 Subject: [PATCH] fix: issue 31, bug causing an error to be thrown if any registered ISigningCredentialStore implementations return null --- .../Default/DefaultKeyMaterialService.cs | 8 +- .../Default/DefaultKeyMaterialServiceTests.cs | 156 ++++++++++++++++++ 2 files changed, 163 insertions(+), 1 deletion(-) create mode 100644 src/Open.IdentityServer/test/Open.IdentityServer.UnitTests/Services/Default/DefaultKeyMaterialServiceTests.cs diff --git a/src/Open.IdentityServer/src/Services/Default/DefaultKeyMaterialService.cs b/src/Open.IdentityServer/src/Services/Default/DefaultKeyMaterialService.cs index 461396f8a..8afa349e1 100644 --- a/src/Open.IdentityServer/src/Services/Default/DefaultKeyMaterialService.cs +++ b/src/Open.IdentityServer/src/Services/Default/DefaultKeyMaterialService.cs @@ -1,4 +1,5 @@ // Copyright (c) Brock Allen & Dominick Baier. All rights reserved. +// Modified by Rock Solid Knowledge Ltd. Copyright in modifications 2026, Rock Solid Knowledge Ltd. // Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. @@ -62,7 +63,12 @@ public async Task> GetAllSigningCredentialsAsync foreach (var store in _signingCredentialStores) { - credentials.Add(await store.GetSigningCredentialsAsync()); + var credential = await store.GetSigningCredentialsAsync(); + + if (credential != null) + { + credentials.Add(credential); + } } return credentials; diff --git a/src/Open.IdentityServer/test/Open.IdentityServer.UnitTests/Services/Default/DefaultKeyMaterialServiceTests.cs b/src/Open.IdentityServer/test/Open.IdentityServer.UnitTests/Services/Default/DefaultKeyMaterialServiceTests.cs new file mode 100644 index 000000000..f4bb674e5 --- /dev/null +++ b/src/Open.IdentityServer/test/Open.IdentityServer.UnitTests/Services/Default/DefaultKeyMaterialServiceTests.cs @@ -0,0 +1,156 @@ +// Copyright (c) 2026, Rock Solid Knowledge Ltd +// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. + +using System.Collections.Generic; +using System.Security.Cryptography; +using System.Threading.Tasks; +using AwesomeAssertions; +using Microsoft.IdentityModel.Tokens; +using Open.IdentityServer.Models; +using Open.IdentityServer.Services; +using Open.IdentityServer.Stores; +using Xunit; + +namespace IdentityServer.UnitTests.Services.Default; + +public class TestSigningCredentialStore(SigningCredentials signingCredentials): ISigningCredentialStore { + public Task GetSigningCredentialsAsync() => Task.FromResult(signingCredentials); +} + +public class TestValidationKeysStore(IEnumerable securityKeyInfos): IValidationKeysStore { + public Task> GetValidationKeysAsync() => Task.FromResult(securityKeyInfos); +} + +public class DefaultKeyMaterialServiceTests +{ + private IEnumerable signingCredentialStores = + [ + new TestSigningCredentialStore(fakeSigningCredentials1), + new TestSigningCredentialStore(fakeSigningCredentials2), + new TestSigningCredentialStore(fakeSigningCredentials3), + ]; + private IEnumerable validationKeysStores = + [ + new TestValidationKeysStore([fakeSecurityKey1, fakeSecurityKey4, fakeSecurityKey5]), + new TestValidationKeysStore([fakeSecurityKey2, fakeSecurityKey6, fakeSecurityKey7]), + new TestValidationKeysStore([fakeSecurityKey3, fakeSecurityKey8, fakeSecurityKey9]), + ]; + + // Test SigningCredentials + private static SigningCredentials fakeSigningCredentials1 = new(new RsaSecurityKey(RSA.Create(1024)), "RS256"); + private static SigningCredentials fakeSigningCredentials2 = new(new RsaSecurityKey(RSA.Create(1024)), "PS384"); + private static SigningCredentials fakeSigningCredentials3 = new(new ECDsaSecurityKey(ECDsa.Create(ECCurve.NamedCurves.nistP521)), "ES512"); + + // Test SigningCredentials as SecurityKeyInfo + private static SecurityKeyInfo fakeSecurityKey1 = new() { Key = fakeSigningCredentials1.Key , SigningAlgorithm = fakeSigningCredentials1.Algorithm }; + private static SecurityKeyInfo fakeSecurityKey2 = new() { Key = fakeSigningCredentials2.Key , SigningAlgorithm = fakeSigningCredentials2.Algorithm }; + private static SecurityKeyInfo fakeSecurityKey3 = new() { Key = fakeSigningCredentials3.Key , SigningAlgorithm = fakeSigningCredentials3.Algorithm }; + + // Other Test SecurityKeyInfo + private static SecurityKeyInfo fakeSecurityKey4 = new() { Key = new RsaSecurityKey(RSA.Create(1024)) , SigningAlgorithm = "RS384" }; + private static SecurityKeyInfo fakeSecurityKey5 = new() { Key = new RsaSecurityKey(RSA.Create(1024)) , SigningAlgorithm = "RS512" }; + private static SecurityKeyInfo fakeSecurityKey6 = new() { Key = new RsaSecurityKey(RSA.Create(1024)) , SigningAlgorithm = "PS256" }; + private static SecurityKeyInfo fakeSecurityKey7 = new() { Key = new RsaSecurityKey(RSA.Create(1024)) , SigningAlgorithm = "PS512" }; + private static SecurityKeyInfo fakeSecurityKey8 = new() { Key = new ECDsaSecurityKey(ECDsa.Create(ECCurve.NamedCurves.nistP256)) , SigningAlgorithm = "ES256" }; + private static SecurityKeyInfo fakeSecurityKey9 = new() { Key = new ECDsaSecurityKey(ECDsa.Create(ECCurve.NamedCurves.nistP256)) , SigningAlgorithm = "ES384" }; + + private DefaultKeyMaterialService CreateSut() => new(validationKeysStores, signingCredentialStores); + + [Fact] + public async Task GetSigningCredentialsAsync_WhenNoSigningCredentialStores_ShouldReturnNull() + { + signingCredentialStores = []; + + var sut = CreateSut(); + + var actual = await sut.GetSigningCredentialsAsync(); + + actual.Should().BeNull(); + } + + [Fact] + public async Task GetSigningCredentialsAsync_WhenNoAllowedAlgorithmsSpecified_ShouldReturnCredentialsFromFirstStore() + { + var sut = CreateSut(); + + var actual = await sut.GetSigningCredentialsAsync(); + + actual.Should().BeEquivalentTo(fakeSigningCredentials1); + } + + [Fact] + public async Task GetSigningCredentialsAsync_WhenAllowedAlgorithmsSpecified_ShouldReturnFirstCredentialsThatMatchesAllowedValues() + { + var sut = CreateSut(); + + var actual = await sut.GetSigningCredentialsAsync(["PS384", "ES512"]); + + actual.Should().BeEquivalentTo(fakeSigningCredentials2); + } + + [Fact] + public async Task GetAllSigningCredentialsAsync_WhenNoSigningCredentialStores_ShouldEmptyResultSet() + { + signingCredentialStores = []; + + var sut = CreateSut(); + + var actual = await sut.GetAllSigningCredentialsAsync(); + + actual.Should().BeEmpty(); + } + + [Fact] + public async Task GetAllSigningCredentialsAsync_WhenSigningCredentialStoreReturnsNull_ShouldSkipThatStore() + { + signingCredentialStores = [ + new TestSigningCredentialStore(fakeSigningCredentials1), + new TestSigningCredentialStore(null), + new TestSigningCredentialStore(fakeSigningCredentials3), + ]; + + var sut = CreateSut(); + + var actual = await sut.GetAllSigningCredentialsAsync(); + + actual.Should().BeEquivalentTo([fakeSigningCredentials1, fakeSigningCredentials3]); + } + + [Fact] + public async Task GetAllSigningCredentialsAsync_ShouldReturnResultsFromAllRegisteredStores() + { + var sut = CreateSut(); + + var actual = await sut.GetAllSigningCredentialsAsync(); + + actual.Should().BeEquivalentTo([ + fakeSigningCredentials1, fakeSigningCredentials2, fakeSigningCredentials3, + ]); + } + + [Fact] + public async Task GetValidationKeysAsync_WhenNoSigningCredentialStores_ShouldEmptyResultSet() + { + validationKeysStores = []; + + var sut = CreateSut(); + + var actual = await sut.GetValidationKeysAsync(); + + actual.Should().BeEmpty(); + } + + [Fact] + public async Task GetValidationKeysAsync_ShouldReturnResultsFromAllRegisteredStores() + { + var sut = CreateSut(); + + var actual = await sut.GetValidationKeysAsync(); + + actual.Should().BeEquivalentTo([ + fakeSecurityKey1, fakeSecurityKey2, fakeSecurityKey3, + fakeSecurityKey4, fakeSecurityKey5, fakeSecurityKey6, + fakeSecurityKey7, fakeSecurityKey8, fakeSecurityKey9, + ]); + } +} \ No newline at end of file