diff --git a/src/OpenIddict.Abstractions/OpenIddictResources.resx b/src/OpenIddict.Abstractions/OpenIddictResources.resx
index 218911a9e..8ff98b2c0 100644
--- a/src/OpenIddict.Abstractions/OpenIddictResources.resx
+++ b/src/OpenIddict.Abstractions/OpenIddictResources.resx
@@ -1860,6 +1860,12 @@ To use a custom policy relying on the system store, set 'OpenIddictServerOptions
TLS client certificates must contain a private key.
+
+ An existing '{0}' instance is already attached to the execution context.
+
+
+ The '{0}' attached to the execution context could not be resolved.
+
The security token is missing.
@@ -2462,9 +2468,11 @@ To use a custom policy relying on the system store, set 'OpenIddictServerOptions
An existing '{0}' instance is already attached to the execution context.
+ This resource is no longer used and will be removed in a future version.
The '{0}' attached to the execution context could not be resolved.
+ This resource is no longer used and will be removed in a future version.
A certificate-based proof-of-possession is required to use this token.
@@ -2472,6 +2480,9 @@ To use a custom policy relying on the system store, set 'OpenIddictServerOptions
The specified certificate-based proof-of-possession is not valid.
+
+ The specified TLS client certificate is not allowed or valid for this operation.
+
The '{0}' parameter shouldn't be null or empty at this point.
@@ -3315,6 +3326,9 @@ This may indicate that the hashed entry is corrupted or malformed.
The payload of the '{0}' reference token was used instead of its reference identifier, which may indicate that the payload stored in the database has leaked and is being used as a regular token.
+
+ Certificate validation failed because the token binding certificate provided by an anonymous client was not valid: {Errors}.
+
https://documentation.openiddict.com/errors/{0}
diff --git a/src/OpenIddict.Client.SystemNetHttp/OpenIddictClientSystemNetHttpConfiguration.cs b/src/OpenIddict.Client.SystemNetHttp/OpenIddictClientSystemNetHttpConfiguration.cs
index ef9783bc4..685774a1e 100644
--- a/src/OpenIddict.Client.SystemNetHttp/OpenIddictClientSystemNetHttpConfiguration.cs
+++ b/src/OpenIddict.Client.SystemNetHttp/OpenIddictClientSystemNetHttpConfiguration.cs
@@ -78,7 +78,7 @@ public void Configure(string? name, HttpClientFactoryOptions options)
// an async-local context to flow per-instance properties and uses dynamic client
// names to ensure the inner HttpClientHandler is not reused if the context differs.
var context = OpenIddictClientSystemNetHttpContext.Current ??
- throw new InvalidOperationException(SR.FormatID2202(nameof(OpenIddictClientSystemNetHttpContext)));
+ throw new InvalidOperationException(SR.FormatID0516(nameof(OpenIddictClientSystemNetHttpContext)));
var settings = _provider.GetRequiredService>().CurrentValue;
diff --git a/src/OpenIddict.Client.SystemNetHttp/OpenIddictClientSystemNetHttpHandlers.cs b/src/OpenIddict.Client.SystemNetHttp/OpenIddictClientSystemNetHttpHandlers.cs
index 31e811fe2..9ad12b373 100644
--- a/src/OpenIddict.Client.SystemNetHttp/OpenIddictClientSystemNetHttpHandlers.cs
+++ b/src/OpenIddict.Client.SystemNetHttp/OpenIddictClientSystemNetHttpHandlers.cs
@@ -200,7 +200,7 @@ public ValueTask HandleAsync(TContext context)
if (OpenIddictClientSystemNetHttpContext.Current is not null)
{
- throw new InvalidOperationException(SR.FormatID2201(nameof(OpenIddictClientSystemNetHttpContext)));
+ throw new InvalidOperationException(SR.FormatID0515(nameof(OpenIddictClientSystemNetHttpContext)));
}
try
diff --git a/src/OpenIddict.Server/OpenIddictServerBuilder.cs b/src/OpenIddict.Server/OpenIddictServerBuilder.cs
index dabdd762c..bca5f7a7b 100644
--- a/src/OpenIddict.Server/OpenIddictServerBuilder.cs
+++ b/src/OpenIddict.Server/OpenIddictServerBuilder.cs
@@ -116,8 +116,11 @@ public OpenIddictServerBuilder Configure(Action configu
///
/// Makes client identification optional so that token, introspection and revocation
/// requests that don't specify a client_id are not automatically rejected.
- /// Enabling this option is NOT recommended.
///
+ ///
+ /// Enabling this option is NOT recommended and should only be used for
+ /// backward compatibility with legacy authorization server deployments.
+ ///
/// The instance.
public OpenIddictServerBuilder AcceptAnonymousClients()
=> Configure(options => options.AcceptAnonymousClients = true);
diff --git a/src/OpenIddict.Server/OpenIddictServerHandlers.Exchange.cs b/src/OpenIddict.Server/OpenIddictServerHandlers.Exchange.cs
index 70a946019..138ab3433 100644
--- a/src/OpenIddict.Server/OpenIddictServerHandlers.Exchange.cs
+++ b/src/OpenIddict.Server/OpenIddictServerHandlers.Exchange.cs
@@ -412,30 +412,18 @@ public ValueTask HandleAsync(ValidateTokenRequestContext context)
{
ArgumentNullException.ThrowIfNull(context);
- // Reject grant_type=authorization_code requests that don't specify a client_id or a client_assertion,
- // as the client identifier MUST be sent by the client application in the request body if it cannot
- // be inferred from the client authentication method (e.g the username when using basic).
- //
- // See https://tools.ietf.org/html/rfc6749#section-4.1.3 for more information.
- if (context.Request.IsAuthorizationCodeGrantType() &&
- string.IsNullOrEmpty(context.Request.ClientId) &&
- string.IsNullOrEmpty(context.Request.ClientAssertion))
+ if (!context.Request.IsAuthorizationCodeGrantType() && !context.Request.IsClientCredentialsGrantType())
{
- context.Logger.LogInformation(6077, SR.GetResourceString(SR.ID6077), Parameters.ClientId);
-
- context.Reject(
- error: Errors.InvalidRequest,
- description: SR.FormatID2029(Parameters.ClientId),
- uri: SR.FormatID8000(SR.ID2029));
-
return ValueTask.CompletedTask;
}
- // Reject grant_type=client_credentials requests that don't specify a client_id or a client_assertion.
+ // Reject grant_type=authorization_code and grant_type=client_credentials requests that
+ // don't specify a client_id or a client_assertion, as the client identity MUST be sent
+ // by the client application (even when using mTLS OAuth 2.0 client authentication).
//
- // See https://tools.ietf.org/html/rfc6749#section-4.4.1 for more information.
- if (context.Request.IsClientCredentialsGrantType() &&
- string.IsNullOrEmpty(context.Request.ClientId) &&
+ // See https://tools.ietf.org/html/rfc6749#section-4.1.3
+ // and https://tools.ietf.org/html/rfc6749#section-4.4.1 for more information.
+ if (string.IsNullOrEmpty(context.Request.ClientId) &&
string.IsNullOrEmpty(context.Request.ClientAssertion))
{
context.Logger.LogInformation(6077, SR.GetResourceString(SR.ID6077), Parameters.ClientId);
@@ -568,7 +556,8 @@ public ValueTask HandleAsync(ValidateTokenRequestContext context)
// See https://tools.ietf.org/html/rfc6749#section-4.4.1 for more information.
if (context.Request.IsClientCredentialsGrantType() &&
string.IsNullOrEmpty(context.Request.ClientAssertion) &&
- string.IsNullOrEmpty(context.Request.ClientSecret))
+ string.IsNullOrEmpty(context.Request.ClientSecret) &&
+ context.Transaction.RemoteCertificate is null)
{
context.Reject(
error: Errors.InvalidRequest,
diff --git a/src/OpenIddict.Server/OpenIddictServerHandlers.cs b/src/OpenIddict.Server/OpenIddictServerHandlers.cs
index d67b9bb15..cdd1526ab 100644
--- a/src/OpenIddict.Server/OpenIddictServerHandlers.cs
+++ b/src/OpenIddict.Server/OpenIddictServerHandlers.cs
@@ -9,6 +9,7 @@
using System.Diagnostics;
using System.Globalization;
using System.Security.Claims;
+using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using System.Text.Json;
@@ -1004,7 +1005,13 @@ public async ValueTask HandleAsync(ProcessAuthenticationContext context)
case OpenIddictServerEndpointType.Introspection when context.Options.AcceptAnonymousClients:
case OpenIddictServerEndpointType.Revocation when context.Options.AcceptAnonymousClients:
- case OpenIddictServerEndpointType.Token when context.Options.AcceptAnonymousClients:
+ return;
+
+ // Note: the authorization code and client credentials grant types never
+ // allow anonymous clients, even if the corresponding option is enabled.
+ case OpenIddictServerEndpointType.Token when context.Options.AcceptAnonymousClients &&
+ !context.Request.IsAuthorizationCodeGrantType() &&
+ !context.Request.IsClientCredentialsGrantType():
return;
// Note: despite being conceptually similar to the token endpoint, the pushed authorization
@@ -1240,9 +1247,9 @@ OpenIddictServerEndpointType.EndUserVerification or
///
public sealed class ValidateClientCertificate : IOpenIddictServerHandler
{
- private readonly IOpenIddictApplicationManager _applicationManager;
+ private readonly IOpenIddictApplicationManager? _applicationManager;
- public ValidateClientCertificate() => throw new InvalidOperationException(SR.GetResourceString(SR.ID0016));
+ public ValidateClientCertificate() { }
public ValidateClientCertificate(IOpenIddictApplicationManager applicationManager)
=> _applicationManager = applicationManager ?? throw new ArgumentNullException(nameof(applicationManager));
@@ -1252,9 +1259,18 @@ public ValidateClientCertificate(IOpenIddictApplicationManager applicationManage
///
public static OpenIddictServerHandlerDescriptor Descriptor { get; }
= OpenIddictServerHandlerDescriptor.CreateBuilder()
- .AddFilter()
.AddFilter()
- .AddFilter()
+ .UseScopedHandler(static provider =>
+ {
+ // Note: the application manager is only resolved if the degraded mode was not enabled to ensure
+ // invalid core configuration exceptions are not thrown even if the managers were registered.
+ var options = provider.GetRequiredService>().CurrentValue;
+
+ return options.EnableDegradedMode ?
+ new ValidateClientCertificate() :
+ new ValidateClientCertificate(provider.GetService() ??
+ throw new InvalidOperationException(SR.GetResourceString(SR.ID0016)));
+ })
.UseScopedHandler()
.SetOrder(ValidateClientSecret.Descriptor.Order + 1_000)
.SetType(OpenIddictServerHandlerType.BuiltIn)
@@ -1265,7 +1281,6 @@ public async ValueTask HandleAsync(ProcessAuthenticationContext context)
{
ArgumentNullException.ThrowIfNull(context);
- Debug.Assert(!string.IsNullOrEmpty(context.ClientId), SR.FormatID4000(Parameters.ClientId));
Debug.Assert(context.Transaction.RemoteCertificate is not null, SR.GetResourceString(SR.ID4020));
// Don't validate the client certificate on endpoints that don't support client authentication/token binding.
@@ -1277,6 +1292,106 @@ OpenIddictServerEndpointType.EndUserVerification or
return;
}
+ // If the client is anonymous, assume the provided certificate will exclusively be used for mTLS token
+ // binding, validate the certificate using the base chain policy specified in the options and ensure
+ // the certificate is self-signed as PKI certificates cannot be used for token binding exclusively.
+ if (string.IsNullOrEmpty(context.ClientId))
+ {
+ // Note: to avoid building and introspecting a X.509 certificate chain and reduce the cost
+ // of this check, a certificate is always assumed to be self-signed when it is self-issued.
+ //
+ // A second pass is performed once the chain is built to validate whether the certificate is self-signed or not.
+ if (context.Options.SelfSignedTlsClientAuthenticationPolicy is not X509ChainPolicy policy ||
+ !OpenIddictHelpers.IsSelfIssuedCertificate(context.Transaction.RemoteCertificate))
+ {
+ context.Reject(
+ error: Errors.InvalidRequest,
+ description: SR.GetResourceString(SR.ID2205),
+ uri: SR.FormatID8000(SR.ID2205));
+
+ return;
+ }
+
+ // Always clone the X.509 chain policy to ensure the original instance is never mutated.
+ policy = policy.Clone();
+
+ // Note: to allow validating certificates that are exclusively used for mTLS token binding, the chain policy
+ // is amended to consider the specified self-signed certificate as a trusted root and basically disable chain
+ // validation while still validating the other aspects of the certificate (e.g expiration date, key usage, etc).
+#if SUPPORTS_X509_CHAIN_POLICY_CUSTOM_TRUST_STORE
+ policy.CustomTrustStore.Add(context.Transaction.RemoteCertificate);
+#else
+ policy.ExtraStore.Add(context.Transaction.RemoteCertificate);
+#endif
+
+ using var chain = new X509Chain()
+ {
+ ChainPolicy = policy
+ };
+
+ try
+ {
+ // Ensure the specified certificate is valid based on the chain policy.
+ if (!chain.Build(context.Transaction.RemoteCertificate))
+ {
+ context.Logger.LogInformation(6293, SR.GetResourceString(SR.ID6293),
+ chain.ChainStatus.Select(static status => status.Status).ToArray());
+
+ context.Reject(
+ error: Errors.InvalidRequest,
+ description: SR.GetResourceString(SR.ID2197),
+ uri: SR.FormatID8000(SR.ID2197));
+
+ return;
+ }
+
+ if (chain.ChainElements is not [X509ChainElement])
+ {
+ context.Reject(
+ error: Errors.InvalidRequest,
+ description: SR.GetResourceString(SR.ID2205),
+ uri: SR.FormatID8000(SR.ID2205));
+
+ return;
+ }
+ }
+
+ catch (CryptographicException exception) when (!OpenIddictHelpers.IsFatal(exception))
+ {
+ context.Logger.LogWarning(6288, exception, SR.GetResourceString(SR.ID6288));
+
+ context.Reject(
+ error: Errors.InvalidRequest,
+ description: SR.GetResourceString(SR.ID2197),
+ uri: SR.FormatID8000(SR.ID2197));
+
+ return;
+ }
+
+ finally
+ {
+ // Dispose the certificates instantiated internally while building the chain.
+ for (var index = 0; index < chain.ChainElements.Count; index++)
+ {
+ chain.ChainElements[index].Certificate.Dispose();
+ }
+ }
+
+ return;
+ }
+
+ // Note: when the degraded mode is enabled, the application is responsible for manually
+ // validating the client certificate provided by the client using a custom event handler.
+ if (context.Options.EnableDegradedMode)
+ {
+ return;
+ }
+
+ if (_applicationManager is null)
+ {
+ throw new InvalidOperationException(SR.GetResourceString(SR.ID0016));
+ }
+
var application = await _applicationManager.FindByClientIdAsync(context.ClientId) ??
throw new InvalidOperationException(SR.GetResourceString(SR.ID0032));
@@ -1291,7 +1406,12 @@ OpenIddictServerEndpointType.EndUserVerification or
{
if (context.Options.SelfSignedTlsClientAuthenticationPolicy is null)
{
- throw new InvalidOperationException(SR.GetResourceString(SR.ID0506));
+ context.Reject(
+ error: Errors.InvalidRequest,
+ description: SR.GetResourceString(SR.ID2205),
+ uri: SR.FormatID8000(SR.ID2205));
+
+ return;
}
if (await _applicationManager.GetSelfSignedTlsClientAuthenticationPolicyAsync(
@@ -1346,7 +1466,12 @@ OpenIddictServerEndpointType.EndUserVerification or
{
if (context.Options.PublicKeyInfrastructureTlsClientAuthenticationPolicy is null)
{
- throw new InvalidOperationException(SR.GetResourceString(SR.ID0505));
+ context.Reject(
+ error: Errors.InvalidRequest,
+ description: SR.GetResourceString(SR.ID2205),
+ uri: SR.FormatID8000(SR.ID2205));
+
+ return;
}
if (await _applicationManager.GetPublicKeyInfrastructureTlsClientAuthenticationPolicyAsync(
diff --git a/src/OpenIddict.Server/OpenIddictServerOptions.cs b/src/OpenIddict.Server/OpenIddictServerOptions.cs
index 63a4881e4..bd70ae9c8 100644
--- a/src/OpenIddict.Server/OpenIddictServerOptions.cs
+++ b/src/OpenIddict.Server/OpenIddictServerOptions.cs
@@ -359,6 +359,9 @@ public sealed class OpenIddictServerOptions
/// Enabling this option allows client applications to communicate with the token,
/// introspection and revocation endpoints without having to send their client identifier.
///
+ ///
+ /// Setting this property to is NOT recommended.
+ ///
public bool AcceptAnonymousClients { get; set; }
///
@@ -593,38 +596,50 @@ public sealed class OpenIddictServerOptions
///
/// Gets or sets a boolean indicating whether audience permissions should be ignored.
- /// Setting this property to is NOT recommended.
///
+ ///
+ /// Setting this property to is NOT recommended.
+ ///
public bool IgnoreAudiencePermissions { get; set; }
///
/// Gets or sets a boolean indicating whether endpoint permissions should be ignored.
- /// Setting this property to is NOT recommended.
///
+ ///
+ /// Setting this property to is NOT recommended.
+ ///
public bool IgnoreEndpointPermissions { get; set; }
///
/// Gets or sets a boolean indicating whether grant type permissions should be ignored.
- /// Setting this property to is NOT recommended.
///
+ ///
+ /// Setting this property to is NOT recommended.
+ ///
public bool IgnoreGrantTypePermissions { get; set; }
///
/// Gets or sets a boolean indicating whether resource permissions should be ignored.
- /// Setting this property to is NOT recommended.
///
+ ///
+ /// Setting this property to is NOT recommended.
+ ///
public bool IgnoreResourcePermissions { get; set; }
///
/// Gets or sets a boolean indicating whether response type permissions should be ignored.
- /// Setting this property to is NOT recommended.
///
+ ///
+ /// Setting this property to is NOT recommended.
+ ///
public bool IgnoreResponseTypePermissions { get; set; }
///
/// Gets or sets a boolean indicating whether scope permissions should be ignored.
- /// Setting this property to is NOT recommended.
///
+ ///
+ /// Setting this property to is NOT recommended.
+ ///
public bool IgnoreScopePermissions { get; set; }
///
diff --git a/src/OpenIddict.Validation.SystemNetHttp/OpenIddictValidationSystemNetHttpConfiguration.cs b/src/OpenIddict.Validation.SystemNetHttp/OpenIddictValidationSystemNetHttpConfiguration.cs
index 6bd88a213..c75a49345 100644
--- a/src/OpenIddict.Validation.SystemNetHttp/OpenIddictValidationSystemNetHttpConfiguration.cs
+++ b/src/OpenIddict.Validation.SystemNetHttp/OpenIddictValidationSystemNetHttpConfiguration.cs
@@ -75,7 +75,7 @@ public void Configure(string? name, HttpClientFactoryOptions options)
// an async-local context to flow per-instance properties and uses dynamic client
// names to ensure the inner HttpClientHandler is not reused if the context differs.
var context = OpenIddictValidationSystemNetHttpContext.Current ??
- throw new InvalidOperationException(SR.FormatID2202(nameof(OpenIddictValidationSystemNetHttpContext)));
+ throw new InvalidOperationException(SR.FormatID0516(nameof(OpenIddictValidationSystemNetHttpContext)));
var settings = _provider.GetRequiredService>().CurrentValue;
diff --git a/src/OpenIddict.Validation.SystemNetHttp/OpenIddictValidationSystemNetHttpHandlers.cs b/src/OpenIddict.Validation.SystemNetHttp/OpenIddictValidationSystemNetHttpHandlers.cs
index 00b2bf0d1..66ddc8772 100644
--- a/src/OpenIddict.Validation.SystemNetHttp/OpenIddictValidationSystemNetHttpHandlers.cs
+++ b/src/OpenIddict.Validation.SystemNetHttp/OpenIddictValidationSystemNetHttpHandlers.cs
@@ -84,7 +84,7 @@ public ValueTask HandleAsync(TContext context)
if (OpenIddictValidationSystemNetHttpContext.Current is not null)
{
- throw new InvalidOperationException(SR.FormatID2201(nameof(OpenIddictValidationSystemNetHttpContext)));
+ throw new InvalidOperationException(SR.FormatID0515(nameof(OpenIddictValidationSystemNetHttpContext)));
}
try
diff --git a/test/OpenIddict.Server.IntegrationTests/OpenIddictServerIntegrationTests.cs b/test/OpenIddict.Server.IntegrationTests/OpenIddictServerIntegrationTests.cs
index 9b3a45352..ab7a300aa 100644
--- a/test/OpenIddict.Server.IntegrationTests/OpenIddictServerIntegrationTests.cs
+++ b/test/OpenIddict.Server.IntegrationTests/OpenIddictServerIntegrationTests.cs
@@ -9,6 +9,7 @@
using System.Collections.Immutable;
using System.Globalization;
using System.Security.Claims;
+using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using System.Text.Json;
using System.Text.Json.Nodes;
@@ -1099,13 +1100,170 @@ public async Task ProcessAuthentication_RequestIsRejectedWhenClientSecretIsInval
}
#if SUPPORTS_X509_CHAIN_POLICY_CUSTOM_TRUST_STORE
+ [Theory]
+ [InlineData(OpenIddictServerEndpointType.Introspection)]
+ [InlineData(OpenIddictServerEndpointType.Revocation)]
+ [InlineData(OpenIddictServerEndpointType.Token)]
+ public async Task ProcessAuthentication_SelfSignedClientCertificateIsRejectedWhenBaseChainPolicyIsNullWithUnknownClient(OpenIddictServerEndpointType type)
+ {
+ // Arrange
+ await using var server = await CreateServerAsync(options =>
+ {
+ options.EnableDegradedMode();
+ options.AcceptAnonymousClients();
+
+ options.AddEventHandler(builder =>
+ {
+ builder.UseInlineHandler(context =>
+ {
+ context.Transaction.RemoteCertificate = X509Certificate2.CreateFromPem($"""
+ -----BEGIN CERTIFICATE-----
+ MIIC8jCCAdqgAwIBAgIIYfcknj8KXN0wDQYJKoZIhvcNAQELBQAwIjEgMB4GA1UE
+ AxMXU2VsZi1zaWduZWQgY2VydGlmaWNhdGUwIBcNMjYwMjAxMTc1MjI2WhgPMjEy
+ NjAyMDExNzUyMjZaMCIxIDAeBgNVBAMTF1NlbGYtc2lnbmVkIGNlcnRpZmljYXRl
+ MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA45uKd5cdlLmEBGLDEB75
+ o9e3/kMmQjVhhMeBsy4m2t7zw5jZo7OPcahXiXZHttom9tJm7BWPWvYx7p0N9+ss
+ h/E5lzKyV7ZXg+mM+KeECtVhiy+82BuIPelCshrpaV3lIg93y47FYLIWXxdggjt6
+ 6VUaxzlTeo+IpuMz8IssL7VpJnjCT5NmqPNVkv1VR1uuetVqP7546ZFw31RiGl/0
+ I1uUlb7SwLwhLUK1iyLmGNA3VDB0m0DvLmlIEY3ZE5zxQp/Rxq6DfjbXm2LWJyu6
+ NO7k7JixXOorEl+6HdJZHTWNFK5jCo2ZZAwWn+uUuzgmLILPJFLDVutXjuEZpsym
+ uQIDAQABoyowKDAOBgNVHQ8BAf8EBAMCB4AwFgYDVR0lAQH/BAwwCgYIKwYBBQUH
+ AwIwDQYJKoZIhvcNAQELBQADggEBAGHH3f/bkfViTvPE7yXJkB0bs88mYxajluMA
+ hgihEN5joPT6zHxMLBND2sitIozCMeeaj0rg+OaT/zDBgOLup/BM92UaPpYcgDCy
+ 3tHqZLOOJOR4aYnHhIQUnx+NRtKEM4q/hL/xLHeliKmV7TQXISEZlTbb0gOU7TFp
+ nJlP60Vo9F/WD6xcKNxBgV5aB/+2FjiTTw2pF0VUmvcZdQAN5ysfrmKNXbvv1oCp
+ AohiwRiPrwe3mJ8iCqzEY/qQqImEiIT8WC2Fty+UYyBwfXMObi1AO++QkaMUbUJl
+ 0aBuRoD85FLotjHIHXkFHERjOolheYdKt5nrGCCz/PmXBfsSCTo=
+ -----END CERTIFICATE-----
+ """);
+
+ return ValueTask.CompletedTask;
+ });
+
+ builder.SetOrder(ValidateClientType.Descriptor.Order - 500);
+ });
+ });
+
+ await using var client = await server.CreateClientAsync();
+
+ // Act
+ var response = type switch
+ {
+ OpenIddictServerEndpointType.Introspection => await client.PostAsync("/connect/introspect", new OpenIddictRequest
+ {
+ Token = "2YotnFZFEjr1zCsicMWpAA"
+ }),
+
+ OpenIddictServerEndpointType.Revocation => await client.PostAsync("/connect/revoke", new OpenIddictRequest
+ {
+ Token = "SlAV32hkKG",
+ TokenTypeHint = TokenTypeHints.RefreshToken
+ }),
+
+ OpenIddictServerEndpointType.Token => await client.PostAsync("/connect/token", new OpenIddictRequest
+ {
+ GrantType = GrantTypes.Password,
+ Username = "johndoe",
+ Password = "A3ddj3w"
+ }),
+
+ _ => throw new NotSupportedException()
+ };
+
+ // Assert
+ Assert.Equal(Errors.InvalidRequest, response.Error);
+ Assert.Equal(SR.GetResourceString(SR.ID2205), response.ErrorDescription);
+ Assert.Equal(SR.FormatID8000(SR.ID2205), response.ErrorUri);
+ }
+
+ [Theory]
+ [InlineData(OpenIddictServerEndpointType.Introspection)]
+ [InlineData(OpenIddictServerEndpointType.Revocation)]
+ [InlineData(OpenIddictServerEndpointType.Token)]
+ public async Task ProcessAuthentication_SelfSignedClientCertificateIsRejectedWhenInvalidWithUnknownClient(OpenIddictServerEndpointType type)
+ {
+ // Arrange
+ await using var server = await CreateServerAsync(options =>
+ {
+ options.EnableDegradedMode();
+ options.AcceptAnonymousClients();
+
+ options.Configure(options => options.SelfSignedTlsClientAuthenticationPolicy = new X509ChainPolicy
+ {
+ ApplicationPolicy = { new Oid("1.3.6.1.4.1.99999.1.1") }
+ });
+
+ options.AddEventHandler(builder =>
+ {
+ builder.UseInlineHandler(context =>
+ {
+ context.Transaction.RemoteCertificate = X509Certificate2.CreateFromPem($"""
+ -----BEGIN CERTIFICATE-----
+ MIIC8jCCAdqgAwIBAgIIYfcknj8KXN0wDQYJKoZIhvcNAQELBQAwIjEgMB4GA1UE
+ AxMXU2VsZi1zaWduZWQgY2VydGlmaWNhdGUwIBcNMjYwMjAxMTc1MjI2WhgPMjEy
+ NjAyMDExNzUyMjZaMCIxIDAeBgNVBAMTF1NlbGYtc2lnbmVkIGNlcnRpZmljYXRl
+ MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA45uKd5cdlLmEBGLDEB75
+ o9e3/kMmQjVhhMeBsy4m2t7zw5jZo7OPcahXiXZHttom9tJm7BWPWvYx7p0N9+ss
+ h/E5lzKyV7ZXg+mM+KeECtVhiy+82BuIPelCshrpaV3lIg93y47FYLIWXxdggjt6
+ 6VUaxzlTeo+IpuMz8IssL7VpJnjCT5NmqPNVkv1VR1uuetVqP7546ZFw31RiGl/0
+ I1uUlb7SwLwhLUK1iyLmGNA3VDB0m0DvLmlIEY3ZE5zxQp/Rxq6DfjbXm2LWJyu6
+ NO7k7JixXOorEl+6HdJZHTWNFK5jCo2ZZAwWn+uUuzgmLILPJFLDVutXjuEZpsym
+ uQIDAQABoyowKDAOBgNVHQ8BAf8EBAMCB4AwFgYDVR0lAQH/BAwwCgYIKwYBBQUH
+ AwIwDQYJKoZIhvcNAQELBQADggEBAGHH3f/bkfViTvPE7yXJkB0bs88mYxajluMA
+ hgihEN5joPT6zHxMLBND2sitIozCMeeaj0rg+OaT/zDBgOLup/BM92UaPpYcgDCy
+ 3tHqZLOOJOR4aYnHhIQUnx+NRtKEM4q/hL/xLHeliKmV7TQXISEZlTbb0gOU7TFp
+ nJlP60Vo9F/WD6xcKNxBgV5aB/+2FjiTTw2pF0VUmvcZdQAN5ysfrmKNXbvv1oCp
+ AohiwRiPrwe3mJ8iCqzEY/qQqImEiIT8WC2Fty+UYyBwfXMObi1AO++QkaMUbUJl
+ 0aBuRoD85FLotjHIHXkFHERjOolheYdKt5nrGCCz/PmXBfsSCTo=
+ -----END CERTIFICATE-----
+ """);
+
+ return ValueTask.CompletedTask;
+ });
+
+ builder.SetOrder(ValidateClientType.Descriptor.Order - 500);
+ });
+ });
+
+ await using var client = await server.CreateClientAsync();
+
+ // Act
+ var response = type switch
+ {
+ OpenIddictServerEndpointType.Introspection => await client.PostAsync("/connect/introspect", new OpenIddictRequest
+ {
+ Token = "2YotnFZFEjr1zCsicMWpAA"
+ }),
+
+ OpenIddictServerEndpointType.Revocation => await client.PostAsync("/connect/revoke", new OpenIddictRequest
+ {
+ Token = "SlAV32hkKG",
+ TokenTypeHint = TokenTypeHints.RefreshToken
+ }),
+
+ OpenIddictServerEndpointType.Token => await client.PostAsync("/connect/token", new OpenIddictRequest
+ {
+ GrantType = GrantTypes.Password,
+ Username = "johndoe",
+ Password = "A3ddj3w"
+ }),
+
+ _ => throw new NotSupportedException()
+ };
+
+ // Assert
+ Assert.Equal(Errors.InvalidRequest, response.Error);
+ Assert.Equal(SR.GetResourceString(SR.ID2197), response.ErrorDescription);
+ Assert.Equal(SR.FormatID8000(SR.ID2197), response.ErrorUri);
+ }
+
[Theory]
[InlineData(OpenIddictServerEndpointType.DeviceAuthorization)]
[InlineData(OpenIddictServerEndpointType.Introspection)]
[InlineData(OpenIddictServerEndpointType.PushedAuthorization)]
[InlineData(OpenIddictServerEndpointType.Revocation)]
[InlineData(OpenIddictServerEndpointType.Token)]
- public async Task ProcessAuthentication_SelfSignedClientCertificateIsRejectedWhenChainPolicyIsNull(OpenIddictServerEndpointType type)
+ public async Task ProcessAuthentication_SelfSignedClientCertificateIsRejectedWhenApplicationChainPolicyIsNull(OpenIddictServerEndpointType type)
{
// Arrange
var application = new OpenIddictApplication();
@@ -1459,7 +1617,7 @@ public async Task ProcessAuthentication_SelfSignedClientCertificatePolicyIsAmend
[InlineData(OpenIddictServerEndpointType.PushedAuthorization)]
[InlineData(OpenIddictServerEndpointType.Revocation)]
[InlineData(OpenIddictServerEndpointType.Token)]
- public async Task ProcessAuthentication_PkiClientCertificateIsRejectedWhenChainPolicyIsNull(OpenIddictServerEndpointType type)
+ public async Task ProcessAuthentication_PkiClientCertificateIsRejectedWhenApplicationChainPolicyIsNull(OpenIddictServerEndpointType type)
{
// Arrange
var application = new OpenIddictApplication();