From c1698a6477a39da8314f574a3f6e1f25f054c405 Mon Sep 17 00:00:00 2001 From: Kirill Kovalev Date: Wed, 9 Oct 2024 19:31:40 +0500 Subject: [PATCH] Add signing algorithms dynamic validation --- Abblix.Jwt/IJsonWebTokenCreator.cs | 2 +- Abblix.Jwt/IJsonWebTokenValidator.cs | 2 +- Abblix.Jwt/JsonWebKeyExtensions.cs | 8 +- Abblix.Jwt/JsonWebTokenCreator.cs | 2 +- Abblix.Jwt/JsonWebTokenValidator.cs | 2 +- Abblix.Jwt/SigningAlgorithms.cs | 21 ++++- .../Controllers/DiscoveryController.cs | 9 +- .../Formatters/UserInfoResponseFormatter.cs | 13 +-- .../Model/AuthorizationRequest.cs | 2 +- .../Model/ClientRegistrationRequest.cs | 5 +- Abblix.Oidc.Server.Mvc/Model/TokenRequest.cs | 6 -- .../Common/Constants/CodeChallengeMethods.cs | 6 ++ .../AuthorizationEndpointMetadata.cs | 1 + .../BackChannelAuthenticationValidator.cs | 2 +- .../Validation/RedirectUrisValidator.cs | 3 +- .../SignedResponseAlgorithmsValidator.cs | 86 ++++++++++++++++++ .../Validation/SigningAlgorithmsValidator.cs | 89 +++++++++++++++++++ .../Endpoints/ServiceCollectionExtensions.cs | 10 +-- .../Grants/AuthorizationCodeGrantHandler.cs | 13 +-- .../Features/ServiceCollectionExtensions.cs | 15 ++++ .../TokenStatusValidatorDecorator.cs | 2 +- .../Model/AuthorizationRequest.cs | 2 +- .../Model/ClientRegistrationRequest.cs | 6 +- .../Model/ConfigurationResponse.cs | 30 +++++++ Abblix.Oidc.Server/Model/TokenRequest.cs | 3 +- .../ServiceCollectionExtensions.cs | 3 +- 26 files changed, 285 insertions(+), 58 deletions(-) create mode 100644 Abblix.Oidc.Server/Endpoints/DynamicClientManagement/Validation/SignedResponseAlgorithmsValidator.cs create mode 100644 Abblix.Oidc.Server/Endpoints/DynamicClientManagement/Validation/SigningAlgorithmsValidator.cs diff --git a/Abblix.Jwt/IJsonWebTokenCreator.cs b/Abblix.Jwt/IJsonWebTokenCreator.cs index 92d8bf61..84775fd4 100644 --- a/Abblix.Jwt/IJsonWebTokenCreator.cs +++ b/Abblix.Jwt/IJsonWebTokenCreator.cs @@ -30,7 +30,7 @@ public interface IJsonWebTokenCreator /// /// Lists the all supported signing algorithms for JWT creation. /// - IEnumerable SigningAlgValuesSupported { get; } + IEnumerable SignedResponseAlgorithmsSupported { get; } /// /// Issues a new JWT based on the specified JsonWebToken object, signing key, and optional encrypting key. diff --git a/Abblix.Jwt/IJsonWebTokenValidator.cs b/Abblix.Jwt/IJsonWebTokenValidator.cs index 80430fcc..266dc3ba 100644 --- a/Abblix.Jwt/IJsonWebTokenValidator.cs +++ b/Abblix.Jwt/IJsonWebTokenValidator.cs @@ -31,7 +31,7 @@ public interface IJsonWebTokenValidator /// Indicates which algorithms are accepted by the validator for verifying the signatures of incoming JWTs, /// ensuring that only tokens signed with recognized and secure algorithms are considered valid. /// - IEnumerable SigningAlgValuesSupported { get; } + IEnumerable SigningAlgorithmsSupported { get; } /// /// Asynchronously validates a JWT against a set of specified parameters. diff --git a/Abblix.Jwt/JsonWebKeyExtensions.cs b/Abblix.Jwt/JsonWebKeyExtensions.cs index c9602195..1d79d0b5 100644 --- a/Abblix.Jwt/JsonWebKeyExtensions.cs +++ b/Abblix.Jwt/JsonWebKeyExtensions.cs @@ -41,11 +41,7 @@ public static class JsonWebKeyExtensions /// Thrown when the algorithm is not supported. public static SigningCredentials ToSigningCredentials(this JsonWebKey jsonWebKey) { - return jsonWebKey.Algorithm switch - { - SigningAlgorithms.RS256 => new SigningCredentials(jsonWebKey.ToSecurityKey(), SigningAlgorithms.RS256), - _ => throw new InvalidOperationException($"Not supported algorithm: {jsonWebKey.Algorithm}"), - }; + return new SigningCredentials(jsonWebKey.ToSecurityKey(), jsonWebKey.Algorithm); } /// @@ -68,7 +64,7 @@ public static EncryptingCredentials ToEncryptingCredentials(this JsonWebKey json { return jsonWebKey.Algorithm switch { - SigningAlgorithms.RS256 => new EncryptingCredentials( + SecurityAlgorithms.RsaSha256 => new EncryptingCredentials( jsonWebKey.ToSecurityKey(), SecurityAlgorithms.RsaOAEP, SecurityAlgorithms.Aes128CbcHmacSha256), diff --git a/Abblix.Jwt/JsonWebTokenCreator.cs b/Abblix.Jwt/JsonWebTokenCreator.cs index d12fe242..362e4897 100644 --- a/Abblix.Jwt/JsonWebTokenCreator.cs +++ b/Abblix.Jwt/JsonWebTokenCreator.cs @@ -40,7 +40,7 @@ public sealed class JsonWebTokenCreator : IJsonWebTokenCreator /// This property reflects the JWT security token handler's default outbound algorithm mapping, /// indicating the algorithms available for signing the tokens. /// - public IEnumerable SigningAlgValuesSupported => JsonWebTokenAlgorithms.SigningAlgValuesSupported; + public IEnumerable SignedResponseAlgorithmsSupported => JsonWebTokenAlgorithms.SigningAlgValuesSupported; /// /// Asynchronously issues a JWT based on the specified JsonWebToken, signing key, and optional encrypting key. diff --git a/Abblix.Jwt/JsonWebTokenValidator.cs b/Abblix.Jwt/JsonWebTokenValidator.cs index c6c9b762..ad14f42c 100644 --- a/Abblix.Jwt/JsonWebTokenValidator.cs +++ b/Abblix.Jwt/JsonWebTokenValidator.cs @@ -38,7 +38,7 @@ public class JsonWebTokenValidator : IJsonWebTokenValidator /// by the JwtSecurityTokenHandler for inbound tokens, as well as an option to accept tokens without a signature. /// This allows for flexibility in validating JWTs with various security requirements. /// - public IEnumerable SigningAlgValuesSupported => JsonWebTokenAlgorithms.SigningAlgValuesSupported; + public IEnumerable SigningAlgorithmsSupported => JsonWebTokenAlgorithms.SigningAlgValuesSupported; /// /// Asynchronously validates a JWT string against specified validation parameters. diff --git a/Abblix.Jwt/SigningAlgorithms.cs b/Abblix.Jwt/SigningAlgorithms.cs index 04326e6f..8009ac70 100644 --- a/Abblix.Jwt/SigningAlgorithms.cs +++ b/Abblix.Jwt/SigningAlgorithms.cs @@ -27,6 +27,13 @@ namespace Abblix.Jwt; /// public static class SigningAlgorithms { + /// + /// Represents the "none" signing algorithm. + /// This value is used when no digital signature or MAC operation is performed on the JWT. + /// It is important to use this algorithm with caution as it implies that the JWT is unprotected. + /// + public const string None = "none"; + /// /// Represents the RS256 (RSA Signature with SHA-256) signing algorithm. /// This algorithm is commonly used for creating JWT signatures using RSA keys with SHA-256 hashing. @@ -34,9 +41,15 @@ public static class SigningAlgorithms public const string RS256 = "RS256"; /// - /// Represents the "none" signing algorithm. - /// This value is used when no digital signature or MAC operation is performed on the JWT. - /// It is important to use this algorithm with caution as it implies that the JWT is unprotected. + /// Represents the PS256 (RSA PSS Signature with SHA-256) signing algorithm. + /// This algorithm is similar to RS256 but uses RSA PSS (Probabilistic Signature Scheme) for improved security. /// - public const string None = "none"; + public const string PS256 = "PS256"; + + /// + /// Represents the ES256 (Elliptic Curve Signature with SHA-256) signing algorithm. + /// This algorithm uses the ECDSA (Elliptic Curve Digital Signature Algorithm) with SHA-256 hashing, + /// offering a compact signature size and high security, making it suitable for JWT signing. + /// + public const string ES256 = "ES256"; } diff --git a/Abblix.Oidc.Server.Mvc/Controllers/DiscoveryController.cs b/Abblix.Oidc.Server.Mvc/Controllers/DiscoveryController.cs index d4265855..1ae11fd3 100644 --- a/Abblix.Oidc.Server.Mvc/Controllers/DiscoveryController.cs +++ b/Abblix.Oidc.Server.Mvc/Controllers/DiscoveryController.cs @@ -117,6 +117,7 @@ public Task> ConfigurationAsync( ResponseModesSupported = authorizationHandler.Metadata.ResponseModesSupported, TokenEndpointAuthMethodsSupported = clientAuthenticator.ClientAuthenticationMethodsSupported, + TokenEndpointAuthSigningAlgValuesSupported = jwtValidator.SigningAlgorithmsSupported, SubjectTypesSupported = subjectTypeConverter.SubjectTypesSupported, PromptValuesSupported = authorizationHandler.Metadata.PromptValuesSupported, @@ -125,19 +126,19 @@ public Task> ConfigurationAsync( RequestParameterSupported = authorizationHandler.Metadata.RequestParameterSupported, RequestObjectSigningAlgValuesSupported = authorizationHandler.Metadata.RequestParameterSupported - ? jwtValidator.SigningAlgValuesSupported + ? jwtValidator.SigningAlgorithmsSupported : null, RequirePushedAuthorizationRequests = options.Value.RequirePushedAuthorizationRequests, RequireSignedRequestObject = options.Value.RequireSignedRequestObject, - IdTokenSigningAlgValuesSupported = jwtCreator.SigningAlgValuesSupported, - UserInfoSigningAlgValuesSupported = jwtCreator.SigningAlgValuesSupported, + IdTokenSigningAlgValuesSupported = jwtCreator.SignedResponseAlgorithmsSupported, + UserInfoSigningAlgValuesSupported = jwtCreator.SignedResponseAlgorithmsSupported, BackChannelAuthenticationEndpoint = Resolve(Path.BackChannelAuthentication, OidcEndpoints.BackChannelAuthentication), BackChannelTokenDeliveryModesSupported = options.Value.BackChannelAuthentication.TokenDeliveryModesSupported, BackChannelUserCodeParameterSupported = options.Value.BackChannelAuthentication.UserCodeParameterSupported, - BackChannelAuthenticationRequestSigningAlgValuesSupported = jwtValidator.SigningAlgValuesSupported, + BackChannelAuthenticationRequestSigningAlgValuesSupported = jwtValidator.SigningAlgorithmsSupported, }; return Task.FromResult>(response); diff --git a/Abblix.Oidc.Server.Mvc/Formatters/UserInfoResponseFormatter.cs b/Abblix.Oidc.Server.Mvc/Formatters/UserInfoResponseFormatter.cs index da441796..04822a74 100644 --- a/Abblix.Oidc.Server.Mvc/Formatters/UserInfoResponseFormatter.cs +++ b/Abblix.Oidc.Server.Mvc/Formatters/UserInfoResponseFormatter.cs @@ -71,13 +71,19 @@ public async Task> FormatResponseAsync( { switch (response) { + case UserInfoFoundResponse { + ClientInfo.UserInfoSignedResponseAlgorithm: SigningAlgorithms.None, + User: var user, + }: + + return new JsonResult(user); + case UserInfoFoundResponse { ClientInfo: var clientInfo, User: var user, Issuer: var issuer, - } - when clientInfo.UserInfoSignedResponseAlgorithm != SigningAlgorithms.None: + }: var token = new JsonWebToken { @@ -96,9 +102,6 @@ public async Task> FormatResponseAsync( Content = await _clientJwtFormatter.FormatAsync(token, clientInfo), }; - case UserInfoFoundResponse { User: var user }: - return new JsonResult(user); - case UserInfoErrorResponse error: return new BadRequestObjectResult(error); diff --git a/Abblix.Oidc.Server.Mvc/Model/AuthorizationRequest.cs b/Abblix.Oidc.Server.Mvc/Model/AuthorizationRequest.cs index 9fa05202..b45ce0a7 100644 --- a/Abblix.Oidc.Server.Mvc/Model/AuthorizationRequest.cs +++ b/Abblix.Oidc.Server.Mvc/Model/AuthorizationRequest.cs @@ -170,7 +170,7 @@ public record AuthorizationRequest /// supporting enhanced security for public clients in the PKCE flow. /// [BindProperty(SupportsGet = true, Name = Parameters.CodeChallengeMethod)] - [AllowedValues(CodeChallengeMethods.Plain, CodeChallengeMethods.S256)] + [AllowedValues(CodeChallengeMethods.Plain, CodeChallengeMethods.S256, CodeChallengeMethods.S512)] public string? CodeChallengeMethod { get; init; } /// diff --git a/Abblix.Oidc.Server.Mvc/Model/ClientRegistrationRequest.cs b/Abblix.Oidc.Server.Mvc/Model/ClientRegistrationRequest.cs index e3d1c95f..f31c2301 100644 --- a/Abblix.Oidc.Server.Mvc/Model/ClientRegistrationRequest.cs +++ b/Abblix.Oidc.Server.Mvc/Model/ClientRegistrationRequest.cs @@ -166,7 +166,6 @@ public record ClientRegistrationRequest /// Specifies the algorithm that the OpenID Provider should use to sign ID Tokens sent to this client. /// [JsonPropertyName(Parameters.IdTokenSignedResponseAlg)] - [AllowedValues(SigningAlgorithms.None, SigningAlgorithms.RS256)] public string? IdTokenSignedResponseAlg { get; init; } /// @@ -188,7 +187,6 @@ public record ClientRegistrationRequest /// Indicates the preferred algorithm for signing UserInfo responses sent to this client. /// [JsonPropertyName(Parameters.UserInfoSignedResponseAlg)] - [AllowedValues(SigningAlgorithms.None, SigningAlgorithms.RS256)] public string? UserInfoSignedResponseAlg { get; init; } /// @@ -244,7 +242,6 @@ public record ClientRegistrationRequest /// Endpoint. Specifies the algorithm to be used by the client for signing JWTs used in client authentication. /// [JsonPropertyName(Parameters.TokenEndpointAuthSigningAlg)] - [AllowedValues(SigningAlgorithms.None, SigningAlgorithms.RS256)] public string? TokenEndpointAuthSigningAlg { get; init; } /// @@ -403,7 +400,7 @@ public Core.ClientRegistrationRequest Map() RequestObjectSigningAlg = RequestObjectSigningAlg, IdTokenEncryptedResponseAlg = IdTokenEncryptedResponseAlg, - IdTokenEncryptedResponseEnc = RequestObjectEncryptionEnc, + IdTokenEncryptedResponseEnc = IdTokenEncryptedResponseEnc, IdTokenSignedResponseAlg = IdTokenSignedResponseAlg, TokenEndpointAuthMethod = TokenEndpointAuthMethod, diff --git a/Abblix.Oidc.Server.Mvc/Model/TokenRequest.cs b/Abblix.Oidc.Server.Mvc/Model/TokenRequest.cs index d80b1541..e5546675 100644 --- a/Abblix.Oidc.Server.Mvc/Model/TokenRequest.cs +++ b/Abblix.Oidc.Server.Mvc/Model/TokenRequest.cs @@ -83,12 +83,6 @@ public record TokenRequest /// Scopes specify the level of access required and the associated permissions. /// [BindProperty(SupportsGet = true, Name = Parameters.Scope)] - [AllowedValues( - Scopes.OpenId, - Scopes.Profile, - Scopes.Email, - Scopes.Phone, - Scopes.OfflineAccess)] [ModelBinder(typeof(SpaceSeparatedValuesBinder))] public string[] Scope { get; set; } = Array.Empty(); diff --git a/Abblix.Oidc.Server/Common/Constants/CodeChallengeMethods.cs b/Abblix.Oidc.Server/Common/Constants/CodeChallengeMethods.cs index 77176a0f..96ba7db2 100644 --- a/Abblix.Oidc.Server/Common/Constants/CodeChallengeMethods.cs +++ b/Abblix.Oidc.Server/Common/Constants/CodeChallengeMethods.cs @@ -36,4 +36,10 @@ public static class CodeChallengeMethods /// Represents the "S256" code challenge method where the code verifier is hashed using SHA-256. /// public const string S256 = "S256"; + + /// + /// Represents the "S512" code challenge method where the code verifier is hashed using SHA-512. + /// This method provides a higher level of security through a stronger hashing algorithm. + /// + public const string S512 = "S512"; } diff --git a/Abblix.Oidc.Server/Endpoints/Authorization/Interfaces/AuthorizationEndpointMetadata.cs b/Abblix.Oidc.Server/Endpoints/Authorization/Interfaces/AuthorizationEndpointMetadata.cs index 7e887e35..f97f51a5 100644 --- a/Abblix.Oidc.Server/Endpoints/Authorization/Interfaces/AuthorizationEndpointMetadata.cs +++ b/Abblix.Oidc.Server/Endpoints/Authorization/Interfaces/AuthorizationEndpointMetadata.cs @@ -81,6 +81,7 @@ public record AuthorizationEndpointMetadata /// public List CodeChallengeMethodsSupported { get; init; } = new() { + CodeChallengeMethods.S512, CodeChallengeMethods.S256, CodeChallengeMethods.Plain, }; diff --git a/Abblix.Oidc.Server/Endpoints/DynamicClientManagement/Validation/BackChannelAuthenticationValidator.cs b/Abblix.Oidc.Server/Endpoints/DynamicClientManagement/Validation/BackChannelAuthenticationValidator.cs index b7065525..532a8050 100644 --- a/Abblix.Oidc.Server/Endpoints/DynamicClientManagement/Validation/BackChannelAuthenticationValidator.cs +++ b/Abblix.Oidc.Server/Endpoints/DynamicClientManagement/Validation/BackChannelAuthenticationValidator.cs @@ -102,7 +102,7 @@ public BackChannelAuthenticationValidator(IJsonWebTokenValidator jwtValidator) // Check if the signing algorithm specified in the request is supported var signingAlgorithm = context.Request.BackChannelAuthenticationRequestSigningAlg; if (signingAlgorithm.HasValue() && - !_jwtValidator.SigningAlgValuesSupported.Contains(signingAlgorithm, StringComparer.Ordinal)) + !_jwtValidator.SigningAlgorithmsSupported.Contains(signingAlgorithm, StringComparer.Ordinal)) { return new ClientRegistrationValidationError( ErrorCodes.InvalidRequest, diff --git a/Abblix.Oidc.Server/Endpoints/DynamicClientManagement/Validation/RedirectUrisValidator.cs b/Abblix.Oidc.Server/Endpoints/DynamicClientManagement/Validation/RedirectUrisValidator.cs index 7a5a216c..51a2a2a1 100644 --- a/Abblix.Oidc.Server/Endpoints/DynamicClientManagement/Validation/RedirectUrisValidator.cs +++ b/Abblix.Oidc.Server/Endpoints/DynamicClientManagement/Validation/RedirectUrisValidator.cs @@ -108,5 +108,6 @@ internal class RedirectUrisValidator : SyncClientRegistrationContextValidator } // Helper method to determine if the provided URI uses localhost or loopback address - private static bool IsLocalhost(Uri uri) => uri.IsLoopback || uri.Host == "localhost"; + private static bool IsLocalhost(Uri uri) + => uri.IsLoopback || string.Equals(uri.Host, "localhost", StringComparison.Ordinal); } diff --git a/Abblix.Oidc.Server/Endpoints/DynamicClientManagement/Validation/SignedResponseAlgorithmsValidator.cs b/Abblix.Oidc.Server/Endpoints/DynamicClientManagement/Validation/SignedResponseAlgorithmsValidator.cs new file mode 100644 index 00000000..76ca1da1 --- /dev/null +++ b/Abblix.Oidc.Server/Endpoints/DynamicClientManagement/Validation/SignedResponseAlgorithmsValidator.cs @@ -0,0 +1,86 @@ +// Abblix OIDC Server Library +// Copyright (c) Abblix LLP. All rights reserved. +// +// DISCLAIMER: This software is provided 'as-is', without any express or implied +// warranty. Use at your own risk. Abblix LLP is not liable for any damages +// arising from the use of this software. +// +// LICENSE RESTRICTIONS: This code may not be modified, copied, or redistributed +// in any form outside of the official GitHub repository at: +// https://github.com/Abblix/OIDC.Server. All development and modifications +// must occur within the official repository and are managed solely by Abblix LLP. +// +// Unauthorized use, modification, or distribution of this software is strictly +// prohibited and may be subject to legal action. +// +// For full licensing terms, please visit: +// +// https://oidc.abblix.com/license +// +// CONTACT: For license inquiries or permissions, contact Abblix LLP at +// info@abblix.com + +using Abblix.Jwt; +using Abblix.Oidc.Server.Common.Constants; +using Abblix.Oidc.Server.Endpoints.DynamicClientManagement.Interfaces; +using static Abblix.Oidc.Server.Model.ClientRegistrationRequest; + +namespace Abblix.Oidc.Server.Endpoints.DynamicClientManagement.Validation; + +/// +/// Validates the signing algorithms specified for ID tokens and user info responses in a client registration request. +/// This class checks if the requested signing algorithms are supported by the JWT creator, ensuring compliance +/// with security standards. +/// +public class SignedResponseAlgorithmsValidator: SyncClientRegistrationContextValidator +{ + /// + /// Initializes a new instance of the class. + /// The constructor takes a JWT creator to access the supported signing algorithms for validation. + /// + /// The service responsible for creating signed JWTs. + public SignedResponseAlgorithmsValidator(IJsonWebTokenCreator jwtCreator) + { + _jwtCreator = jwtCreator; + } + + private readonly IJsonWebTokenCreator _jwtCreator; + + /// + /// Validates the signing algorithms specified for ID tokens and user info responses. + /// This method ensures that the JWT creator supports the requested algorithms. + /// + /// The validation context containing the client registration data. + /// + /// A if any signing algorithm is not supported; + /// otherwise, null if all validations are successful. + /// + protected override ClientRegistrationValidationError? Validate(ClientRegistrationValidationContext context) + { + var request = context.Request; + return Validate( request.IdTokenSignedResponseAlg, Parameters.IdTokenSignedResponseAlg) ?? + Validate(request.UserInfoSignedResponseAlg, Parameters.UserInfoSignedResponseAlg); + } + + /// + /// Validates that the JWT creator supports the specified signing algorithm. + /// If the algorithm is not supported, it returns a validation error. + /// + /// The signing algorithm to validate. + /// + /// A description used in the error message to identify which signing algorithm is invalid. + /// + /// A if the algorithm is not supported; otherwise, null. + /// + private ClientRegistrationValidationError? Validate(string? alg, string description) + { + if (alg is not null && !_jwtCreator.SignedResponseAlgorithmsSupported.Contains(alg, StringComparer.Ordinal)) + { + return new ClientRegistrationValidationError( + ErrorCodes.InvalidRequest, + $"The signing algorithm for {description} is not supported"); + } + + return null; + } +} diff --git a/Abblix.Oidc.Server/Endpoints/DynamicClientManagement/Validation/SigningAlgorithmsValidator.cs b/Abblix.Oidc.Server/Endpoints/DynamicClientManagement/Validation/SigningAlgorithmsValidator.cs new file mode 100644 index 00000000..ce0ebdd5 --- /dev/null +++ b/Abblix.Oidc.Server/Endpoints/DynamicClientManagement/Validation/SigningAlgorithmsValidator.cs @@ -0,0 +1,89 @@ +// Abblix OIDC Server Library +// Copyright (c) Abblix LLP. All rights reserved. +// +// DISCLAIMER: This software is provided 'as-is', without any express or implied +// warranty. Use at your own risk. Abblix LLP is not liable for any damages +// arising from the use of this software. +// +// LICENSE RESTRICTIONS: This code may not be modified, copied, or redistributed +// in any form outside of the official GitHub repository at: +// https://github.com/Abblix/OIDC.Server. All development and modifications +// must occur within the official repository and are managed solely by Abblix LLP. +// +// Unauthorized use, modification, or distribution of this software is strictly +// prohibited and may be subject to legal action. +// +// For full licensing terms, please visit: +// +// https://oidc.abblix.com/license +// +// CONTACT: For license inquiries or permissions, contact Abblix LLP at +// info@abblix.com + +using Abblix.Jwt; +using Abblix.Oidc.Server.Common.Constants; +using Abblix.Oidc.Server.Endpoints.DynamicClientManagement.Interfaces; +using static Abblix.Oidc.Server.Model.ClientRegistrationRequest; + +namespace Abblix.Oidc.Server.Endpoints.DynamicClientManagement.Validation; + +/// +/// Validates the signing algorithms specified for request objects, backchannel authentication requests, +/// and token endpoints in a client registration request. +/// This class ensures that the requested signing algorithms are supported by the JWT validator, +/// maintaining compliance with security standards. +/// +public class SigningAlgorithmsValidator: SyncClientRegistrationContextValidator +{ + /// + /// Initializes a new instance of the class. + /// The constructor takes a JWT validator to access the supported signing algorithms for validation. + /// + /// The service responsible for validating signing algorithms used in JWTs. + public SigningAlgorithmsValidator(IJsonWebTokenValidator jwtValidator) + { + _jwtValidator = jwtValidator; + } + + private readonly IJsonWebTokenValidator _jwtValidator; + + /// + /// Validates the signing algorithms specified in the client registration request. + /// This method checks if the requested algorithms are supported by the JWT validator for various purposes. + /// + /// The validation context containing the client registration data. + /// + /// A if any signing algorithm is not supported; + /// otherwise, null if all validations are successful. + /// + protected override ClientRegistrationValidationError? Validate(ClientRegistrationValidationContext context) + { + var request = context.Request; + return Validate(request.RequestObjectSigningAlg, Parameters.RequestObjectSigningAlg) + ?? Validate(request.BackChannelAuthenticationRequestSigningAlg, Parameters.BackChannelAuthenticationRequestSigningAlg) + ?? Validate(request.TokenEndpointAuthSigningAlg, Parameters.TokenEndpointAuthSigningAlg) + ; + } + + /// + /// Validates that the JWT validator supports the specified signing algorithm. + /// If the algorithm is not supported, it returns a validation error. + /// + /// The signing algorithm to validate. + /// + /// A description used in the error message to identify which signing algorithm is invalid. + /// + /// A if the algorithm is not supported; otherwise, null. + /// + private ClientRegistrationValidationError? Validate(string? alg, string description) + { + if (alg is not null && !_jwtValidator.SigningAlgorithmsSupported.Contains(alg, StringComparer.Ordinal)) + { + return new ClientRegistrationValidationError( + ErrorCodes.InvalidRequest, + $"The signing algorithm for {description} is not supported"); + } + + return null; + } +} diff --git a/Abblix.Oidc.Server/Endpoints/ServiceCollectionExtensions.cs b/Abblix.Oidc.Server/Endpoints/ServiceCollectionExtensions.cs index 59c1f8b1..dd44c08b 100644 --- a/Abblix.Oidc.Server/Endpoints/ServiceCollectionExtensions.cs +++ b/Abblix.Oidc.Server/Endpoints/ServiceCollectionExtensions.cs @@ -21,6 +21,7 @@ // info@abblix.com using Abblix.DependencyInjection; +using Abblix.Jwt; using Abblix.Oidc.Server.Common.Configuration; using Abblix.Oidc.Server.Common.Implementation; using Abblix.Oidc.Server.Common.Interfaces; @@ -52,8 +53,6 @@ using Abblix.Oidc.Server.Endpoints.Token.Validation; using Abblix.Oidc.Server.Endpoints.UserInfo; using Abblix.Oidc.Server.Endpoints.UserInfo.Interfaces; -using Abblix.Oidc.Server.Features.BackChannelAuthentication; -using Abblix.Oidc.Server.Features.BackChannelAuthentication.Interfaces; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; @@ -340,6 +339,8 @@ private static IServiceCollection AddClientRegistrationContextValidators(this IS .AddSingleton() .AddSingleton() .AddSingleton() + .AddSingleton() + .AddSingleton() .Compose(); } @@ -383,10 +384,7 @@ public static IServiceCollection AddBackChannelAuthenticationEndpoint(this IServ .AddScoped() .AddScoped() .AddScoped() - .AddScoped() - - .AddSingleton() - .AddScoped(); + .AddScoped(); } public static IServiceCollection AddBackChannelAuthenticationContextValidators(this IServiceCollection services) diff --git a/Abblix.Oidc.Server/Endpoints/Token/Grants/AuthorizationCodeGrantHandler.cs b/Abblix.Oidc.Server/Endpoints/Token/Grants/AuthorizationCodeGrantHandler.cs index 3ce4c095..94a3c6a0 100644 --- a/Abblix.Oidc.Server/Endpoints/Token/Grants/AuthorizationCodeGrantHandler.cs +++ b/Abblix.Oidc.Server/Endpoints/Token/Grants/AuthorizationCodeGrantHandler.cs @@ -125,17 +125,18 @@ public async Task AuthorizeAsync(TokenRequest request, /// exchanging the authorization code for a token. This method ensures that the correct transformation is applied. /// It supports both 'plain' and 'S256' methods, with 'S256' being the recommended approach for stronger security. /// - /// The PKCE challenge method, either 'plain' or 'S256'. + /// The PKCE challenge method, either 'plain', 'S256' or 'S512'. /// The code verifier submitted by the client during the token request. /// The transformed code challenge based on the specified method. private static string CalculateChallenge(string method, string codeVerifier) => method switch { // Encodes the code verifier using SHA256 and URL-safe base64 encoding for 'S256' method. - CodeChallengeMethods.S256 => - HttpServerUtility.UrlTokenEncode( - SHA256.HashData( - Encoding.ASCII.GetBytes( - codeVerifier))), + CodeChallengeMethods.S256 => HttpServerUtility.UrlTokenEncode( + SHA256.HashData(Encoding.ASCII.GetBytes(codeVerifier))), + + // Encodes the code verifier using SHA512 and URL-safe base64 encoding for 'S512' method. + CodeChallengeMethods.S512 => HttpServerUtility.UrlTokenEncode( + SHA512.HashData(Encoding.ASCII.GetBytes(codeVerifier))), // Returns the code verifier as-is for the 'plain' method. CodeChallengeMethods.Plain => codeVerifier, diff --git a/Abblix.Oidc.Server/Features/ServiceCollectionExtensions.cs b/Abblix.Oidc.Server/Features/ServiceCollectionExtensions.cs index 5a26ccde..f305dfec 100644 --- a/Abblix.Oidc.Server/Features/ServiceCollectionExtensions.cs +++ b/Abblix.Oidc.Server/Features/ServiceCollectionExtensions.cs @@ -26,6 +26,8 @@ using Abblix.Oidc.Server.Common.Implementation; using Abblix.Oidc.Server.Common.Interfaces; using Abblix.Oidc.Server.Endpoints.Authorization.Interfaces; +using Abblix.Oidc.Server.Features.BackChannelAuthentication; +using Abblix.Oidc.Server.Features.BackChannelAuthentication.Interfaces; using Abblix.Oidc.Server.Features.ClientAuthentication; using Abblix.Oidc.Server.Features.ClientInformation; using Abblix.Oidc.Server.Features.Consents; @@ -405,4 +407,17 @@ public static IServiceCollection AddRequestObject(this IServiceCollection servic services.TryAddSingleton(); return services; } + + /// + /// Configures services for handling back-channel authentication requests, enabling secure server-to-server + /// authentication flows. + /// + /// The to configure. + /// The configured . + public static IServiceCollection AddBackChannelAuthentication(this IServiceCollection services) + { + return services + .AddSingleton() + .AddSingleton(); + } } diff --git a/Abblix.Oidc.Server/Features/Tokens/Revocation/TokenStatusValidatorDecorator.cs b/Abblix.Oidc.Server/Features/Tokens/Revocation/TokenStatusValidatorDecorator.cs index 14f7fe4d..5b306c38 100644 --- a/Abblix.Oidc.Server/Features/Tokens/Revocation/TokenStatusValidatorDecorator.cs +++ b/Abblix.Oidc.Server/Features/Tokens/Revocation/TokenStatusValidatorDecorator.cs @@ -44,7 +44,7 @@ public TokenStatusValidatorDecorator( private readonly ITokenRegistry _tokenRegistry; private readonly IJsonWebTokenValidator _innerValidator; - public IEnumerable SigningAlgValuesSupported => _innerValidator.SigningAlgValuesSupported; + public IEnumerable SigningAlgorithmsSupported => _innerValidator.SigningAlgorithmsSupported; /// /// Validates a JSON Web Token (JWT) and checks its revocation status. diff --git a/Abblix.Oidc.Server/Model/AuthorizationRequest.cs b/Abblix.Oidc.Server/Model/AuthorizationRequest.cs index cdd23032..4f6926f2 100644 --- a/Abblix.Oidc.Server/Model/AuthorizationRequest.cs +++ b/Abblix.Oidc.Server/Model/AuthorizationRequest.cs @@ -155,7 +155,7 @@ public record AuthorizationRequest /// verifier. /// [JsonPropertyName(Parameters.CodeChallengeMethod)] - [AllowedValues(CodeChallengeMethods.Plain, CodeChallengeMethods.S256)] + [AllowedValues(CodeChallengeMethods.Plain, CodeChallengeMethods.S256, CodeChallengeMethods.S512)] public string? CodeChallengeMethod { get; init; } /// diff --git a/Abblix.Oidc.Server/Model/ClientRegistrationRequest.cs b/Abblix.Oidc.Server/Model/ClientRegistrationRequest.cs index bf6a1e3d..33bcbd45 100644 --- a/Abblix.Oidc.Server/Model/ClientRegistrationRequest.cs +++ b/Abblix.Oidc.Server/Model/ClientRegistrationRequest.cs @@ -20,7 +20,6 @@ // CONTACT: For license inquiries or permissions, contact Abblix LLP at // info@abblix.com -using System.ComponentModel.DataAnnotations; using System.Text.Json.Serialization; using Abblix.Jwt; using Abblix.Oidc.Server.Common.Constants; @@ -98,7 +97,7 @@ public record ClientRegistrationRequest public Uri? LogoUri { get; init; } /// - /// URL of the home page of the client. + /// URL that points to the home page of the client. /// [JsonPropertyName(Parameters.ClientUri)] [AbsoluteUri] @@ -148,7 +147,6 @@ public record ClientRegistrationRequest /// JWS algorithm for the ID Token issued to this client. /// [JsonPropertyName(Parameters.IdTokenSignedResponseAlg)] - [AllowedValues(SigningAlgorithms.None, SigningAlgorithms.RS256)] public string? IdTokenSignedResponseAlg { get; init; } /// @@ -167,7 +165,6 @@ public record ClientRegistrationRequest /// JWS algorithm for UserInfo Responses. /// [JsonPropertyName(Parameters.UserInfoSignedResponseAlg)] - [AllowedValues(SigningAlgorithms.None, SigningAlgorithms.RS256)] public string? UserInfoSignedResponseAlg { get; init; } /// @@ -215,7 +212,6 @@ public record ClientRegistrationRequest /// JWS algorithm that MUST be used for Private Key JWT Client Authentication at the Token Endpoint. /// [JsonPropertyName(Parameters.TokenEndpointAuthSigningAlg)] - [AllowedValues(SigningAlgorithms.None, SigningAlgorithms.RS256)] public string? TokenEndpointAuthSigningAlg { get; init; } /// diff --git a/Abblix.Oidc.Server/Model/ConfigurationResponse.cs b/Abblix.Oidc.Server/Model/ConfigurationResponse.cs index d9e8ee6c..b81b3a18 100644 --- a/Abblix.Oidc.Server/Model/ConfigurationResponse.cs +++ b/Abblix.Oidc.Server/Model/ConfigurationResponse.cs @@ -19,6 +19,27 @@ // // CONTACT: For license inquiries or permissions, contact Abblix LLP at // info@abblix.com +// Abblix OIDC Server Library +// Copyright (c) Abblix LLP. All rights reserved. +// +// DISCLAIMER: This software is provided 'as-is', without any express or implied +// warranty. Use at your own risk. Abblix LLP is not liable for any damages +// arising from the use of this software. +// +// LICENSE RESTRICTIONS: This code may not be modified, copied, or redistributed +// in any form outside of the official GitHub repository at: +// https://github.com/Abblix/OIDC.Server. All development and modifications +// must occur within the official repository and are managed solely by Abblix LLP. +// +// Unauthorized use, modification, or distribution of this software is strictly +// prohibited and may be subject to legal action. +// +// For full licensing terms, please visit: +// +// https://oidc.abblix.com/license +// +// CONTACT: For license inquiries or permissions, contact Abblix LLP at +// info@abblix.com using System.Text.Json.Serialization; @@ -60,6 +81,7 @@ public static class Parameters public const string ResponseTypesSupported = "response_types_supported"; public const string ResponseModesSupported = "response_modes_supported"; public const string TokenEndpointAuthMethodsSupported = "token_endpoint_auth_methods_supported"; + public const string TokenEndpointAuthSigningAlgValuesSupported = "token_endpoint_auth_signing_alg_values_supported"; public const string IdTokenSigningAlgValuesSupported = "id_token_signing_alg_values_supported"; public const string SubjectTypesSupported = "subject_types_supported"; public const string CodeChallengeMethodsSupported = "code_challenge_methods_supported"; @@ -227,6 +249,14 @@ public static class Parameters [JsonPropertyName(Parameters.TokenEndpointAuthMethodsSupported)] public IEnumerable TokenEndpointAuthMethodsSupported { init; get; } = default!; + /// + /// Lists the signing algorithms supported by the OpenID Provider for authenticating clients at the token endpoint. + /// These algorithms are used to verify the signatures of the authentication requests sent to the token endpoint, + /// ensuring the integrity and authenticity of the requests. + /// + [JsonPropertyName(Parameters.TokenEndpointAuthSigningAlgValuesSupported)] + public IEnumerable? TokenEndpointAuthSigningAlgValuesSupported { get; init; } + /// /// Lists the ID token signing algorithm values supported by the OpenID Provider, /// indicating the algorithms that can be used to sign the ID token. diff --git a/Abblix.Oidc.Server/Model/TokenRequest.cs b/Abblix.Oidc.Server/Model/TokenRequest.cs index 344147ec..5a2bd477 100644 --- a/Abblix.Oidc.Server/Model/TokenRequest.cs +++ b/Abblix.Oidc.Server/Model/TokenRequest.cs @@ -91,7 +91,7 @@ public static class Parameters public Uri[]? Resources { get; set; } /// - /// The refresh token used to obtain a new access token. Required when using the refresh token grant type. + /// The refresh token used to get a new access token. Required when using the refresh token grant type. /// [JsonPropertyName(Parameters.RefreshToken)] public string? RefreshToken { get; set; } @@ -101,7 +101,6 @@ public static class Parameters /// This defines the permissions or resources the client is requesting access to. /// [JsonPropertyName(Parameters.Scope)] - [AllowedValues(Scopes.OpenId, Scopes.Profile, Scopes.Email, Scopes.Phone, Scopes.OfflineAccess)] public string[] Scope { get; set; } = Array.Empty(); /// diff --git a/Abblix.Oidc.Server/ServiceCollectionExtensions.cs b/Abblix.Oidc.Server/ServiceCollectionExtensions.cs index 146f6839..7724bfdf 100644 --- a/Abblix.Oidc.Server/ServiceCollectionExtensions.cs +++ b/Abblix.Oidc.Server/ServiceCollectionExtensions.cs @@ -112,7 +112,8 @@ public static IServiceCollection AddFeatures(this IServiceCollection services) .AddLogoutNotification() .AddStorages() .AddUserInfo() - .AddRequestObject(); + .AddRequestObject() + .AddBackChannelAuthentication(); } ///