Skip to content
24 changes: 12 additions & 12 deletions Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
<PackageVersion Include="Duende.AccessTokenManagement" Version="3.2.0" />
<PackageVersion Include="Duende.AccessTokenManagement.OpenIdConnect" Version="3.2.0" />
<PackageVersion Include="Duende.AspNetCore.Authentication.JwtBearer" Version="0.1.3" />
<PackageVersion Include="Duende.IdentityModel" Version="7.1.0-preview.1" />
<PackageVersion Include="Duende.IdentityModel" Version="7.1.0-preview.2" />
<PackageVersion Include="Duende.IdentityModel.OidcClient" Version="6.0.1" />
<PackageVersion Include="Duende.IdentityServer" Version="7.1.0" />
<PackageVersion Include="IdentityModel.AspNetCore.OAuth2Introspection" Version="6.2.0" />
Expand Down Expand Up @@ -108,23 +108,23 @@

<PackageVersion Include="PublicApiGenerator" Version="11.1.0" />
<PackageVersion Include="RichardSzalay.MockHttp" Version="7.0.0" />

<PackageVersion Include="Serilog" Version="4.2.0" />
<PackageVersion Include="Serilog.AspNetCore" Version="8.0.3" />
<PackageVersion Include="Serilog.Sinks.Console" Version="6.0.0" />
<PackageVersion Include="Serilog.Sinks.TextWriter" Version="3.0.0" />
<PackageVersion Include="Serilog.Sinks.XUnit" Version="3.0.19" />
<PackageVersion Include="Serilog.Extensions.Logging" Version="9.0.0" />

<PackageVersion Include="Shouldly" Version="4.2.1" />
<PackageVersion Include="SimpleExec" Version="12.0.0" />

<PackageVersion Include="System.IdentityModel.Tokens.Jwt" Condition="'$(TargetFramework)' == 'net8.0'" Version="7.1.2" />
<PackageVersion Include="System.IdentityModel.Tokens.Jwt" Condition="'$(TargetFramework)' == 'net9.0'" Version="8.0.1" />
<PackageVersion Include="System.Net.Http" Version="4.3.4" />
<PackageVersion Include="System.Text.Json" Version="8.0.5" />
<PackageVersion Include="System.Text.RegularExpressions" Version="4.3.1" />

