You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1443 lines
71 KiB
1443 lines
71 KiB
/*
|
|
* Licensed under the Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
|
|
* See https://github.com/openiddict/openiddict-core for more information concerning
|
|
* the license and the contributors participating to this project.
|
|
*/
|
|
|
|
using System;
|
|
using System.Collections.Immutable;
|
|
using System.Diagnostics;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using System.Security.Cryptography;
|
|
using System.Security.Cryptography.X509Certificates;
|
|
using System.Text;
|
|
using System.Text.Json;
|
|
using System.Threading.Tasks;
|
|
using JetBrains.Annotations;
|
|
using Microsoft.Extensions.Logging;
|
|
using Microsoft.IdentityModel.Tokens;
|
|
using OpenIddict.Abstractions;
|
|
using static OpenIddict.Abstractions.OpenIddictConstants;
|
|
using static OpenIddict.Server.OpenIddictServerEvents;
|
|
|
|
namespace OpenIddict.Server
|
|
{
|
|
public static partial class OpenIddictServerHandlers
|
|
{
|
|
public static class Discovery
|
|
{
|
|
public static ImmutableArray<OpenIddictServerHandlerDescriptor> DefaultHandlers { get; } = ImmutableArray.Create(
|
|
/*
|
|
* Configuration request top-level processing:
|
|
*/
|
|
ExtractConfigurationRequest.Descriptor,
|
|
ValidateConfigurationRequest.Descriptor,
|
|
HandleConfigurationRequest.Descriptor,
|
|
ApplyConfigurationResponse<ProcessErrorContext>.Descriptor,
|
|
ApplyConfigurationResponse<ProcessRequestContext>.Descriptor,
|
|
|
|
/*
|
|
* Configuration request handling:
|
|
*/
|
|
AttachEndpoints.Descriptor,
|
|
AttachGrantTypes.Descriptor,
|
|
AttachResponseModes.Descriptor,
|
|
AttachResponseTypes.Descriptor,
|
|
AttachClientAuthenticationMethods.Descriptor,
|
|
AttachCodeChallengeMethods.Descriptor,
|
|
AttachScopes.Descriptor,
|
|
AttachClaims.Descriptor,
|
|
AttachSubjectTypes.Descriptor,
|
|
AttachSigningAlgorithms.Descriptor,
|
|
AttachAdditionalMetadata.Descriptor,
|
|
|
|
/*
|
|
* Cryptography request top-level processing:
|
|
*/
|
|
ExtractCryptographyRequest.Descriptor,
|
|
ValidateCryptographyRequest.Descriptor,
|
|
HandleCryptographyRequest.Descriptor,
|
|
ApplyCryptographyResponse<ProcessErrorContext>.Descriptor,
|
|
ApplyCryptographyResponse<ProcessRequestContext>.Descriptor,
|
|
|
|
/*
|
|
* Cryptography request handling:
|
|
*/
|
|
AttachSigningKeys.Descriptor);
|
|
|
|
/// <summary>
|
|
/// Contains the logic responsible of extracting configuration requests and invoking the corresponding event handlers.
|
|
/// </summary>
|
|
public class ExtractConfigurationRequest : IOpenIddictServerHandler<ProcessRequestContext>
|
|
{
|
|
private readonly IOpenIddictServerDispatcher _dispatcher;
|
|
|
|
public ExtractConfigurationRequest([NotNull] IOpenIddictServerDispatcher dispatcher)
|
|
=> _dispatcher = dispatcher;
|
|
|
|
/// <summary>
|
|
/// Gets the default descriptor definition assigned to this handler.
|
|
/// </summary>
|
|
public static OpenIddictServerHandlerDescriptor Descriptor { get; }
|
|
= OpenIddictServerHandlerDescriptor.CreateBuilder<ProcessRequestContext>()
|
|
.UseScopedHandler<ExtractConfigurationRequest>()
|
|
.SetOrder(100_000)
|
|
.SetType(OpenIddictServerHandlerType.BuiltIn)
|
|
.Build();
|
|
|
|
/// <summary>
|
|
/// Processes the event.
|
|
/// </summary>
|
|
/// <param name="context">The context associated with the event to process.</param>
|
|
/// <returns>
|
|
/// A <see cref="ValueTask"/> that can be used to monitor the asynchronous operation.
|
|
/// </returns>
|
|
public async ValueTask HandleAsync([NotNull] ProcessRequestContext context)
|
|
{
|
|
if (context == null)
|
|
{
|
|
throw new ArgumentNullException(nameof(context));
|
|
}
|
|
|
|
if (context.EndpointType != OpenIddictServerEndpointType.Configuration)
|
|
{
|
|
return;
|
|
}
|
|
|
|
var notification = new ExtractConfigurationRequestContext(context.Transaction);
|
|
await _dispatcher.DispatchAsync(notification);
|
|
|
|
if (notification.IsRequestHandled)
|
|
{
|
|
context.HandleRequest();
|
|
return;
|
|
}
|
|
|
|
else if (notification.IsRequestSkipped)
|
|
{
|
|
context.SkipRequest();
|
|
return;
|
|
}
|
|
|
|
else if (notification.IsRejected)
|
|
{
|
|
context.Reject(
|
|
error: notification.Error ?? Errors.InvalidRequest,
|
|
description: notification.ErrorDescription,
|
|
uri: notification.ErrorUri);
|
|
return;
|
|
}
|
|
|
|
if (notification.Request == null)
|
|
{
|
|
throw new InvalidOperationException(new StringBuilder()
|
|
.Append("The configuration request was not correctly extracted. To extract configuration requests, ")
|
|
.Append("create a class implementing 'IOpenIddictServerHandler<ExtractConfigurationRequestContext>' ")
|
|
.AppendLine("and register it using 'services.AddOpenIddict().AddServer().AddEventHandler()'.")
|
|
.ToString());
|
|
}
|
|
|
|
context.Logger.LogInformation("The configuration request was successfully extracted: {Request}.", notification.Request);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Contains the logic responsible of validating configuration requests and invoking the corresponding event handlers.
|
|
/// </summary>
|
|
public class ValidateConfigurationRequest : IOpenIddictServerHandler<ProcessRequestContext>
|
|
{
|
|
private readonly IOpenIddictServerDispatcher _dispatcher;
|
|
|
|
public ValidateConfigurationRequest([NotNull] IOpenIddictServerDispatcher dispatcher)
|
|
=> _dispatcher = dispatcher;
|
|
|
|
/// <summary>
|
|
/// Gets the default descriptor definition assigned to this handler.
|
|
/// </summary>
|
|
public static OpenIddictServerHandlerDescriptor Descriptor { get; }
|
|
= OpenIddictServerHandlerDescriptor.CreateBuilder<ProcessRequestContext>()
|
|
.UseScopedHandler<ValidateConfigurationRequest>()
|
|
.SetOrder(ExtractConfigurationRequest.Descriptor.Order + 1_000)
|
|
.SetType(OpenIddictServerHandlerType.BuiltIn)
|
|
.Build();
|
|
|
|
/// <summary>
|
|
/// Processes the event.
|
|
/// </summary>
|
|
/// <param name="context">The context associated with the event to process.</param>
|
|
/// <returns>
|
|
/// A <see cref="ValueTask"/> that can be used to monitor the asynchronous operation.
|
|
/// </returns>
|
|
public async ValueTask HandleAsync([NotNull] ProcessRequestContext context)
|
|
{
|
|
if (context == null)
|
|
{
|
|
throw new ArgumentNullException(nameof(context));
|
|
}
|
|
|
|
if (context.EndpointType != OpenIddictServerEndpointType.Configuration)
|
|
{
|
|
return;
|
|
}
|
|
|
|
var notification = new ValidateConfigurationRequestContext(context.Transaction);
|
|
await _dispatcher.DispatchAsync(notification);
|
|
|
|
if (notification.IsRequestHandled)
|
|
{
|
|
context.HandleRequest();
|
|
return;
|
|
}
|
|
|
|
else if (notification.IsRequestSkipped)
|
|
{
|
|
context.SkipRequest();
|
|
return;
|
|
}
|
|
|
|
else if (notification.IsRejected)
|
|
{
|
|
context.Reject(
|
|
error: notification.Error ?? Errors.InvalidRequest,
|
|
description: notification.ErrorDescription,
|
|
uri: notification.ErrorUri);
|
|
return;
|
|
}
|
|
|
|
context.Logger.LogInformation("The configuration request was successfully validated.");
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Contains the logic responsible of handling configuration requests and invoking the corresponding event handlers.
|
|
/// </summary>
|
|
public class HandleConfigurationRequest : IOpenIddictServerHandler<ProcessRequestContext>
|
|
{
|
|
private readonly IOpenIddictServerDispatcher _dispatcher;
|
|
|
|
public HandleConfigurationRequest([NotNull] IOpenIddictServerDispatcher dispatcher)
|
|
=> _dispatcher = dispatcher;
|
|
|
|
/// <summary>
|
|
/// Gets the default descriptor definition assigned to this handler.
|
|
/// </summary>
|
|
public static OpenIddictServerHandlerDescriptor Descriptor { get; }
|
|
= OpenIddictServerHandlerDescriptor.CreateBuilder<ProcessRequestContext>()
|
|
.UseScopedHandler<HandleConfigurationRequest>()
|
|
.SetOrder(ValidateConfigurationRequest.Descriptor.Order + 1_000)
|
|
.SetType(OpenIddictServerHandlerType.BuiltIn)
|
|
.Build();
|
|
|
|
/// <summary>
|
|
/// Processes the event.
|
|
/// </summary>
|
|
/// <param name="context">The context associated with the event to process.</param>
|
|
/// <returns>
|
|
/// A <see cref="ValueTask"/> that can be used to monitor the asynchronous operation.
|
|
/// </returns>
|
|
public async ValueTask HandleAsync([NotNull] ProcessRequestContext context)
|
|
{
|
|
if (context == null)
|
|
{
|
|
throw new ArgumentNullException(nameof(context));
|
|
}
|
|
|
|
if (context.EndpointType != OpenIddictServerEndpointType.Configuration)
|
|
{
|
|
return;
|
|
}
|
|
|
|
var notification = new HandleConfigurationRequestContext(context.Transaction);
|
|
await _dispatcher.DispatchAsync(notification);
|
|
|
|
if (notification.IsRequestHandled)
|
|
{
|
|
context.HandleRequest();
|
|
return;
|
|
}
|
|
|
|
else if (notification.IsRequestSkipped)
|
|
{
|
|
context.SkipRequest();
|
|
return;
|
|
}
|
|
|
|
else if (notification.IsRejected)
|
|
{
|
|
context.Reject(
|
|
error: notification.Error ?? Errors.InvalidRequest,
|
|
description: notification.ErrorDescription,
|
|
uri: notification.ErrorUri);
|
|
return;
|
|
}
|
|
|
|
var response = new OpenIddictResponse
|
|
{
|
|
[Metadata.Issuer] = notification.Issuer?.AbsoluteUri,
|
|
[Metadata.AuthorizationEndpoint] = notification.AuthorizationEndpoint?.AbsoluteUri,
|
|
[Metadata.TokenEndpoint] = notification.TokenEndpoint?.AbsoluteUri,
|
|
[Metadata.IntrospectionEndpoint] = notification.IntrospectionEndpoint?.AbsoluteUri,
|
|
[Metadata.EndSessionEndpoint] = notification.LogoutEndpoint?.AbsoluteUri,
|
|
[Metadata.RevocationEndpoint] = notification.RevocationEndpoint?.AbsoluteUri,
|
|
[Metadata.UserinfoEndpoint] = notification.UserinfoEndpoint?.AbsoluteUri,
|
|
[Metadata.DeviceAuthorizationEndpoint] = notification.DeviceEndpoint?.AbsoluteUri,
|
|
[Metadata.JwksUri] = notification.CryptographyEndpoint?.AbsoluteUri,
|
|
[Metadata.GrantTypesSupported] = notification.GrantTypes.ToArray(),
|
|
[Metadata.ResponseTypesSupported] = notification.ResponseTypes.ToArray(),
|
|
[Metadata.ResponseModesSupported] = notification.ResponseModes.ToArray(),
|
|
[Metadata.ScopesSupported] = notification.Scopes.ToArray(),
|
|
[Metadata.ClaimsSupported] = notification.Claims.ToArray(),
|
|
[Metadata.IdTokenSigningAlgValuesSupported] = notification.IdTokenSigningAlgorithms.ToArray(),
|
|
[Metadata.CodeChallengeMethodsSupported] = notification.CodeChallengeMethods.ToArray(),
|
|
[Metadata.SubjectTypesSupported] = notification.SubjectTypes.ToArray(),
|
|
[Metadata.TokenEndpointAuthMethodsSupported] = notification.TokenEndpointAuthenticationMethods.ToArray(),
|
|
[Metadata.IntrospectionEndpointAuthMethodsSupported] = notification.IntrospectionEndpointAuthenticationMethods.ToArray(),
|
|
[Metadata.RevocationEndpointAuthMethodsSupported] = notification.RevocationEndpointAuthenticationMethods.ToArray()
|
|
};
|
|
|
|
foreach (var metadata in notification.Metadata)
|
|
{
|
|
response.SetParameter(metadata.Key, metadata.Value);
|
|
}
|
|
|
|
context.Response = response;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Contains the logic responsible of processing configuration responses and invoking the corresponding event handlers.
|
|
/// </summary>
|
|
public class ApplyConfigurationResponse<TContext> : IOpenIddictServerHandler<TContext> where TContext : BaseRequestContext
|
|
{
|
|
private readonly IOpenIddictServerDispatcher _dispatcher;
|
|
|
|
public ApplyConfigurationResponse([NotNull] IOpenIddictServerDispatcher dispatcher)
|
|
=> _dispatcher = dispatcher;
|
|
|
|
/// <summary>
|
|
/// Gets the default descriptor definition assigned to this handler.
|
|
/// </summary>
|
|
public static OpenIddictServerHandlerDescriptor Descriptor { get; }
|
|
= OpenIddictServerHandlerDescriptor.CreateBuilder<TContext>()
|
|
.UseScopedHandler<ApplyConfigurationResponse<TContext>>()
|
|
.SetOrder(int.MaxValue - 100_000)
|
|
.SetType(OpenIddictServerHandlerType.BuiltIn)
|
|
.Build();
|
|
|
|
/// <summary>
|
|
/// Processes the event.
|
|
/// </summary>
|
|
/// <param name="context">The context associated with the event to process.</param>
|
|
/// <returns>
|
|
/// A <see cref="ValueTask"/> that can be used to monitor the asynchronous operation.
|
|
/// </returns>
|
|
public async ValueTask HandleAsync([NotNull] TContext context)
|
|
{
|
|
if (context == null)
|
|
{
|
|
throw new ArgumentNullException(nameof(context));
|
|
}
|
|
|
|
if (context.EndpointType != OpenIddictServerEndpointType.Configuration)
|
|
{
|
|
return;
|
|
}
|
|
|
|
var notification = new ApplyConfigurationResponseContext(context.Transaction);
|
|
await _dispatcher.DispatchAsync(notification);
|
|
|
|
if (notification.IsRequestHandled)
|
|
{
|
|
context.HandleRequest();
|
|
return;
|
|
}
|
|
|
|
else if (notification.IsRequestSkipped)
|
|
{
|
|
context.SkipRequest();
|
|
return;
|
|
}
|
|
|
|
throw new InvalidOperationException(new StringBuilder()
|
|
.Append("The configuration response was not correctly applied. To apply configuration responses, ")
|
|
.Append("create a class implementing 'IOpenIddictServerHandler<ApplyConfigurationResponseContext>' ")
|
|
.AppendLine("and register it using 'services.AddOpenIddict().AddServer().AddEventHandler()'.")
|
|
.ToString());
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Contains the logic responsible of attaching the endpoint URLs to the provider discovery document.
|
|
/// </summary>
|
|
public class AttachEndpoints : IOpenIddictServerHandler<HandleConfigurationRequestContext>
|
|
{
|
|
/// <summary>
|
|
/// Gets the default descriptor definition assigned to this handler.
|
|
/// </summary>
|
|
public static OpenIddictServerHandlerDescriptor Descriptor { get; }
|
|
= OpenIddictServerHandlerDescriptor.CreateBuilder<HandleConfigurationRequestContext>()
|
|
.UseSingletonHandler<AttachEndpoints>()
|
|
.SetOrder(int.MaxValue - 100_000)
|
|
.SetType(OpenIddictServerHandlerType.BuiltIn)
|
|
.Build();
|
|
|
|
/// <summary>
|
|
/// Processes the event.
|
|
/// </summary>
|
|
/// <param name="context">The context associated with the event to process.</param>
|
|
/// <returns>
|
|
/// A <see cref="ValueTask"/> that can be used to monitor the asynchronous operation.
|
|
/// </returns>
|
|
public ValueTask HandleAsync([NotNull] HandleConfigurationRequestContext context)
|
|
{
|
|
if (context == null)
|
|
{
|
|
throw new ArgumentNullException(nameof(context));
|
|
}
|
|
|
|
// Note: while OpenIddict allows specifying multiple endpoint addresses, the OAuth 2.0
|
|
// and OpenID Connect discovery specifications only allow a single address per endpoint.
|
|
|
|
context.AuthorizationEndpoint ??= GetEndpointAbsoluteUri(context.Issuer,
|
|
context.Options.AuthorizationEndpointUris.FirstOrDefault());
|
|
|
|
context.CryptographyEndpoint ??= GetEndpointAbsoluteUri(context.Issuer,
|
|
context.Options.CryptographyEndpointUris.FirstOrDefault());
|
|
|
|
context.DeviceEndpoint ??= GetEndpointAbsoluteUri(context.Issuer,
|
|
context.Options.DeviceEndpointUris.FirstOrDefault());
|
|
|
|
context.IntrospectionEndpoint ??= GetEndpointAbsoluteUri(context.Issuer,
|
|
context.Options.IntrospectionEndpointUris.FirstOrDefault());
|
|
|
|
context.LogoutEndpoint ??= GetEndpointAbsoluteUri(context.Issuer,
|
|
context.Options.LogoutEndpointUris.FirstOrDefault());
|
|
|
|
context.RevocationEndpoint ??= GetEndpointAbsoluteUri(context.Issuer,
|
|
context.Options.RevocationEndpointUris.FirstOrDefault());
|
|
|
|
context.TokenEndpoint ??= GetEndpointAbsoluteUri(context.Issuer,
|
|
context.Options.TokenEndpointUris.FirstOrDefault());
|
|
|
|
context.UserinfoEndpoint ??= GetEndpointAbsoluteUri(context.Issuer,
|
|
context.Options.UserinfoEndpointUris.FirstOrDefault());
|
|
|
|
return default;
|
|
|
|
static Uri GetEndpointAbsoluteUri(Uri issuer, Uri endpoint)
|
|
{
|
|
// If the endpoint is disabled (i.e a null address is specified), return null.
|
|
if (endpoint == null)
|
|
{
|
|
return null;
|
|
}
|
|
|
|
// If the endpoint address is already an absolute URL, return it as-is.
|
|
if (endpoint.IsAbsoluteUri)
|
|
{
|
|
return endpoint;
|
|
}
|
|
|
|
// At this stage, throw an exception if the issuer cannot be retrieved.
|
|
if (issuer == null || !issuer.IsAbsoluteUri)
|
|
{
|
|
throw new InvalidOperationException("The issuer must be a non-null, non-empty absolute URL.");
|
|
}
|
|
|
|
// Ensure the issuer ends with a trailing slash, as it is necessary
|
|
// for Uri's constructor to correctly compute correct absolute URLs.
|
|
if (!issuer.OriginalString.EndsWith("/"))
|
|
{
|
|
issuer = new Uri(issuer.OriginalString + "/", UriKind.Absolute);
|
|
}
|
|
|
|
// Ensure the endpoint does not start with a leading slash, as it is necessary
|
|
// for Uri's constructor to correctly compute correct absolute URLs.
|
|
if (endpoint.OriginalString.StartsWith("/"))
|
|
{
|
|
endpoint = new Uri(endpoint.OriginalString.Substring(1, endpoint.OriginalString.Length - 1), UriKind.Relative);
|
|
}
|
|
|
|
return new Uri(issuer, endpoint);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Contains the logic responsible of attaching the supported grant types to the provider discovery document.
|
|
/// </summary>
|
|
public class AttachGrantTypes : IOpenIddictServerHandler<HandleConfigurationRequestContext>
|
|
{
|
|
/// <summary>
|
|
/// Gets the default descriptor definition assigned to this handler.
|
|
/// </summary>
|
|
public static OpenIddictServerHandlerDescriptor Descriptor { get; }
|
|
= OpenIddictServerHandlerDescriptor.CreateBuilder<HandleConfigurationRequestContext>()
|
|
.UseSingletonHandler<AttachGrantTypes>()
|
|
.SetOrder(AttachEndpoints.Descriptor.Order + 1_000)
|
|
.SetType(OpenIddictServerHandlerType.BuiltIn)
|
|
.Build();
|
|
|
|
/// <summary>
|
|
/// Processes the event.
|
|
/// </summary>
|
|
/// <param name="context">The context associated with the event to process.</param>
|
|
/// <returns>
|
|
/// A <see cref="ValueTask"/> that can be used to monitor the asynchronous operation.
|
|
/// </returns>
|
|
public ValueTask HandleAsync([NotNull] HandleConfigurationRequestContext context)
|
|
{
|
|
if (context == null)
|
|
{
|
|
throw new ArgumentNullException(nameof(context));
|
|
}
|
|
|
|
context.GrantTypes.UnionWith(context.Options.GrantTypes);
|
|
|
|
return default;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Contains the logic responsible of attaching the supported response modes to the provider discovery document.
|
|
/// </summary>
|
|
public class AttachResponseModes : IOpenIddictServerHandler<HandleConfigurationRequestContext>
|
|
{
|
|
/// <summary>
|
|
/// Gets the default descriptor definition assigned to this handler.
|
|
/// </summary>
|
|
public static OpenIddictServerHandlerDescriptor Descriptor { get; }
|
|
= OpenIddictServerHandlerDescriptor.CreateBuilder<HandleConfigurationRequestContext>()
|
|
.UseSingletonHandler<AttachResponseModes>()
|
|
.SetOrder(AttachGrantTypes.Descriptor.Order + 1_000)
|
|
.SetType(OpenIddictServerHandlerType.BuiltIn)
|
|
.Build();
|
|
|
|
/// <summary>
|
|
/// Processes the event.
|
|
/// </summary>
|
|
/// <param name="context">The context associated with the event to process.</param>
|
|
/// <returns>
|
|
/// A <see cref="ValueTask"/> that can be used to monitor the asynchronous operation.
|
|
/// </returns>
|
|
public ValueTask HandleAsync([NotNull] HandleConfigurationRequestContext context)
|
|
{
|
|
if (context == null)
|
|
{
|
|
throw new ArgumentNullException(nameof(context));
|
|
}
|
|
|
|
context.ResponseModes.UnionWith(context.Options.ResponseModes);
|
|
|
|
return default;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Contains the logic responsible of attaching the supported response types to the provider discovery document.
|
|
/// </summary>
|
|
public class AttachResponseTypes : IOpenIddictServerHandler<HandleConfigurationRequestContext>
|
|
{
|
|
/// <summary>
|
|
/// Gets the default descriptor definition assigned to this handler.
|
|
/// </summary>
|
|
public static OpenIddictServerHandlerDescriptor Descriptor { get; }
|
|
= OpenIddictServerHandlerDescriptor.CreateBuilder<HandleConfigurationRequestContext>()
|
|
.UseSingletonHandler<AttachResponseTypes>()
|
|
.SetOrder(AttachResponseModes.Descriptor.Order + 1_000)
|
|
.SetType(OpenIddictServerHandlerType.BuiltIn)
|
|
.Build();
|
|
|
|
/// <summary>
|
|
/// Processes the event.
|
|
/// </summary>
|
|
/// <param name="context">The context associated with the event to process.</param>
|
|
/// <returns>
|
|
/// A <see cref="ValueTask"/> that can be used to monitor the asynchronous operation.
|
|
/// </returns>
|
|
public ValueTask HandleAsync([NotNull] HandleConfigurationRequestContext context)
|
|
{
|
|
if (context == null)
|
|
{
|
|
throw new ArgumentNullException(nameof(context));
|
|
}
|
|
|
|
context.ResponseTypes.UnionWith(context.Options.ResponseTypes);
|
|
|
|
return default;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Contains the logic responsible of attaching the supported client
|
|
/// authentication methods to the provider discovery document.
|
|
/// </summary>
|
|
public class AttachClientAuthenticationMethods : IOpenIddictServerHandler<HandleConfigurationRequestContext>
|
|
{
|
|
/// <summary>
|
|
/// Gets the default descriptor definition assigned to this handler.
|
|
/// </summary>
|
|
public static OpenIddictServerHandlerDescriptor Descriptor { get; }
|
|
= OpenIddictServerHandlerDescriptor.CreateBuilder<HandleConfigurationRequestContext>()
|
|
.UseSingletonHandler<AttachClientAuthenticationMethods>()
|
|
.SetOrder(AttachResponseTypes.Descriptor.Order + 1_000)
|
|
.SetType(OpenIddictServerHandlerType.BuiltIn)
|
|
.Build();
|
|
|
|
/// <summary>
|
|
/// Processes the event.
|
|
/// </summary>
|
|
/// <param name="context">The context associated with the event to process.</param>
|
|
/// <returns>
|
|
/// A <see cref="ValueTask"/> that can be used to monitor the asynchronous operation.
|
|
/// </returns>
|
|
public ValueTask HandleAsync([NotNull] HandleConfigurationRequestContext context)
|
|
{
|
|
if (context == null)
|
|
{
|
|
throw new ArgumentNullException(nameof(context));
|
|
}
|
|
|
|
if (context.IntrospectionEndpoint != null)
|
|
{
|
|
context.IntrospectionEndpointAuthenticationMethods.Add(ClientAuthenticationMethods.ClientSecretBasic);
|
|
context.IntrospectionEndpointAuthenticationMethods.Add(ClientAuthenticationMethods.ClientSecretPost);
|
|
}
|
|
|
|
if (context.RevocationEndpoint != null)
|
|
{
|
|
context.RevocationEndpointAuthenticationMethods.Add(ClientAuthenticationMethods.ClientSecretBasic);
|
|
context.RevocationEndpointAuthenticationMethods.Add(ClientAuthenticationMethods.ClientSecretPost);
|
|
}
|
|
|
|
if (context.TokenEndpoint != null)
|
|
{
|
|
context.TokenEndpointAuthenticationMethods.Add(ClientAuthenticationMethods.ClientSecretBasic);
|
|
context.TokenEndpointAuthenticationMethods.Add(ClientAuthenticationMethods.ClientSecretPost);
|
|
}
|
|
|
|
return default;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Contains the logic responsible of attaching the supported
|
|
/// code challenge methods to the provider discovery document.
|
|
/// </summary>
|
|
public class AttachCodeChallengeMethods : IOpenIddictServerHandler<HandleConfigurationRequestContext>
|
|
{
|
|
/// <summary>
|
|
/// Gets the default descriptor definition assigned to this handler.
|
|
/// </summary>
|
|
public static OpenIddictServerHandlerDescriptor Descriptor { get; }
|
|
= OpenIddictServerHandlerDescriptor.CreateBuilder<HandleConfigurationRequestContext>()
|
|
.UseSingletonHandler<AttachCodeChallengeMethods>()
|
|
.SetOrder(AttachClientAuthenticationMethods.Descriptor.Order + 1_000)
|
|
.SetType(OpenIddictServerHandlerType.BuiltIn)
|
|
.Build();
|
|
|
|
/// <summary>
|
|
/// Processes the event.
|
|
/// </summary>
|
|
/// <param name="context">The context associated with the event to process.</param>
|
|
/// <returns>
|
|
/// A <see cref="ValueTask"/> that can be used to monitor the asynchronous operation.
|
|
/// </returns>
|
|
public ValueTask HandleAsync([NotNull] HandleConfigurationRequestContext context)
|
|
{
|
|
if (context == null)
|
|
{
|
|
throw new ArgumentNullException(nameof(context));
|
|
}
|
|
|
|
context.CodeChallengeMethods.UnionWith(context.Options.CodeChallengeMethods);
|
|
|
|
return default;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Contains the logic responsible of attaching the supported response types to the provider discovery document.
|
|
/// </summary>
|
|
public class AttachScopes : IOpenIddictServerHandler<HandleConfigurationRequestContext>
|
|
{
|
|
/// <summary>
|
|
/// Gets the default descriptor definition assigned to this handler.
|
|
/// </summary>
|
|
public static OpenIddictServerHandlerDescriptor Descriptor { get; }
|
|
= OpenIddictServerHandlerDescriptor.CreateBuilder<HandleConfigurationRequestContext>()
|
|
.UseSingletonHandler<AttachScopes>()
|
|
.SetOrder(AttachCodeChallengeMethods.Descriptor.Order + 1_000)
|
|
.SetType(OpenIddictServerHandlerType.BuiltIn)
|
|
.Build();
|
|
|
|
/// <summary>
|
|
/// Processes the event.
|
|
/// </summary>
|
|
/// <param name="context">The context associated with the event to process.</param>
|
|
/// <returns>
|
|
/// A <see cref="ValueTask"/> that can be used to monitor the asynchronous operation.
|
|
/// </returns>
|
|
public ValueTask HandleAsync([NotNull] HandleConfigurationRequestContext context)
|
|
{
|
|
if (context == null)
|
|
{
|
|
throw new ArgumentNullException(nameof(context));
|
|
}
|
|
|
|
context.Scopes.UnionWith(context.Options.Scopes);
|
|
|
|
return default;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Contains the logic responsible of attaching the supported claims to the provider discovery document.
|
|
/// </summary>
|
|
public class AttachClaims : IOpenIddictServerHandler<HandleConfigurationRequestContext>
|
|
{
|
|
/// <summary>
|
|
/// Gets the default descriptor definition assigned to this handler.
|
|
/// </summary>
|
|
public static OpenIddictServerHandlerDescriptor Descriptor { get; }
|
|
= OpenIddictServerHandlerDescriptor.CreateBuilder<HandleConfigurationRequestContext>()
|
|
.UseSingletonHandler<AttachClaims>()
|
|
.SetOrder(AttachScopes.Descriptor.Order + 1_000)
|
|
.SetType(OpenIddictServerHandlerType.BuiltIn)
|
|
.Build();
|
|
|
|
/// <summary>
|
|
/// Processes the event.
|
|
/// </summary>
|
|
/// <param name="context">The context associated with the event to process.</param>
|
|
/// <returns>
|
|
/// A <see cref="ValueTask"/> that can be used to monitor the asynchronous operation.
|
|
/// </returns>
|
|
public ValueTask HandleAsync([NotNull] HandleConfigurationRequestContext context)
|
|
{
|
|
if (context == null)
|
|
{
|
|
throw new ArgumentNullException(nameof(context));
|
|
}
|
|
|
|
context.Claims.UnionWith(context.Options.Claims);
|
|
|
|
return default;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Contains the logic responsible of attaching the supported subject types to the provider discovery document.
|
|
/// </summary>
|
|
public class AttachSubjectTypes : IOpenIddictServerHandler<HandleConfigurationRequestContext>
|
|
{
|
|
/// <summary>
|
|
/// Gets the default descriptor definition assigned to this handler.
|
|
/// </summary>
|
|
public static OpenIddictServerHandlerDescriptor Descriptor { get; }
|
|
= OpenIddictServerHandlerDescriptor.CreateBuilder<HandleConfigurationRequestContext>()
|
|
.UseSingletonHandler<AttachSubjectTypes>()
|
|
.SetOrder(AttachClaims.Descriptor.Order + 1_000)
|
|
.SetType(OpenIddictServerHandlerType.BuiltIn)
|
|
.Build();
|
|
|
|
/// <summary>
|
|
/// Processes the event.
|
|
/// </summary>
|
|
/// <param name="context">The context associated with the event to process.</param>
|
|
/// <returns>
|
|
/// A <see cref="ValueTask"/> that can be used to monitor the asynchronous operation.
|
|
/// </returns>
|
|
public ValueTask HandleAsync([NotNull] HandleConfigurationRequestContext context)
|
|
{
|
|
if (context == null)
|
|
{
|
|
throw new ArgumentNullException(nameof(context));
|
|
}
|
|
|
|
context.SubjectTypes.Add(SubjectTypes.Public);
|
|
|
|
return default;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Contains the logic responsible of attaching the supported signing algorithms to the provider discovery document.
|
|
/// </summary>
|
|
public class AttachSigningAlgorithms : IOpenIddictServerHandler<HandleConfigurationRequestContext>
|
|
{
|
|
/// <summary>
|
|
/// Gets the default descriptor definition assigned to this handler.
|
|
/// </summary>
|
|
public static OpenIddictServerHandlerDescriptor Descriptor { get; }
|
|
= OpenIddictServerHandlerDescriptor.CreateBuilder<HandleConfigurationRequestContext>()
|
|
.UseSingletonHandler<AttachSigningAlgorithms>()
|
|
.SetOrder(AttachSubjectTypes.Descriptor.Order + 1_000)
|
|
.SetType(OpenIddictServerHandlerType.BuiltIn)
|
|
.Build();
|
|
|
|
/// <summary>
|
|
/// Processes the event.
|
|
/// </summary>
|
|
/// <param name="context">The context associated with the event to process.</param>
|
|
/// <returns>
|
|
/// A <see cref="ValueTask"/> that can be used to monitor the asynchronous operation.
|
|
/// </returns>
|
|
public ValueTask HandleAsync([NotNull] HandleConfigurationRequestContext context)
|
|
{
|
|
if (context == null)
|
|
{
|
|
throw new ArgumentNullException(nameof(context));
|
|
}
|
|
|
|
foreach (var credentials in context.Options.SigningCredentials)
|
|
{
|
|
// Try to resolve the JWA algorithm short name.
|
|
var algorithm = credentials.Algorithm switch
|
|
{
|
|
#if SUPPORTS_ECDSA
|
|
SecurityAlgorithms.EcdsaSha256 => SecurityAlgorithms.EcdsaSha256,
|
|
SecurityAlgorithms.EcdsaSha384 => SecurityAlgorithms.EcdsaSha384,
|
|
SecurityAlgorithms.EcdsaSha512 => SecurityAlgorithms.EcdsaSha512,
|
|
SecurityAlgorithms.EcdsaSha256Signature => SecurityAlgorithms.EcdsaSha256,
|
|
SecurityAlgorithms.EcdsaSha384Signature => SecurityAlgorithms.EcdsaSha384,
|
|
SecurityAlgorithms.EcdsaSha512Signature => SecurityAlgorithms.EcdsaSha512,
|
|
#endif
|
|
SecurityAlgorithms.RsaSha256 => SecurityAlgorithms.RsaSha256,
|
|
SecurityAlgorithms.RsaSha384 => SecurityAlgorithms.RsaSha384,
|
|
SecurityAlgorithms.RsaSha512 => SecurityAlgorithms.RsaSha512,
|
|
SecurityAlgorithms.RsaSha256Signature => SecurityAlgorithms.RsaSha256,
|
|
SecurityAlgorithms.RsaSha384Signature => SecurityAlgorithms.RsaSha384,
|
|
SecurityAlgorithms.RsaSha512Signature => SecurityAlgorithms.RsaSha512,
|
|
|
|
SecurityAlgorithms.RsaSsaPssSha256 => SecurityAlgorithms.RsaSsaPssSha256,
|
|
SecurityAlgorithms.RsaSsaPssSha384 => SecurityAlgorithms.RsaSsaPssSha384,
|
|
SecurityAlgorithms.RsaSsaPssSha512 => SecurityAlgorithms.RsaSsaPssSha512,
|
|
SecurityAlgorithms.RsaSsaPssSha256Signature => SecurityAlgorithms.RsaSsaPssSha256,
|
|
SecurityAlgorithms.RsaSsaPssSha384Signature => SecurityAlgorithms.RsaSsaPssSha384,
|
|
SecurityAlgorithms.RsaSsaPssSha512Signature => SecurityAlgorithms.RsaSsaPssSha512,
|
|
|
|
_ => null
|
|
};
|
|
|
|
// If the algorithm cannot be resolved, ignore it.
|
|
if (string.IsNullOrEmpty(algorithm))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
context.IdTokenSigningAlgorithms.Add(algorithm);
|
|
}
|
|
|
|
return default;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Contains the logic responsible of attaching additional metadata to the provider discovery document.
|
|
/// </summary>
|
|
public class AttachAdditionalMetadata : IOpenIddictServerHandler<HandleConfigurationRequestContext>
|
|
{
|
|
/// <summary>
|
|
/// Gets the default descriptor definition assigned to this handler.
|
|
/// </summary>
|
|
public static OpenIddictServerHandlerDescriptor Descriptor { get; }
|
|
= OpenIddictServerHandlerDescriptor.CreateBuilder<HandleConfigurationRequestContext>()
|
|
.UseSingletonHandler<AttachAdditionalMetadata>()
|
|
.SetOrder(AttachSigningAlgorithms.Descriptor.Order + 1_000)
|
|
.SetType(OpenIddictServerHandlerType.BuiltIn)
|
|
.Build();
|
|
|
|
/// <summary>
|
|
/// Processes the event.
|
|
/// </summary>
|
|
/// <param name="context">The context associated with the event to process.</param>
|
|
/// <returns>
|
|
/// A <see cref="ValueTask"/> that can be used to monitor the asynchronous operation.
|
|
/// </returns>
|
|
public ValueTask HandleAsync([NotNull] HandleConfigurationRequestContext context)
|
|
{
|
|
if (context == null)
|
|
{
|
|
throw new ArgumentNullException(nameof(context));
|
|
}
|
|
|
|
// Note: the optional claims/request/request_uri parameters are not yet supported
|
|
// by OpenIddict, so "false" is returned to encourage clients not to use them.
|
|
context.Metadata[Metadata.ClaimsParameterSupported] = false;
|
|
context.Metadata[Metadata.RequestParameterSupported] = false;
|
|
context.Metadata[Metadata.RequestUriParameterSupported] = false;
|
|
|
|
return default;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Contains the logic responsible of extracting cryptography requests and invoking the corresponding event handlers.
|
|
/// </summary>
|
|
public class ExtractCryptographyRequest : IOpenIddictServerHandler<ProcessRequestContext>
|
|
{
|
|
private readonly IOpenIddictServerDispatcher _dispatcher;
|
|
|
|
public ExtractCryptographyRequest([NotNull] IOpenIddictServerDispatcher dispatcher)
|
|
=> _dispatcher = dispatcher;
|
|
|
|
/// <summary>
|
|
/// Gets the default descriptor definition assigned to this handler.
|
|
/// </summary>
|
|
public static OpenIddictServerHandlerDescriptor Descriptor { get; }
|
|
= OpenIddictServerHandlerDescriptor.CreateBuilder<ProcessRequestContext>()
|
|
.UseScopedHandler<ExtractCryptographyRequest>()
|
|
.SetOrder(100_000)
|
|
.SetType(OpenIddictServerHandlerType.BuiltIn)
|
|
.Build();
|
|
|
|
/// <summary>
|
|
/// Processes the event.
|
|
/// </summary>
|
|
/// <param name="context">The context associated with the event to process.</param>
|
|
/// <returns>
|
|
/// A <see cref="ValueTask"/> that can be used to monitor the asynchronous operation.
|
|
/// </returns>
|
|
public async ValueTask HandleAsync([NotNull] ProcessRequestContext context)
|
|
{
|
|
if (context == null)
|
|
{
|
|
throw new ArgumentNullException(nameof(context));
|
|
}
|
|
|
|
if (context.EndpointType != OpenIddictServerEndpointType.Cryptography)
|
|
{
|
|
return;
|
|
}
|
|
|
|
var notification = new ExtractCryptographyRequestContext(context.Transaction);
|
|
await _dispatcher.DispatchAsync(notification);
|
|
|
|
if (notification.IsRequestHandled)
|
|
{
|
|
context.HandleRequest();
|
|
return;
|
|
}
|
|
|
|
else if (notification.IsRequestSkipped)
|
|
{
|
|
context.SkipRequest();
|
|
return;
|
|
}
|
|
|
|
else if (notification.IsRejected)
|
|
{
|
|
context.Reject(
|
|
error: notification.Error ?? Errors.InvalidRequest,
|
|
description: notification.ErrorDescription,
|
|
uri: notification.ErrorUri);
|
|
return;
|
|
}
|
|
|
|
if (notification.Request == null)
|
|
{
|
|
throw new InvalidOperationException(new StringBuilder()
|
|
.Append("The cryptography request was not correctly extracted. To extract configuration requests, ")
|
|
.Append("create a class implementing 'IOpenIddictServerHandler<ExtractCryptographyRequestContext>' ")
|
|
.AppendLine("and register it using 'services.AddOpenIddict().AddServer().AddEventHandler()'.")
|
|
.ToString());
|
|
}
|
|
|
|
context.Logger.LogInformation("The cryptography request was successfully extracted: {Request}.", notification.Request);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Contains the logic responsible of validating cryptography requests and invoking the corresponding event handlers.
|
|
/// </summary>
|
|
public class ValidateCryptographyRequest : IOpenIddictServerHandler<ProcessRequestContext>
|
|
{
|
|
private readonly IOpenIddictServerDispatcher _dispatcher;
|
|
|
|
public ValidateCryptographyRequest([NotNull] IOpenIddictServerDispatcher dispatcher)
|
|
=> _dispatcher = dispatcher;
|
|
|
|
/// <summary>
|
|
/// Gets the default descriptor definition assigned to this handler.
|
|
/// </summary>
|
|
public static OpenIddictServerHandlerDescriptor Descriptor { get; }
|
|
= OpenIddictServerHandlerDescriptor.CreateBuilder<ProcessRequestContext>()
|
|
.UseScopedHandler<ValidateCryptographyRequest>()
|
|
.SetOrder(ExtractCryptographyRequest.Descriptor.Order + 1_000)
|
|
.SetType(OpenIddictServerHandlerType.BuiltIn)
|
|
.Build();
|
|
|
|
/// <summary>
|
|
/// Processes the event.
|
|
/// </summary>
|
|
/// <param name="context">The context associated with the event to process.</param>
|
|
/// <returns>
|
|
/// A <see cref="ValueTask"/> that can be used to monitor the asynchronous operation.
|
|
/// </returns>
|
|
public async ValueTask HandleAsync([NotNull] ProcessRequestContext context)
|
|
{
|
|
if (context == null)
|
|
{
|
|
throw new ArgumentNullException(nameof(context));
|
|
}
|
|
|
|
if (context.EndpointType != OpenIddictServerEndpointType.Cryptography)
|
|
{
|
|
return;
|
|
}
|
|
|
|
var notification = new ValidateCryptographyRequestContext(context.Transaction);
|
|
await _dispatcher.DispatchAsync(notification);
|
|
|
|
if (notification.IsRequestHandled)
|
|
{
|
|
context.HandleRequest();
|
|
return;
|
|
}
|
|
|
|
else if (notification.IsRequestSkipped)
|
|
{
|
|
context.SkipRequest();
|
|
return;
|
|
}
|
|
|
|
else if (notification.IsRejected)
|
|
{
|
|
context.Reject(
|
|
error: notification.Error ?? Errors.InvalidRequest,
|
|
description: notification.ErrorDescription,
|
|
uri: notification.ErrorUri);
|
|
return;
|
|
}
|
|
|
|
context.Logger.LogInformation("The cryptography request was successfully validated.");
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Contains the logic responsible of handling cryptography requests and invoking the corresponding event handlers.
|
|
/// </summary>
|
|
public class HandleCryptographyRequest : IOpenIddictServerHandler<ProcessRequestContext>
|
|
{
|
|
private readonly IOpenIddictServerDispatcher _dispatcher;
|
|
|
|
public HandleCryptographyRequest([NotNull] IOpenIddictServerDispatcher dispatcher)
|
|
=> _dispatcher = dispatcher;
|
|
|
|
/// <summary>
|
|
/// Gets the default descriptor definition assigned to this handler.
|
|
/// </summary>
|
|
public static OpenIddictServerHandlerDescriptor Descriptor { get; }
|
|
= OpenIddictServerHandlerDescriptor.CreateBuilder<ProcessRequestContext>()
|
|
.UseScopedHandler<HandleCryptographyRequest>()
|
|
.SetOrder(ValidateCryptographyRequest.Descriptor.Order + 1_000)
|
|
.SetType(OpenIddictServerHandlerType.BuiltIn)
|
|
.Build();
|
|
|
|
/// <summary>
|
|
/// Processes the event.
|
|
/// </summary>
|
|
/// <param name="context">The context associated with the event to process.</param>
|
|
/// <returns>
|
|
/// A <see cref="ValueTask"/> that can be used to monitor the asynchronous operation.
|
|
/// </returns>
|
|
public async ValueTask HandleAsync([NotNull] ProcessRequestContext context)
|
|
{
|
|
if (context == null)
|
|
{
|
|
throw new ArgumentNullException(nameof(context));
|
|
}
|
|
|
|
if (context.EndpointType != OpenIddictServerEndpointType.Cryptography)
|
|
{
|
|
return;
|
|
}
|
|
|
|
var notification = new HandleCryptographyRequestContext(context.Transaction);
|
|
await _dispatcher.DispatchAsync(notification);
|
|
|
|
if (notification.IsRequestHandled)
|
|
{
|
|
context.HandleRequest();
|
|
return;
|
|
}
|
|
|
|
else if (notification.IsRequestSkipped)
|
|
{
|
|
context.SkipRequest();
|
|
return;
|
|
}
|
|
|
|
else if (notification.IsRejected)
|
|
{
|
|
context.Reject(
|
|
error: notification.Error ?? Errors.InvalidRequest,
|
|
description: notification.ErrorDescription,
|
|
uri: notification.ErrorUri);
|
|
return;
|
|
}
|
|
|
|
using var stream = new MemoryStream();
|
|
using var writer = new Utf8JsonWriter(stream);
|
|
|
|
writer.WriteStartArray();
|
|
|
|
foreach (var key in notification.Keys)
|
|
{
|
|
// Ensure a key type has been provided.
|
|
// See https://tools.ietf.org/html/rfc7517#section-4.1
|
|
if (string.IsNullOrEmpty(key.Kty))
|
|
{
|
|
context.Logger.LogError("A JSON Web Key was excluded from the key set because " +
|
|
"it didn't contain the mandatory 'kid' parameter.");
|
|
|
|
continue;
|
|
}
|
|
|
|
writer.WriteStartObject();
|
|
|
|
if (!string.IsNullOrEmpty(key.Kid)) writer.WriteString(JsonWebKeyParameterNames.Kid, key.Kid);
|
|
if (!string.IsNullOrEmpty(key.Use)) writer.WriteString(JsonWebKeyParameterNames.Use, key.Use);
|
|
if (!string.IsNullOrEmpty(key.Kty)) writer.WriteString(JsonWebKeyParameterNames.Kty, key.Kty);
|
|
if (!string.IsNullOrEmpty(key.Alg)) writer.WriteString(JsonWebKeyParameterNames.Alg, key.Alg);
|
|
if (!string.IsNullOrEmpty(key.Crv)) writer.WriteString(JsonWebKeyParameterNames.Crv, key.Crv);
|
|
if (!string.IsNullOrEmpty(key.E)) writer.WriteString(JsonWebKeyParameterNames.E, key.E);
|
|
if (!string.IsNullOrEmpty(key.N)) writer.WriteString(JsonWebKeyParameterNames.N, key.N);
|
|
if (!string.IsNullOrEmpty(key.X)) writer.WriteString(JsonWebKeyParameterNames.X, key.X);
|
|
if (!string.IsNullOrEmpty(key.Y)) writer.WriteString(JsonWebKeyParameterNames.Y, key.Y);
|
|
if (!string.IsNullOrEmpty(key.X5t)) writer.WriteString(JsonWebKeyParameterNames.X5t, key.X5t);
|
|
if (!string.IsNullOrEmpty(key.X5u)) writer.WriteString(JsonWebKeyParameterNames.X5u, key.X5u);
|
|
|
|
if (key.KeyOps.Count != 0)
|
|
{
|
|
writer.WritePropertyName(JsonWebKeyParameterNames.KeyOps);
|
|
writer.WriteStartArray();
|
|
|
|
for (var index = 0; index < key.KeyOps.Count; index++)
|
|
{
|
|
writer.WriteStringValue(key.KeyOps[index]);
|
|
}
|
|
|
|
writer.WriteEndArray();
|
|
}
|
|
|
|
if (key.X5c.Count != 0)
|
|
{
|
|
writer.WritePropertyName(JsonWebKeyParameterNames.X5c);
|
|
writer.WriteStartArray();
|
|
|
|
for (var index = 0; index < key.X5c.Count; index++)
|
|
{
|
|
writer.WriteStringValue(key.X5c[index]);
|
|
}
|
|
|
|
writer.WriteEndArray();
|
|
}
|
|
|
|
writer.WriteEndObject();
|
|
}
|
|
|
|
writer.WriteEndArray();
|
|
writer.Flush();
|
|
stream.Seek(0L, SeekOrigin.Begin);
|
|
|
|
using var document = JsonDocument.Parse(stream);
|
|
|
|
// Note: AddParameter() is used here to ensure the mandatory "keys" node
|
|
// is returned to the caller, even if the key set doesn't expose any key.
|
|
// See https://tools.ietf.org/html/rfc7517#section-5 for more information.
|
|
var response = new OpenIddictResponse();
|
|
response.AddParameter(Parameters.Keys, document.RootElement.Clone());
|
|
|
|
context.Response = response;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Contains the logic responsible of processing cryptography responses and invoking the corresponding event handlers.
|
|
/// </summary>
|
|
public class ApplyCryptographyResponse<TContext> : IOpenIddictServerHandler<TContext> where TContext : BaseRequestContext
|
|
{
|
|
private readonly IOpenIddictServerDispatcher _dispatcher;
|
|
|
|
public ApplyCryptographyResponse([NotNull] IOpenIddictServerDispatcher dispatcher)
|
|
=> _dispatcher = dispatcher;
|
|
|
|
/// <summary>
|
|
/// Gets the default descriptor definition assigned to this handler.
|
|
/// </summary>
|
|
public static OpenIddictServerHandlerDescriptor Descriptor { get; }
|
|
= OpenIddictServerHandlerDescriptor.CreateBuilder<TContext>()
|
|
.UseScopedHandler<ApplyCryptographyResponse<TContext>>()
|
|
.SetOrder(int.MaxValue - 100_000)
|
|
.SetType(OpenIddictServerHandlerType.BuiltIn)
|
|
.Build();
|
|
|
|
/// <summary>
|
|
/// Processes the event.
|
|
/// </summary>
|
|
/// <param name="context">The context associated with the event to process.</param>
|
|
/// <returns>
|
|
/// A <see cref="ValueTask"/> that can be used to monitor the asynchronous operation.
|
|
/// </returns>
|
|
public async ValueTask HandleAsync([NotNull] TContext context)
|
|
{
|
|
if (context == null)
|
|
{
|
|
throw new ArgumentNullException(nameof(context));
|
|
}
|
|
|
|
if (context.EndpointType != OpenIddictServerEndpointType.Cryptography)
|
|
{
|
|
return;
|
|
}
|
|
|
|
var notification = new ApplyCryptographyResponseContext(context.Transaction);
|
|
await _dispatcher.DispatchAsync(notification);
|
|
|
|
if (notification.IsRequestHandled)
|
|
{
|
|
context.HandleRequest();
|
|
return;
|
|
}
|
|
|
|
else if (notification.IsRequestSkipped)
|
|
{
|
|
context.SkipRequest();
|
|
return;
|
|
}
|
|
|
|
throw new InvalidOperationException(new StringBuilder()
|
|
.Append("The cryptography response was not correctly applied. To apply cryptography responses, ")
|
|
.Append("create a class implementing 'IOpenIddictServerHandler<ApplyCryptographyResponseContext>' ")
|
|
.AppendLine("and register it using 'services.AddOpenIddict().AddServer().AddEventHandler()'.")
|
|
.ToString());
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Contains the logic responsible of attaching the signing keys to the JWKS document.
|
|
/// </summary>
|
|
public class AttachSigningKeys : IOpenIddictServerHandler<HandleCryptographyRequestContext>
|
|
{
|
|
/// <summary>
|
|
/// Gets the default descriptor definition assigned to this handler.
|
|
/// </summary>
|
|
public static OpenIddictServerHandlerDescriptor Descriptor { get; }
|
|
= OpenIddictServerHandlerDescriptor.CreateBuilder<HandleCryptographyRequestContext>()
|
|
.UseSingletonHandler<AttachSigningKeys>()
|
|
.SetOrder(int.MinValue + 100_000)
|
|
.SetType(OpenIddictServerHandlerType.BuiltIn)
|
|
.Build();
|
|
|
|
/// <summary>
|
|
/// Processes the event.
|
|
/// </summary>
|
|
/// <param name="context">The context associated with the event to process.</param>
|
|
/// <returns>
|
|
/// A <see cref="ValueTask"/> that can be used to monitor the asynchronous operation.
|
|
/// </returns>
|
|
public ValueTask HandleAsync([NotNull] HandleCryptographyRequestContext context)
|
|
{
|
|
if (context == null)
|
|
{
|
|
throw new ArgumentNullException(nameof(context));
|
|
}
|
|
|
|
foreach (var credentials in context.Options.SigningCredentials)
|
|
{
|
|
#if SUPPORTS_ECDSA
|
|
if (!credentials.Key.IsSupportedAlgorithm(SecurityAlgorithms.RsaSha256) &&
|
|
!credentials.Key.IsSupportedAlgorithm(SecurityAlgorithms.RsaSsaPssSha256) &&
|
|
!credentials.Key.IsSupportedAlgorithm(SecurityAlgorithms.EcdsaSha256) &&
|
|
!credentials.Key.IsSupportedAlgorithm(SecurityAlgorithms.EcdsaSha384) &&
|
|
!credentials.Key.IsSupportedAlgorithm(SecurityAlgorithms.EcdsaSha512))
|
|
{
|
|
context.Logger.LogInformation("An unsupported signing key of type '{Type}' was ignored and excluded " +
|
|
"from the key set. Only RSA and ECDSA asymmetric security keys can be " +
|
|
"exposed via the JWKS endpoint.", credentials.Key.GetType().Name);
|
|
|
|
continue;
|
|
}
|
|
#else
|
|
if (!credentials.Key.IsSupportedAlgorithm(SecurityAlgorithms.RsaSha256) &&
|
|
!credentials.Key.IsSupportedAlgorithm(SecurityAlgorithms.RsaSsaPssSha256))
|
|
{
|
|
context.Logger.LogInformation("An unsupported signing key of type '{Type}' was ignored and excluded " +
|
|
"from the key set. Only RSA asymmetric security keys can be exposed " +
|
|
"via the JWKS endpoint.", credentials.Key.GetType().Name);
|
|
|
|
continue;
|
|
}
|
|
#endif
|
|
|
|
var key = new JsonWebKey
|
|
{
|
|
Use = JsonWebKeyUseNames.Sig,
|
|
|
|
// Resolve the JWA identifier from the algorithm specified in the credentials.
|
|
Alg = credentials.Algorithm switch
|
|
{
|
|
#if SUPPORTS_ECDSA
|
|
SecurityAlgorithms.EcdsaSha256 => SecurityAlgorithms.EcdsaSha256,
|
|
SecurityAlgorithms.EcdsaSha384 => SecurityAlgorithms.EcdsaSha384,
|
|
SecurityAlgorithms.EcdsaSha512 => SecurityAlgorithms.EcdsaSha512,
|
|
SecurityAlgorithms.EcdsaSha256Signature => SecurityAlgorithms.EcdsaSha256,
|
|
SecurityAlgorithms.EcdsaSha384Signature => SecurityAlgorithms.EcdsaSha384,
|
|
SecurityAlgorithms.EcdsaSha512Signature => SecurityAlgorithms.EcdsaSha512,
|
|
#endif
|
|
SecurityAlgorithms.RsaSha256 => SecurityAlgorithms.RsaSha256,
|
|
SecurityAlgorithms.RsaSha384 => SecurityAlgorithms.RsaSha384,
|
|
SecurityAlgorithms.RsaSha512 => SecurityAlgorithms.RsaSha512,
|
|
SecurityAlgorithms.RsaSha256Signature => SecurityAlgorithms.RsaSha256,
|
|
SecurityAlgorithms.RsaSha384Signature => SecurityAlgorithms.RsaSha384,
|
|
SecurityAlgorithms.RsaSha512Signature => SecurityAlgorithms.RsaSha512,
|
|
|
|
SecurityAlgorithms.RsaSsaPssSha256 => SecurityAlgorithms.RsaSsaPssSha256,
|
|
SecurityAlgorithms.RsaSsaPssSha384 => SecurityAlgorithms.RsaSsaPssSha384,
|
|
SecurityAlgorithms.RsaSsaPssSha512 => SecurityAlgorithms.RsaSsaPssSha512,
|
|
SecurityAlgorithms.RsaSsaPssSha256Signature => SecurityAlgorithms.RsaSsaPssSha256,
|
|
SecurityAlgorithms.RsaSsaPssSha384Signature => SecurityAlgorithms.RsaSsaPssSha384,
|
|
SecurityAlgorithms.RsaSsaPssSha512Signature => SecurityAlgorithms.RsaSsaPssSha512,
|
|
|
|
_ => null
|
|
},
|
|
|
|
// Use the key identifier specified in the signing credentials.
|
|
Kid = credentials.Kid
|
|
};
|
|
|
|
if (credentials.Key.IsSupportedAlgorithm(SecurityAlgorithms.RsaSha256) ||
|
|
credentials.Key.IsSupportedAlgorithm(SecurityAlgorithms.RsaSsaPssSha256))
|
|
{
|
|
// Note: IdentityModel 5 doesn't expose a method allowing to retrieve the underlying algorithm
|
|
// from a generic asymmetric security key. To work around this limitation, try to cast
|
|
// the security key to the built-in IdentityModel types to extract the required RSA instance.
|
|
// See https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/issues/395.
|
|
|
|
var parameters = credentials.Key switch
|
|
{
|
|
X509SecurityKey x509SecurityKey when x509SecurityKey.PublicKey is RSA algorithm =>
|
|
algorithm.ExportParameters(includePrivateParameters: false),
|
|
|
|
RsaSecurityKey rsaSecurityKey when rsaSecurityKey.Rsa != null =>
|
|
rsaSecurityKey.Rsa.ExportParameters(includePrivateParameters: false),
|
|
|
|
RsaSecurityKey rsaSecurityKey => rsaSecurityKey.Parameters,
|
|
|
|
_ => (RSAParameters?) null
|
|
};
|
|
|
|
if (parameters == null)
|
|
{
|
|
context.Logger.LogWarning("A signing key of type '{Type}' was ignored because its RSA public " +
|
|
"parameters couldn't be extracted.", credentials.Key.GetType().Name);
|
|
|
|
continue;
|
|
}
|
|
|
|
Debug.Assert(parameters.Value.Exponent != null &&
|
|
parameters.Value.Modulus != null,
|
|
"RSA.ExportParameters() shouldn't return a null exponent/modulus.");
|
|
|
|
key.Kty = JsonWebAlgorithmsKeyTypes.RSA;
|
|
|
|
// Note: both E and N must be base64url-encoded.
|
|
// See https://tools.ietf.org/html/rfc7518#section-6.3.1.1
|
|
key.E = Base64UrlEncoder.Encode(parameters.Value.Exponent);
|
|
key.N = Base64UrlEncoder.Encode(parameters.Value.Modulus);
|
|
}
|
|
|
|
#if SUPPORTS_ECDSA
|
|
else if (credentials.Key.IsSupportedAlgorithm(SecurityAlgorithms.EcdsaSha256) ||
|
|
credentials.Key.IsSupportedAlgorithm(SecurityAlgorithms.EcdsaSha384) ||
|
|
credentials.Key.IsSupportedAlgorithm(SecurityAlgorithms.EcdsaSha512))
|
|
{
|
|
var parameters = credentials.Key switch
|
|
{
|
|
X509SecurityKey x509SecurityKey when x509SecurityKey.PublicKey is ECDsa algorithm =>
|
|
algorithm.ExportParameters(includePrivateParameters: false),
|
|
|
|
ECDsaSecurityKey ecdsaSecurityKey when ecdsaSecurityKey.ECDsa != null =>
|
|
ecdsaSecurityKey.ECDsa.ExportParameters(includePrivateParameters: false),
|
|
|
|
_ => (ECParameters?) null
|
|
};
|
|
|
|
if (parameters == null)
|
|
{
|
|
context.Logger.LogWarning("A signing key of type '{Type}' was ignored because its EC public " +
|
|
"parameters couldn't be extracted.", credentials.Key.GetType().Name);
|
|
|
|
continue;
|
|
}
|
|
|
|
Debug.Assert(parameters.Value.Q.X != null &&
|
|
parameters.Value.Q.Y != null,
|
|
"ECDsa.ExportParameters() shouldn't return null coordinates.");
|
|
|
|
Debug.Assert(parameters.Value.Curve.IsNamed,
|
|
"ECDsa.ExportParameters() shouldn't return an unnamed curve.");
|
|
|
|
key.Kty = JsonWebAlgorithmsKeyTypes.EllipticCurve;
|
|
key.Crv = IsCurve(parameters.Value, ECCurve.NamedCurves.nistP256) ? JsonWebKeyECTypes.P256 :
|
|
IsCurve(parameters.Value, ECCurve.NamedCurves.nistP384) ? JsonWebKeyECTypes.P384 :
|
|
IsCurve(parameters.Value, ECCurve.NamedCurves.nistP521) ? JsonWebKeyECTypes.P521 : null;
|
|
|
|
// Note: both X and Y must be base64url-encoded.
|
|
// See https://tools.ietf.org/html/rfc7518#section-6.2.1.2
|
|
key.X = Base64UrlEncoder.Encode(parameters.Value.Q.X);
|
|
key.Y = Base64UrlEncoder.Encode(parameters.Value.Q.Y);
|
|
}
|
|
#endif
|
|
|
|
// If the signing key is embedded in a X.509 certificate, set
|
|
// the x5t and x5c parameters using the certificate details.
|
|
var certificate = (credentials.Key as X509SecurityKey)?.Certificate;
|
|
if (certificate != null)
|
|
{
|
|
// x5t must be base64url-encoded.
|
|
// See https://tools.ietf.org/html/rfc7517#section-4.8
|
|
key.X5t = Base64UrlEncoder.Encode(certificate.GetCertHash());
|
|
|
|
// x5t#S256 must be base64url-encoded.
|
|
// See https://tools.ietf.org/html/rfc7517#section-4.9
|
|
key.X5tS256 = Base64UrlEncoder.Encode(GetCertificateHash(certificate, HashAlgorithmName.SHA256));
|
|
|
|
// Unlike E or N, the certificates contained in x5c
|
|
// must be base64-encoded and not base64url-encoded.
|
|
// See https://tools.ietf.org/html/rfc7517#section-4.7
|
|
key.X5c.Add(Convert.ToBase64String(certificate.RawData));
|
|
}
|
|
|
|
context.Keys.Add(key);
|
|
}
|
|
|
|
return default;
|
|
|
|
#if SUPPORTS_ECDSA
|
|
static bool IsCurve(ECParameters parameters, ECCurve curve) =>
|
|
string.Equals(parameters.Curve.Oid.FriendlyName, curve.Oid.FriendlyName, StringComparison.Ordinal);
|
|
#endif
|
|
|
|
static byte[] GetCertificateHash(X509Certificate2 certificate, HashAlgorithmName algorithm)
|
|
{
|
|
#if SUPPORTS_CERTIFICATE_HASHING_WITH_SPECIFIED_ALGORITHM
|
|
return certificate.GetCertHash(algorithm);
|
|
#else
|
|
using var hash = CryptoConfig.CreateFromName(algorithm.Name) as HashAlgorithm;
|
|
if (hash == null || hash is KeyedHashAlgorithm)
|
|
{
|
|
throw new InvalidOperationException("The specified hash algorithm is not valid.");
|
|
}
|
|
|
|
return hash.ComputeHash(certificate.RawData);
|
|
#endif
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|