Browse Source

Allow using mTLS client authentication with the client credentials grant and support mTLS token binding for anonymous clients

pull/2446/head
Kévin Chalet 2 weeks ago
parent
commit
59a137cd63
  1. 14
      src/OpenIddict.Abstractions/OpenIddictResources.resx
  2. 2
      src/OpenIddict.Client.SystemNetHttp/OpenIddictClientSystemNetHttpConfiguration.cs
  3. 2
      src/OpenIddict.Client.SystemNetHttp/OpenIddictClientSystemNetHttpHandlers.cs
  4. 5
      src/OpenIddict.Server/OpenIddictServerBuilder.cs
  5. 29
      src/OpenIddict.Server/OpenIddictServerHandlers.Exchange.cs
  6. 141
      src/OpenIddict.Server/OpenIddictServerHandlers.cs
  7. 27
      src/OpenIddict.Server/OpenIddictServerOptions.cs
  8. 2
      src/OpenIddict.Validation.SystemNetHttp/OpenIddictValidationSystemNetHttpConfiguration.cs
  9. 2
      src/OpenIddict.Validation.SystemNetHttp/OpenIddictValidationSystemNetHttpHandlers.cs
  10. 162
      test/OpenIddict.Server.IntegrationTests/OpenIddictServerIntegrationTests.cs

14
src/OpenIddict.Abstractions/OpenIddictResources.resx

@ -1860,6 +1860,12 @@ To use a custom policy relying on the system store, set 'OpenIddictServerOptions
<data name="ID0514" xml:space="preserve">
<value>TLS client certificates must contain a private key.</value>
</data>
<data name="ID0515" xml:space="preserve">
<value>An existing '{0}' instance is already attached to the execution context.</value>
</data>
<data name="ID0516" xml:space="preserve">
<value>The '{0}' attached to the execution context could not be resolved.</value>
</data>
<data name="ID2000" xml:space="preserve">
<value>The security token is missing.</value>
</data>
@ -2462,9 +2468,11 @@ To use a custom policy relying on the system store, set 'OpenIddictServerOptions
</data>
<data name="ID2201" xml:space="preserve">
<value>An existing '{0}' instance is already attached to the execution context.</value>
<comment>This resource is no longer used and will be removed in a future version.</comment>
</data>
<data name="ID2202" xml:space="preserve">
<value>The '{0}' attached to the execution context could not be resolved.</value>
<comment>This resource is no longer used and will be removed in a future version.</comment>
</data>
<data name="ID2203" xml:space="preserve">
<value>A certificate-based proof-of-possession is required to use this token.</value>
@ -2472,6 +2480,9 @@ To use a custom policy relying on the system store, set 'OpenIddictServerOptions
<data name="ID2204" xml:space="preserve">
<value>The specified certificate-based proof-of-possession is not valid.</value>
</data>
<data name="ID2205" xml:space="preserve">
<value>The specified TLS client certificate is not allowed or valid for this operation.</value>
</data>
<data name="ID4000" xml:space="preserve">
<value>The '{0}' parameter shouldn't be null or empty at this point.</value>
</data>
@ -3315,6 +3326,9 @@ This may indicate that the hashed entry is corrupted or malformed.</value>
<data name="ID6292" xml:space="preserve">
<value>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.</value>
</data>
<data name="ID6293" xml:space="preserve">
<value>Certificate validation failed because the token binding certificate provided by an anonymous client was not valid: {Errors}.</value>
</data>
<data name="ID8000" xml:space="preserve">
<value>https://documentation.openiddict.com/errors/{0}</value>
</data>

2
src/OpenIddict.Client.SystemNetHttp/OpenIddictClientSystemNetHttpConfiguration.cs

@ -78,7 +78,7 @@ public sealed class OpenIddictClientSystemNetHttpConfiguration : IConfigureOptio
// 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<IOptionsMonitor<OpenIddictClientSystemNetHttpOptions>>().CurrentValue;

2
src/OpenIddict.Client.SystemNetHttp/OpenIddictClientSystemNetHttpHandlers.cs

@ -200,7 +200,7 @@ public static partial class OpenIddictClientSystemNetHttpHandlers
if (OpenIddictClientSystemNetHttpContext.Current is not null)
{
throw new InvalidOperationException(SR.FormatID2201(nameof(OpenIddictClientSystemNetHttpContext)));
throw new InvalidOperationException(SR.FormatID0515(nameof(OpenIddictClientSystemNetHttpContext)));
}
try

5
src/OpenIddict.Server/OpenIddictServerBuilder.cs

@ -116,8 +116,11 @@ public sealed class OpenIddictServerBuilder
/// <summary>
/// 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.
/// </summary>
/// <remarks>
/// Enabling this option is NOT recommended and should only be used for
/// backward compatibility with legacy authorization server deployments.
/// </remarks>
/// <returns>The <see cref="OpenIddictServerBuilder"/> instance.</returns>
public OpenIddictServerBuilder AcceptAnonymousClients()
=> Configure(options => options.AcceptAnonymousClients = true);

29
src/OpenIddict.Server/OpenIddictServerHandlers.Exchange.cs