<PackageVersion Include="xunit.core" Version="2.9.3" />
<PackageVersion Include="xunit.runner.visualstudio" Version="2.8.2" />
<PackageVersion Include="Xunit.SkippableFact" Version="1.5.23" />
Expand All @@ -134,14 +134,14 @@
<!-- Transitive Dependencies -->
<!-- These packages are all transitive dependencies that would
otherwise resolve to a version with a security vulnerability. In future, we
would like to update Microsoft.Data.SqlClient and
would like to update Microsoft.Data.SqlClient and
Microsoft.EntityFrameworkCore, and remove these explicit dependencies (assuming
that future versions of the intermediate dependencies that don't have this
problem exist someday). -->
<PackageVersion Include="Azure.Identity" Version="1.11.4" />
<PackageVersion Include="System.Formats.Asn1" Condition="'$(TargetFramework)' == 'net8.0'" Version="8.0.1" />
<PackageVersion Include="System.Formats.Asn1" Condition="'$(TargetFramework)' == 'net9.0'" Version="9.0.0" />
<PackageVersion Include="System.Drawing.Common" Version="6.0.0" />
<PackageVersion Include="Microsoft.Data.SqlClient" Version="5.2.2" />
</ItemGroup>
<PackageVersion Include="Azure.Identity" Version="1.11.4" />
<PackageVersion Include="System.Formats.Asn1" Condition="'$(TargetFramework)' == 'net8.0'" Version="8.0.1" />
<PackageVersion Include="System.Formats.Asn1" Condition="'$(TargetFramework)' == 'net9.0'" Version="9.0.0" />
<PackageVersion Include="System.Drawing.Common" Version="6.0.0" />
<PackageVersion Include="Microsoft.Data.SqlClient" Version="5.2.2" />
</ItemGroup>
</Project>
Original file line number Diff line number Diff line change
Expand Up @@ -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.
/// </summary>
///
/// <summary>
/// <para>
/// 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.
/// </para>
/// <para>
/// 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.
/// </para>
/// <para>
/// 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.
///</para>
/// </summary>
public ICollection<string> SupportedDPoPSigningAlgorithms { get; set; } =
[
SecurityAlgorithms.RsaSha256,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

using Duende.IdentityServer.Stores.Serialization;
using Duende.IdentityServer.Validation;
using Microsoft.IdentityModel.Tokens;

namespace Duende.IdentityServer.Configuration;

Expand Down Expand Up @@ -205,27 +206,86 @@ public class IdentityServerOptions
public PushedAuthorizationOptions PushedAuthorization { get; set; } = new PushedAuthorizationOptions();

/// <summary>
/// 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
/// <see cref="TokenValidator"/>, 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
/// <list type="bullet">
/// <item>JWT access tokens passed to the user info, introspection, and local api endpoints</item>
/// <item>Authentication JWTs used in private_key_jwt authentication</item>
/// <item> JWT secured authorization requests (JAR request objects)</item>
/// <item> Custom usage of the <see cref="TokenValidator"/>, such as in a token exchange implementation.</item>
/// </list>
///
/// Defaults to five minutes.
/// </summary>
public TimeSpan JwtValidationClockSkew { get; set; } = TimeSpan.FromMinutes(5);

/// <summary>
/// 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 <see cref="TokenValidator"/>,
/// such as in a token exchange implementation. Defaults to an empty collection which
/// allows all algorithms.
/// </summary>
public ICollection<string> AllowedJwtAlgorithms { get; set; } = [];
/// <para>
/// 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.
/// </para>
/// <para>
/// 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.
/// </para>
/// <para>
/// 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.
///</para>
/// </summary>
public ICollection<string> 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
];

/// <summary>
/// <para>
/// 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.
/// </para>
/// <para>
/// 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.
/// </para>
/// <para>
/// 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.
///</para>
/// </summary>
public ICollection<string> 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
];

/// <summary>
/// Gets or sets the options for enabling and configuring preview features in the server.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand All @@ -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)
{
Expand All @@ -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);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,7 @@ protected virtual async Task<JsonWebToken> ValidateJwtAsync(JwtRequestValidation
RequireExpirationTime = true,

ClockSkew = Options.JwtValidationClockSkew,
ValidAlgorithms = Options.AllowedJwtAlgorithms
ValidAlgorithms = Options.SupportedRequestObjectSigningAlgorithms
};

var strictJarValidation = context.StrictJarValidation.HasValue ? context.StrictJarValidation.Value : Options.StrictJarValidation;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -120,15 +120,15 @@ public async Task<SecretValidationResult> ValidateAsync(IEnumerable<Secret> secr
RequireExpirationTime = true,

ClockSkew = _options.JwtValidationClockSkew,
ValidAlgorithms = _options.AllowedJwtAlgorithms
ValidAlgorithms = _options.SupportedClientAssertionSigningAlgorithms
};

var issuer = await _issuerNameService.GetCurrentAsync();

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
Expand Down Expand Up @@ -205,7 +205,7 @@ public async Task<SecretValidationResult> ValidateAsync(IEnumerable<Secret> 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)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -279,8 +279,7 @@ private async Task<TokenValidationResult> 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())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
namespace Duende.IdentityServer.Validation;

/// <summary>
/// Base class for a validate authorize or token request
/// Base class for a validated authorize or token request
/// </summary>
public class ValidatedRequest
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand All @@ -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<Dictionary<string, JsonElement>>(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()
Expand Down Expand Up @@ -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<string>
{
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);
}
}
Loading
Loading