diff --git a/Directory.Packages.props b/Directory.Packages.props
index 0da8b6d25..102a04161 100644
--- a/Directory.Packages.props
+++ b/Directory.Packages.props
@@ -9,7 +9,7 @@
-
+
@@ -108,23 +108,23 @@
-
+
-
+
-
+
-
+
@@ -134,14 +134,14 @@
-
-
-
-
-
-
+
+
+
+
+
+
diff --git a/identity-server/src/IdentityServer/Configuration/DependencyInjection/Options/DPoPOptions.cs b/identity-server/src/IdentityServer/Configuration/DependencyInjection/Options/DPoPOptions.cs
index 0a79904d1..73581047f 100644
--- a/identity-server/src/IdentityServer/Configuration/DependencyInjection/Options/DPoPOptions.cs
+++ b/identity-server/src/IdentityServer/Configuration/DependencyInjection/Options/DPoPOptions.cs
@@ -27,6 +27,22 @@ public class DPoPOptions
/// The allowed signing algorithms used in validating DPoP proof tokens. Defaults to:
/// RSA256, RSA384, RSA512, PS256, PS384, PS512, ES256, ES384, ES512.
///
+ ///
+ ///
+ ///
+ /// Specifies the allowed signature algorithms for DPoP proof tokens. The "alg" headers of proofs
+ /// are validated against this collection, and the dpop_signing_alg_values_supported discovery property is populated
+ /// with these values.
+ ///
+ ///
+ /// Defaults to [RS256, RS384, RS512, PS256, PS384, PS512, ES256, ES384, ES512], which allows the RSA, Probabilistic
+ /// RSA, or ECDSA signing algorithms with 256, 384, or 512-bit SHA hashing.
+ ///
+ ///
+ /// If set to an empty collection, all algorithms (including symmetric algorithms) are allowed, and the
+ /// dpop_signing_alg_values_supported will not be set. Explicitly listing the expected values is recommended.
+ ///
+ ///
public ICollection SupportedDPoPSigningAlgorithms { get; set; } =
[
SecurityAlgorithms.RsaSha256,
diff --git a/identity-server/src/IdentityServer/Configuration/DependencyInjection/Options/IdentityServerOptions.cs b/identity-server/src/IdentityServer/Configuration/DependencyInjection/Options/IdentityServerOptions.cs
index 9801927a1..c09d463d6 100644
--- a/identity-server/src/IdentityServer/Configuration/DependencyInjection/Options/IdentityServerOptions.cs
+++ b/identity-server/src/IdentityServer/Configuration/DependencyInjection/Options/IdentityServerOptions.cs
@@ -6,6 +6,7 @@
using Duende.IdentityServer.Stores.Serialization;
using Duende.IdentityServer.Validation;
+using Microsoft.IdentityModel.Tokens;
namespace Duende.IdentityServer.Configuration;
@@ -205,27 +206,86 @@ public class IdentityServerOptions
public PushedAuthorizationOptions PushedAuthorization { get; set; } = new PushedAuthorizationOptions();
///
- /// The allowed clock skew for JWT lifetime validation. Except for DPoP proofs,
- /// all JWTs that have their lifetime validated use this setting to control the
- /// clock skew of lifetime validation. This includes JWT access tokens passed
- /// to the user info, introspection, and local api endpoints, client
- /// authentication JWTs used in private_key_jwt authentication, JWT secured
- /// authorization requests (JAR), and custom usage of the
- /// , such as in a token exchange implementation.
+ /// The allowed clock skew for JWT lifetime validation. This setting controls the clock skew of lifetime validation
+ /// for all JWTs except DPoP proofs, including
+ ///
+ /// JWT access tokens passed to the user info, introspection, and local api endpoints
+ /// Authentication JWTs used in private_key_jwt authentication
+ /// JWT secured authorization requests (JAR request objects)
+ /// Custom usage of the , such as in a token exchange implementation.
+ ///
+ ///
/// Defaults to five minutes.
///
public TimeSpan JwtValidationClockSkew { get; set; } = TimeSpan.FromMinutes(5);
///
- /// The allowed algorithms for JWT validation. Except for DPoP proofs, all JWTs validated by IdentityServer use this
- /// setting to control the allowed signing algorithms. This includes JWT
- /// access tokens passed to the user info, introspection, and local api endpoints,
- /// client authentication JWTs used in private_key_jwt authentication, JWT secured
- /// authorization requests (JAR), and custom usage of the ,
- /// such as in a token exchange implementation. Defaults to an empty collection which
- /// allows all algorithms.
- ///
- public ICollection AllowedJwtAlgorithms { get; set; } = [];
+ ///
+ /// Specifies the allowed signature algorithms for JWT secured authorization requests (JAR). The "alg" header of JAR
+ /// request objects is validated against this collection, and the
+ /// request_object_signing_alg_values_supported discovery property is populated with these values.
+ ///
+ ///
+ /// Defaults to [RS256, RS384, RS512, PS256, PS384, PS512, ES256, ES384, ES512, HS256, HS384, HS512], which allows
+ /// the RSA, Probabilistic RSA, ECDSA, or HMAC signing algorithms with 256, 384, or 512-bit SHA hashing.
+ ///
+ ///
+ /// If set to an empty collection, all algorithms are allowed, but the request_object_signing_alg_values_supported
+ /// will not be set. Explicitly listing the expected values is recommended.
+ ///
+ ///
+ public ICollection SupportedRequestObjectSigningAlgorithms { get; set; } =
+ [
+ SecurityAlgorithms.RsaSha256,
+ SecurityAlgorithms.RsaSha384,
+ SecurityAlgorithms.RsaSha512,
+
+ SecurityAlgorithms.RsaSsaPssSha256,
+ SecurityAlgorithms.RsaSsaPssSha384,
+ SecurityAlgorithms.RsaSsaPssSha512,
+
+ SecurityAlgorithms.EcdsaSha256,
+ SecurityAlgorithms.EcdsaSha384,
+ SecurityAlgorithms.EcdsaSha512,
+
+ SecurityAlgorithms.HmacSha256,
+ SecurityAlgorithms.HmacSha384,
+ SecurityAlgorithms.HmacSha512
+ ];
+
+ ///
+ ///
+ /// Specifies the allowed signature algorithms for client authentication using client assertions (the
+ /// private_key_jwt parameter). The "alg" header of client assertions is validated against this collection, and the
+ /// token_endpoint_auth_signing_alg_values_supported discovery property is populated with these values.
+ ///
+ ///
+ /// Defaults to [RS256, RS384, RS512, PS256, PS384, PS512, ES256, ES384, ES512, HS256, HS384, HS512], which allows
+ /// the RSA, Probabilistic RSA, ECDSA, or HMAC signing algorithms with 256, 384, or 512-bit SHA hashing.
+ ///
+ ///
+ /// If set to an empty collection, all algorithms are allowed, but the
+ /// token_endpoint_auth_signing_alg_values_supported will not be set. Explicitly listing the expected values is
+ /// recommended.
+ ///
+ ///
+ public ICollection SupportedClientAssertionSigningAlgorithms { get; set; } = [
+ SecurityAlgorithms.RsaSha256,
+ SecurityAlgorithms.RsaSha384,
+ SecurityAlgorithms.RsaSha512,
+
+ SecurityAlgorithms.RsaSsaPssSha256,
+ SecurityAlgorithms.RsaSsaPssSha384,
+ SecurityAlgorithms.RsaSsaPssSha512,
+
+ SecurityAlgorithms.EcdsaSha256,
+ SecurityAlgorithms.EcdsaSha384,
+ SecurityAlgorithms.EcdsaSha512,
+
+ SecurityAlgorithms.HmacSha256,
+ SecurityAlgorithms.HmacSha384,
+ SecurityAlgorithms.HmacSha512
+ ];
///
/// Gets or sets the options for enabling and configuring preview features in the server.
diff --git a/identity-server/src/IdentityServer/ResponseHandling/Default/DiscoveryResponseGenerator.cs b/identity-server/src/IdentityServer/ResponseHandling/Default/DiscoveryResponseGenerator.cs
index f959b60bf..377b5d371 100644
--- a/identity-server/src/IdentityServer/ResponseHandling/Default/DiscoveryResponseGenerator.cs
+++ b/identity-server/src/IdentityServer/ResponseHandling/Default/DiscoveryResponseGenerator.cs
@@ -326,8 +326,14 @@ where scope.ShowInDiscoveryDocument
types.Add(OidcConstants.EndpointAuthenticationMethods.TlsClientAuth);
types.Add(OidcConstants.EndpointAuthenticationMethods.SelfSignedTlsClientAuth);
}
-
entries.Add(OidcConstants.Discovery.TokenEndpointAuthenticationMethodsSupported, types);
+
+ if (types.Contains(OidcConstants.EndpointAuthenticationMethods.PrivateKeyJwt) &&
+ !IEnumerableExtensions.IsNullOrEmpty(Options.SupportedClientAssertionSigningAlgorithms))
+ {
+ entries.Add(OidcConstants.Discovery.TokenEndpointAuthSigningAlgorithmsSupported,
+ Options.SupportedClientAssertionSigningAlgorithms);
+ }
}
var signingCredentials = await Keys.GetAllSigningCredentialsAsync();
@@ -344,24 +350,11 @@ where scope.ShowInDiscoveryDocument
{
entries.Add(OidcConstants.Discovery.RequestParameterSupported, true);
- entries.Add(OidcConstants.Discovery.RequestObjectSigningAlgorithmsSupported, new[]
+ if (!IEnumerableExtensions.IsNullOrEmpty(Options.SupportedRequestObjectSigningAlgorithms))
{
- SecurityAlgorithms.RsaSha256,
- SecurityAlgorithms.RsaSha384,
- SecurityAlgorithms.RsaSha512,
-
- SecurityAlgorithms.RsaSsaPssSha256,
- SecurityAlgorithms.RsaSsaPssSha384,
- SecurityAlgorithms.RsaSsaPssSha512,
-
- SecurityAlgorithms.EcdsaSha256,
- SecurityAlgorithms.EcdsaSha384,
- SecurityAlgorithms.EcdsaSha512,
-
- SecurityAlgorithms.HmacSha256,
- SecurityAlgorithms.HmacSha384,
- SecurityAlgorithms.HmacSha512
- });
+ entries.Add(OidcConstants.Discovery.RequestObjectSigningAlgorithmsSupported,
+ Options.SupportedRequestObjectSigningAlgorithms);
+ }
if (Options.Endpoints.EnableJwtRequestUri)
{
@@ -388,7 +381,8 @@ where scope.ShowInDiscoveryDocument
entries.Add(OidcConstants.Discovery.BackchannelUserCodeParameterSupported, true);
}
- if (Options.Endpoints.EnableTokenEndpoint)
+ if (Options.Endpoints.EnableTokenEndpoint &&
+ !IEnumerableExtensions.IsNullOrEmpty(Options.DPoP.SupportedDPoPSigningAlgorithms))
{
entries.Add(OidcConstants.Discovery.DPoPSigningAlgorithmsSupported, Options.DPoP.SupportedDPoPSigningAlgorithms);
}
diff --git a/identity-server/src/IdentityServer/Validation/Default/JwtRequestValidator.cs b/identity-server/src/IdentityServer/Validation/Default/JwtRequestValidator.cs
index d823ccf96..8c4b1597d 100644
--- a/identity-server/src/IdentityServer/Validation/Default/JwtRequestValidator.cs
+++ b/identity-server/src/IdentityServer/Validation/Default/JwtRequestValidator.cs
@@ -173,7 +173,7 @@ protected virtual async Task ValidateJwtAsync(JwtRequestValidation
RequireExpirationTime = true,
ClockSkew = Options.JwtValidationClockSkew,
- ValidAlgorithms = Options.AllowedJwtAlgorithms
+ ValidAlgorithms = Options.SupportedRequestObjectSigningAlgorithms
};
var strictJarValidation = context.StrictJarValidation.HasValue ? context.StrictJarValidation.Value : Options.StrictJarValidation;
diff --git a/identity-server/src/IdentityServer/Validation/Default/PrivateKeyJwtSecretValidator.cs b/identity-server/src/IdentityServer/Validation/Default/PrivateKeyJwtSecretValidator.cs
index 3d3eba5b5..029fc1079 100644
--- a/identity-server/src/IdentityServer/Validation/Default/PrivateKeyJwtSecretValidator.cs
+++ b/identity-server/src/IdentityServer/Validation/Default/PrivateKeyJwtSecretValidator.cs
@@ -120,7 +120,7 @@ public async Task ValidateAsync(IEnumerable secr
RequireExpirationTime = true,
ClockSkew = _options.JwtValidationClockSkew,
- ValidAlgorithms = _options.AllowedJwtAlgorithms
+ ValidAlgorithms = _options.SupportedClientAssertionSigningAlgorithms
};
var issuer = await _issuerNameService.GetCurrentAsync();
@@ -128,7 +128,7 @@ public async Task ValidateAsync(IEnumerable secr
if (enforceStrictAud)
{
// New strict audience validation requires that the audience be the issuer identifier, disallows multiple
- // audiences in an array, and even disallows wrapping even a single audience in an array
+ // audiences in an array, and even disallows wrapping even a single audience in an array
tokenValidationParameters.AudienceValidator = (audiences, token, parameters) =>
{
// There isn't a particularly nice way to distinguish between a claim that is a single string wrapped in
@@ -205,7 +205,7 @@ public async Task ValidateAsync(IEnumerable secr
return success;
}
- // AudiencesMatch and AudiencesMatchIgnoringTrailingSlash are based on code from
+ // AudiencesMatch and AudiencesMatchIgnoringTrailingSlash are based on code from
// https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/blob/bef98ca10ae55603ce6d37dfb7cd5af27791527c/src/Microsoft.IdentityModel.Tokens/Validators.cs#L158-L193
private bool AudiencesMatch(string tokenAudience, string validAudience)
{
diff --git a/identity-server/src/IdentityServer/Validation/Default/TokenValidator.cs b/identity-server/src/IdentityServer/Validation/Default/TokenValidator.cs
index d4ef49681..c88f09677 100644
--- a/identity-server/src/IdentityServer/Validation/Default/TokenValidator.cs
+++ b/identity-server/src/IdentityServer/Validation/Default/TokenValidator.cs
@@ -279,8 +279,7 @@ private async Task ValidateJwtAsync(string jwtString,
ValidIssuer = await _issuerNameService.GetCurrentAsync(),
IssuerSigningKeys = validationKeys.Select(k => k.Key),
ValidateLifetime = validateLifetime,
- ClockSkew = _options.JwtValidationClockSkew,
- ValidAlgorithms = _options.AllowedJwtAlgorithms
+ ClockSkew = _options.JwtValidationClockSkew
};
if (audience.IsPresent())
diff --git a/identity-server/src/IdentityServer/Validation/Models/ValidatedRequest.cs b/identity-server/src/IdentityServer/Validation/Models/ValidatedRequest.cs
index b48d00739..d69227ad0 100644
--- a/identity-server/src/IdentityServer/Validation/Models/ValidatedRequest.cs
+++ b/identity-server/src/IdentityServer/Validation/Models/ValidatedRequest.cs
@@ -13,7 +13,7 @@
namespace Duende.IdentityServer.Validation;
///
-/// Base class for a validate authorize or token request
+/// Base class for a validated authorize or token request
///
public class ValidatedRequest
{
diff --git a/identity-server/test/IdentityServer.IntegrationTests/Endpoints/Authorize/JwtRequestAuthorizeTests.cs b/identity-server/test/IdentityServer.IntegrationTests/Endpoints/Authorize/JwtRequestAuthorizeTests.cs
index 2533cb0a0..e815ee1d5 100644
--- a/identity-server/test/IdentityServer.IntegrationTests/Endpoints/Authorize/JwtRequestAuthorizeTests.cs
+++ b/identity-server/test/IdentityServer.IntegrationTests/Endpoints/Authorize/JwtRequestAuthorizeTests.cs
@@ -913,7 +913,7 @@ public async Task authorize_should_reject_jwt_request_if_client_id_does_not_matc
[Trait("Category", Category)]
public async Task authorize_should_reject_jwt_request_if_signed_by_algorithm_not_allowed_by_configuration()
{
- _mockPipeline.Options.AllowedJwtAlgorithms = ["ES256"];
+ _mockPipeline.Options.SupportedRequestObjectSigningAlgorithms = ["ES256"];
var requestJwt = CreateRequestJwt(
issuer: _client.ClientId,
audience: IdentityServerPipeline.BaseUrl,
diff --git a/identity-server/test/IdentityServer.IntegrationTests/Endpoints/Discovery/DiscoveryEndpointTests.cs b/identity-server/test/IdentityServer.IntegrationTests/Endpoints/Discovery/DiscoveryEndpointTests.cs
index 0f0b42dc6..a37aba995 100644
--- a/identity-server/test/IdentityServer.IntegrationTests/Endpoints/Discovery/DiscoveryEndpointTests.cs
+++ b/identity-server/test/IdentityServer.IntegrationTests/Endpoints/Discovery/DiscoveryEndpointTests.cs
@@ -2,7 +2,6 @@
// See LICENSE in the project root for license information.
using System.Text.Json;
-using Duende.IdentityModel;
using Duende.IdentityModel.Client;
using Duende.IdentityServer;
using Duende.IdentityServer.Configuration;
@@ -53,7 +52,7 @@ public async Task when_lower_case_issuer_option_disabled_issuer_uri_should_be_pr
[Fact]
[Trait("Category", Category)]
- public async Task Algorithms_supported_should_match_signing_key()
+ public async Task IdToken_signing_algorithms_supported_should_match_signing_key()
{
var key = CryptoHelper.CreateECDsaSecurityKey(JsonWebKeyECTypes.P256);
var expectedAlgorithm = SecurityAlgorithms.EcdsaSha256;
@@ -65,20 +64,20 @@ public async Task Algorithms_supported_should_match_signing_key()
services.AddIdentityServerBuilder()
.AddSigningCredential(key, expectedAlgorithm);
};
- pipeline.Initialize("/ROOT");
+ pipeline.Initialize();
- var result = await pipeline.BackChannelClient.GetAsync("https://server/root/.well-known/openid-configuration");
+ var result = await pipeline.BackChannelClient.GetDiscoveryDocumentAsync("https://server/.well-known/openid-configuration");
- var json = await result.Content.ReadAsStringAsync();
- var data = JsonSerializer.Deserialize>(json);
- var algorithmsSupported = data["id_token_signing_alg_values_supported"].EnumerateArray()
- .Select(x => x.GetString()).ToList();
+ var algorithmsSupported = result.TryGetStringArray("id_token_signing_alg_values_supported");
- algorithmsSupported.Count.ShouldBe(2);
+ algorithmsSupported.Count().ShouldBe(2);
algorithmsSupported.ShouldContain(SecurityAlgorithms.RsaSha256);
algorithmsSupported.ShouldContain(SecurityAlgorithms.EcdsaSha256);
}
+
+
+
[Fact]
[Trait("Category", Category)]
public async Task Jwks_entries_should_countain_crv()
@@ -333,24 +332,4 @@ public async Task par_is_included_in_mtls_aliases()
var result = await pipeline.BackChannelClient.GetDiscoveryDocumentAsync("https://server/.well-known/openid-configuration");
result.MtlsEndpointAliases.PushedAuthorizationRequestEndpoint.ShouldNotBeNull();
}
-
- [Fact]
- [Trait("Category", Category)]
- public async Task dpop_signing_algorithms_supported_respects_configuration()
- {
- var pipeline = new IdentityServerPipeline();
- pipeline.Initialize();
-
- var supportedAlgorithms = new List
- {
- SecurityAlgorithms.RsaSha256,
- SecurityAlgorithms.EcdsaSha256
- };
- pipeline.Options.DPoP.SupportedDPoPSigningAlgorithms = supportedAlgorithms;
-
- var result = await pipeline.BackChannelClient.GetDiscoveryDocumentAsync("https://server/.well-known/openid-configuration");
-
- var supportedAlgorithmsFromResponse = result.TryGetStringArray(OidcConstants.Discovery.DPoPSigningAlgorithmsSupported);
- supportedAlgorithmsFromResponse.ShouldBe(supportedAlgorithms);
- }
}
diff --git a/identity-server/test/IdentityServer.IntegrationTests/Endpoints/Discovery/DiscoveryEndpointTests_dpop_signing_algs_supported.cs b/identity-server/test/IdentityServer.IntegrationTests/Endpoints/Discovery/DiscoveryEndpointTests_dpop_signing_algs_supported.cs
new file mode 100644
index 000000000..7cc519c44
--- /dev/null
+++ b/identity-server/test/IdentityServer.IntegrationTests/Endpoints/Discovery/DiscoveryEndpointTests_dpop_signing_algs_supported.cs
@@ -0,0 +1,87 @@
+// Copyright (c) Duende Software. All rights reserved.
+// See LICENSE in the project root for license information.
+
+using System.Text.Json;
+using Duende.IdentityModel;
+using Duende.IdentityModel.Client;
+using IntegrationTests.Common;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.IdentityModel.Tokens;
+
+namespace IntegrationTests.Endpoints.Discovery;
+
+public class DiscoveryEndpointTests_dpop_signing_alg_values_supported
+{
+ private const string Category = "Discovery endpoint - dpop_signing_alg_values_supported";
+
+ [Fact]
+ [Trait("Category", Category)]
+ public async Task dpop_signing_alg_values_supported_should_match_configuration()
+ {
+ var pipeline = new IdentityServerPipeline();
+ pipeline.Initialize();
+
+ var supportedAlgorithms = new List
+ {
+ SecurityAlgorithms.RsaSha256,
+ SecurityAlgorithms.EcdsaSha256
+ };
+ pipeline.Options.DPoP.SupportedDPoPSigningAlgorithms = supportedAlgorithms;
+
+ var result =
+ await pipeline.BackChannelClient.GetDiscoveryDocumentAsync(
+ "https://server/.well-known/openid-configuration");
+
+ var supportedAlgorithmsFromResponse =
+ result.TryGetStringArray(OidcConstants.Discovery.DPoPSigningAlgorithmsSupported);
+ supportedAlgorithmsFromResponse.ShouldBe(supportedAlgorithms);
+ }
+
+ [Fact]
+ public async Task dpop_signing_alg_values_supported_should_default_to_rs_ps_es()
+ {
+ var pipeline = new IdentityServerPipeline();
+ pipeline.Initialize();
+
+ var result =
+ await pipeline.BackChannelClient.GetDiscoveryDocumentAsync(
+ "https://server/.well-known/openid-configuration");
+ var algorithmsSupported = result.TryGetStringArray("dpop_signing_alg_values_supported");
+
+ algorithmsSupported.Count().ShouldBe(9);
+ algorithmsSupported.ShouldContain(SecurityAlgorithms.RsaSha256);
+ algorithmsSupported.ShouldContain(SecurityAlgorithms.RsaSha384);
+ algorithmsSupported.ShouldContain(SecurityAlgorithms.RsaSha512);
+ algorithmsSupported.ShouldContain(SecurityAlgorithms.RsaSsaPssSha384);
+ algorithmsSupported.ShouldContain(SecurityAlgorithms.RsaSsaPssSha512);
+ algorithmsSupported.ShouldContain(SecurityAlgorithms.RsaSsaPssSha256);
+ algorithmsSupported.ShouldContain(SecurityAlgorithms.EcdsaSha256);
+ algorithmsSupported.ShouldContain(SecurityAlgorithms.EcdsaSha384);
+ algorithmsSupported.ShouldContain(SecurityAlgorithms.EcdsaSha512);
+ }
+
+ [Theory]
+ [MemberData(nameof(NullOrEmptySupportedAlgorithms))]
+ public async Task dpop_signing_alg_values_supported_should_not_be_present_if_option_is_null_or_empty(ICollection algorithms)
+ {
+ var pipeline = new IdentityServerPipeline();
+ pipeline.OnPostConfigureServices += svcs =>
+ svcs.AddIdentityServerBuilder().AddJwtBearerClientAuthentication();
+ pipeline.Initialize();
+ pipeline.Options.DPoP.SupportedDPoPSigningAlgorithms = algorithms;
+
+ var result = await pipeline.BackChannelClient
+ .GetAsync("https://server/.well-known/openid-configuration");
+ var json = await result.Content.ReadAsStringAsync();
+ var data = JsonSerializer.Deserialize>(json);
+
+ data.ShouldNotContainKey(OidcConstants.Discovery.DPoPSigningAlgorithmsSupported);
+ }
+
+ public static IEnumerable