@ -412,30 +412,18 @@ public static partial class OpenIddictServerHandlers
{
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 static partial class OpenIddictServerHandlers
// 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,

141
src/OpenIddict.Server/OpenIddictServerHandlers.cs

@ -9,6 +9,7 @@ using System.ComponentModel;
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 static partial class OpenIddictServerHandlers
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 @@ public static partial class OpenIddictServerHandlers
/// </summary>
public sealed class ValidateClientCertificate : IOpenIddictServerHandler<ProcessAuthenticationContext>
{
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 static partial class OpenIddictServerHandlers
/// </summary>
public static OpenIddictServerHandlerDescriptor Descriptor { get; }
= OpenIddictServerHandlerDescriptor.CreateBuilder<ProcessAuthenticationContext>()
.AddFilter<RequireClientIdParameter>()
.AddFilter<RequireClientCertificate>()
.AddFilter<RequireDegradedModeDisabled>()
.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<IOptionsMonitor<OpenIddictServerOptions>>().CurrentValue;
return options.EnableDegradedMode ?
new ValidateClientCertificate() :
new ValidateClientCertificate(provider.GetService<IOpenIddictApplicationManager>() ??
throw new InvalidOperationException(SR.GetResourceString(SR.ID0016)));
})
.UseScopedHandler<ValidateClientCertificate>()
.SetOrder(ValidateClientSecret.Descriptor.Order + 1_000)
.SetType(OpenIddictServerHandlerType.BuiltIn)
@ -1265,7 +1281,6 @@ public static partial class OpenIddictServerHandlers
{
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 @@ public static partial class OpenIddictServerHandlers
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 @@ public static partial class OpenIddictServerHandlers
{
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 @@ public static partial class OpenIddictServerHandlers
{
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(

27
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.
/// </summary>
/// <remarks>
/// Setting this property to <see langword="true"/> is NOT recommended.
/// </remarks>
public bool AcceptAnonymousClients { get; set; }
/// <summary>
@ -593,38 +596,50 @@ public sealed class OpenIddictServerOptions
/// <summary>
/// Gets or sets a boolean indicating whether audience permissions should be ignored.
/// Setting this property to <see langword="true"/> is NOT recommended.
/// </summary>
/// <remarks>
/// Setting this property to <see langword="true"/> is NOT recommended.
/// </remarks>
public bool IgnoreAudiencePermissions { get; set; }
/// <summary>
/// Gets or sets a boolean indicating whether endpoint permissions should be ignored.
/// Setting this property to <see langword="true"/> is NOT recommended.
/// </summary>
/// <remarks>
/// Setting this property to <see langword="true"/> is NOT recommended.
/// </remarks>
public bool IgnoreEndpointPermissions { get; set; }
/// <summary>
/// Gets or sets a boolean indicating whether grant type permissions should be ignored.
/// Setting this property to <see langword="true"/> is NOT recommended.
/// </summary>
/// <remarks>
/// Setting this property to <see langword="true"/> is NOT recommended.
/// </remarks>
public bool IgnoreGrantTypePermissions { get; set; }
/// <summary>
/// Gets or sets a boolean indicating whether resource permissions should be ignored.
/// Setting this property to <see langword="true"/> is NOT recommended.
/// </summary>
/// <remarks>
/// Setting this property to <see langword="true"/> is NOT recommended.
/// </remarks>
public bool IgnoreResourcePermissions { get; set; }
/// <summary>
/// Gets or sets a boolean indicating whether response type permissions should be ignored.
/// Setting this property to <see langword="true"/> is NOT recommended.
/// </summary>
/// <remarks>
/// Setting this property to <see langword="true"/> is NOT recommended.
/// </remarks>
public bool IgnoreResponseTypePermissions { get; set; }
/// <summary>
/// Gets or sets a boolean indicating whether scope permissions should be ignored.
/// Setting this property to <see langword="true"/> is NOT recommended.
/// </summary>
/// <remarks>
/// Setting this property to <see langword="true"/> is NOT recommended.
/// </remarks>
public bool IgnoreScopePermissions { get; set; }
/// <summary>

2
src/OpenIddict.Validation.SystemNetHttp/OpenIddictValidationSystemNetHttpConfiguration.cs

@ -75,7 +75,7 @@ public sealed class OpenIddictValidationSystemNetHttpConfiguration : IConfigureO
// 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<IOptionsMonitor<OpenIddictValidationSystemNetHttpOptions>>().CurrentValue;

2
src/OpenIddict.Validation.SystemNetHttp/OpenIddictValidationSystemNetHttpHandlers.cs

@ -84,7 +84,7 @@ public static partial class OpenIddictValidationSystemNetHttpHandlers
if (OpenIddictValidationSystemNetHttpContext.Current is not null)
{
throw new InvalidOperationException(SR.FormatID2201(nameof(OpenIddictValidationSystemNetHttpContext)));
throw new InvalidOperationException(SR.FormatID0515(nameof(OpenIddictValidationSystemNetHttpContext)));
}
try

162
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 abstract partial class OpenIddictServerIntegrationTests
}
#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<ProcessAuthenticationContext>(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<ProcessAuthenticationContext>(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 abstract partial class OpenIddictServerIntegrationTests
[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();

Loading…
Cancel
Save