Skip to content

Commit

Permalink
Add signing algorithms dynamic validation
Browse files Browse the repository at this point in the history
  • Loading branch information
kirill-abblix committed Oct 9, 2024
1 parent e2f769c commit 2e01ad5
Show file tree
Hide file tree
Showing 25 changed files with 281 additions and 55 deletions.
2 changes: 1 addition & 1 deletion Abblix.Jwt/IJsonWebTokenCreator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ public interface IJsonWebTokenCreator
/// <summary>
/// Lists the all supported signing algorithms for JWT creation.
/// </summary>
IEnumerable<string> SigningAlgValuesSupported { get; }
IEnumerable<string> SignedResponseAlgorithmsSupported { get; }

/// <summary>
/// Issues a new JWT based on the specified JsonWebToken object, signing key, and optional encrypting key.
Expand Down
2 changes: 1 addition & 1 deletion Abblix.Jwt/IJsonWebTokenValidator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
/// </summary>
IEnumerable<string> SigningAlgValuesSupported { get; }
IEnumerable<string> SigningAlgorithmsSupported { get; }

/// <summary>
/// Asynchronously validates a JWT against a set of specified parameters.
Expand Down
8 changes: 2 additions & 6 deletions Abblix.Jwt/JsonWebKeyExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -41,11 +41,7 @@ public static class JsonWebKeyExtensions
/// <exception cref="InvalidOperationException">Thrown when the algorithm is not supported.</exception>
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);
}

/// <summary>
Expand All @@ -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),
Expand Down
2 changes: 1 addition & 1 deletion Abblix.Jwt/JsonWebTokenCreator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
/// </summary>
public IEnumerable<string> SigningAlgValuesSupported => JsonWebTokenAlgorithms.SigningAlgValuesSupported;
public IEnumerable<string> SignedResponseAlgorithmsSupported => JsonWebTokenAlgorithms.SigningAlgValuesSupported;

/// <summary>
/// Asynchronously issues a JWT based on the specified JsonWebToken, signing key, and optional encrypting key.
Expand Down
2 changes: 1 addition & 1 deletion Abblix.Jwt/JsonWebTokenValidator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
/// </summary>
public IEnumerable<string> SigningAlgValuesSupported => JsonWebTokenAlgorithms.SigningAlgValuesSupported;
public IEnumerable<string> SigningAlgorithmsSupported => JsonWebTokenAlgorithms.SigningAlgValuesSupported;

/// <summary>
/// Asynchronously validates a JWT string against specified validation parameters.
Expand Down
21 changes: 17 additions & 4 deletions Abblix.Jwt/SigningAlgorithms.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,16 +27,29 @@ namespace Abblix.Jwt;
/// </summary>
public static class SigningAlgorithms
{
/// <summary>
/// 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.
/// </summary>
public const string None = "none";

/// <summary>
/// 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.
/// </summary>
public const string RS256 = "RS256";

/// <summary>
/// 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.
/// </summary>
public const string None = "none";
public const string PS256 = "PS256";

/// <summary>
/// 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.
/// </summary>
public const string ES256 = "ES256";
}
9 changes: 5 additions & 4 deletions Abblix.Oidc.Server.Mvc/Controllers/DiscoveryController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,7 @@ public Task<ActionResult<ConfigurationResponse>> ConfigurationAsync(
ResponseModesSupported = authorizationHandler.Metadata.ResponseModesSupported,

TokenEndpointAuthMethodsSupported = clientAuthenticator.ClientAuthenticationMethodsSupported,
TokenEndpointAuthSigningAlgValuesSupported = jwtValidator.SigningAlgorithmsSupported,

SubjectTypesSupported = subjectTypeConverter.SubjectTypesSupported,
PromptValuesSupported = authorizationHandler.Metadata.PromptValuesSupported,
Expand All @@ -125,19 +126,19 @@ public Task<ActionResult<ConfigurationResponse>> 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<ActionResult<ConfigurationResponse>>(response);
Expand Down
13 changes: 8 additions & 5 deletions Abblix.Oidc.Server.Mvc/Formatters/UserInfoResponseFormatter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -71,13 +71,19 @@ public async Task<ActionResult<UserInfoResponse>> 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
{
Expand All @@ -96,9 +102,6 @@ public async Task<ActionResult<UserInfoResponse>> FormatResponseAsync(
Content = await _clientJwtFormatter.FormatAsync(token, clientInfo),
};

case UserInfoFoundResponse { User: var user }:
return new JsonResult(user);

case UserInfoErrorResponse error:
return new BadRequestObjectResult(error);

Expand Down
2 changes: 1 addition & 1 deletion Abblix.Oidc.Server.Mvc/Model/AuthorizationRequest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,7 @@ public record AuthorizationRequest
/// supporting enhanced security for public clients in the PKCE flow.
/// </summary>
[BindProperty(SupportsGet = true, Name = Parameters.CodeChallengeMethod)]
[AllowedValues(CodeChallengeMethods.Plain, CodeChallengeMethods.S256)]
[AllowedValues(CodeChallengeMethods.Plain, CodeChallengeMethods.S256, CodeChallengeMethods.S512)]
public string? CodeChallengeMethod { get; init; }

/// <summary>
Expand Down
5 changes: 1 addition & 4 deletions Abblix.Oidc.Server.Mvc/Model/ClientRegistrationRequest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,6 @@ public record ClientRegistrationRequest
/// Specifies the algorithm that the OpenID Provider should use to sign ID Tokens sent to this client.
/// </summary>
[JsonPropertyName(Parameters.IdTokenSignedResponseAlg)]
[AllowedValues(SigningAlgorithms.None, SigningAlgorithms.RS256)]
public string? IdTokenSignedResponseAlg { get; init; }

/// <summary>
Expand All @@ -188,7 +187,6 @@ public record ClientRegistrationRequest
/// Indicates the preferred algorithm for signing UserInfo responses sent to this client.
/// </summary>
[JsonPropertyName(Parameters.UserInfoSignedResponseAlg)]
[AllowedValues(SigningAlgorithms.None, SigningAlgorithms.RS256)]
public string? UserInfoSignedResponseAlg { get; init; }

/// <summary>
Expand Down Expand Up @@ -244,7 +242,6 @@ public record ClientRegistrationRequest
/// Endpoint. Specifies the algorithm to be used by the client for signing JWTs used in client authentication.
/// </summary>
[JsonPropertyName(Parameters.TokenEndpointAuthSigningAlg)]
[AllowedValues(SigningAlgorithms.None, SigningAlgorithms.RS256)]
public string? TokenEndpointAuthSigningAlg { get; init; }

/// <summary>
Expand Down Expand Up @@ -403,7 +400,7 @@ public Core.ClientRegistrationRequest Map()
RequestObjectSigningAlg = RequestObjectSigningAlg,

IdTokenEncryptedResponseAlg = IdTokenEncryptedResponseAlg,
IdTokenEncryptedResponseEnc = RequestObjectEncryptionEnc,
IdTokenEncryptedResponseEnc = IdTokenEncryptedResponseEnc,
IdTokenSignedResponseAlg = IdTokenSignedResponseAlg,

TokenEndpointAuthMethod = TokenEndpointAuthMethod,
Expand Down
6 changes: 0 additions & 6 deletions Abblix.Oidc.Server.Mvc/Model/TokenRequest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -83,12 +83,6 @@ public record TokenRequest
/// Scopes specify the level of access required and the associated permissions.
/// </summary>
[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<string>();

Expand Down
6 changes: 6 additions & 0 deletions Abblix.Oidc.Server/Common/Constants/CodeChallengeMethods.cs
Original file line number Diff line number Diff line change
Expand Up @@ -36,4 +36,10 @@ public static class CodeChallengeMethods
/// Represents the "S256" code challenge method where the code verifier is hashed using SHA-256.
/// </summary>
public const string S256 = "S256";

/// <summary>
/// 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.
/// </summary>
public const string S512 = "S512";
}
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ public record AuthorizationEndpointMetadata
/// </summary>
public List<string> CodeChallengeMethodsSupported { get; init; } = new()
{
CodeChallengeMethods.S512,
CodeChallengeMethods.S256,
CodeChallengeMethods.Plain,
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
// 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;

namespace Abblix.Oidc.Server.Endpoints.DynamicClientManagement.Validation;

/// <summary>
/// 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.
/// </summary>
public class SignedResponseAlgorithmsValidator: SyncClientRegistrationContextValidator
{
/// <summary>
/// Initializes a new instance of the <see cref="SignedResponseAlgorithmsValidator"/> class.
/// The constructor takes a JWT creator to access the supported signing algorithms for validation.
/// </summary>
/// <param name="jwtCreator">The service responsible for creating signed JWTs.</param>
public SignedResponseAlgorithmsValidator(IJsonWebTokenCreator jwtCreator)
{
_jwtCreator = jwtCreator;
}

private readonly IJsonWebTokenCreator _jwtCreator;

/// <summary>
/// Validates the signing algorithms specified for ID tokens and user info responses.
/// This method ensures that the JWT creator supports the requested algorithms.
/// </summary>
/// <param name="context">The validation context containing the client registration data.</param>
/// <returns>
/// A <see cref="ClientRegistrationValidationError"/> if any signing algorithm is not supported;
/// otherwise, null if all validations are successful.
/// </returns>
protected override ClientRegistrationValidationError? Validate(ClientRegistrationValidationContext context)
{
var request = context.Request;
return Validate( request.IdTokenSignedResponseAlg, "id_token") ??
Validate(request.UserInfoSignedResponseAlg, "userinfo_endpoint");
}

/// <summary>
/// Validates that the JWT creator supports the specified signing algorithm.
/// If the algorithm is not supported, it returns a validation error.
/// </summary>
/// <param name="alg">The signing algorithm to validate.</param>
/// <param name="description">
/// A description used in the error message to identify which signing algorithm is invalid.</param>
/// <returns>
/// A <see cref="ClientRegistrationValidationError"/> if the algorithm is not supported; otherwise, null.
/// </returns>
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;
}
}
Loading

0 comments on commit 2e01ad5

Please sign in to comment.