jwt icon indicating copy to clipboard operation
jwt copied to clipboard

Key Acceptance and Curve Mismatch in ECDSA (ES256/ES384/ES512) Signature

Open JWTSecAPI opened this issue 8 months ago • 8 comments

Version, target platform, OS <=10.0.1

Description **Incorrect Curve Handling in ECDSA ** We discovered that your library permits public keys generated over the P-256 curve to be used for signature generation, even when the JWT header explicitly specifies "alg": "ES512". This is a standards violation and introduces a dangerous algorithm downgrade scenario. According to RFC 7518, Section 3.4, ES512 must use the P-521 curve. Allowing weaker curves can be exploited to mount signature confusion or cryptographic downgrade attacks.

How to reproduce Curve parameter validation to ensure alignment with the declared JWT algorithm. Rejecting any JWTs where the key type or curve does not strictly match the alg header.

Additional context An example token using P-256 curve key and 'alg' parameter is ES512

Image

JWTSecAPI avatar Apr 17 '25 02:04 JWTSecAPI

Does the same apply to the latest version, 11.0.0?

abatishchev avatar Apr 17 '25 02:04 abatishchev

Please post the code how you're using the library, currently there are no indication the library is involved.

abatishchev avatar Apr 17 '25 02:04 abatishchev

yes, I have updated the to the latest version 11.0.0 and the problem still exists. I used the same ecdsa key pair to generate two tokens with different header parameters: ES256 and ES512 , and both were verified successfully. The following is my test code:

    //Generate JWT 
    public static string GenerateToken(string privateKeyPem, bool useES512 = false)
    {
        var ecdsa = ECDsa.Create();
        ecdsa.ImportFromPem(privateKeyPem.ToCharArray());

        IJwtAlgorithm algorithm = useES512
            ? new ES512Algorithm(ecdsa,ecdsa)
            : new ES256Algorithm(ecdsa,ecdsa);

        var token = JwtBuilder.Create()
            .WithAlgorithm(algorithm)
            .AddClaim("exp", DateTimeOffset.UtcNow.AddMinutes(30).ToUnixTimeSeconds())
            .AddClaim("sub", "user123")
            .AddClaim("role", "admin")
            .Encode();

        return token;
    }

    // Verify JWT
    public static void VerifyToken(string token, string publicKeyPem, bool useES512 = false)
    {
        var ecdsa = ECDsa.Create();
        ecdsa.ImportFromPem(publicKeyPem.ToCharArray());

        IJwtAlgorithm algorithm = useES512
            ? new ES512Algorithm(ecdsa)
            : new ES256Algorithm(ecdsa);

        try
        {
            var payload = JwtBuilder.Create()
                .WithAlgorithm(algorithm)
                .MustVerifySignature()
                .Decode(token);

            Console.WriteLine("✅ Success:");
            Console.WriteLine(payload);
        }
        catch (Exception ex)
        {
            Console.WriteLine("❌ Error:" + ex.Message);
        }
    }

    public static void Main()
    {
        Console.WriteLine("===ES256===");
        var token256 = GenerateToken(EC_256_priKey, useES512: false);
        Console.WriteLine("Token (ES256):\n" + token256);
        VerifyToken(token256, EC_256_pubKey, useES512: false);

        Console.WriteLine("\n===ES512===");
        var token512 = GenerateToken(EC_256_priKey, useES512: true);
        Console.WriteLine("Token (ES512):\n" + token512);
        VerifyToken(token512, EC_256_pubKey, useES512: true);
    }

JWTSecAPI avatar Apr 17 '25 02:04 JWTSecAPI

Thanks for the code sample!

May I ask to the following: whenever you call the library's code, call the underlying BCL code instead.

The library is actually a pretty thin wrapper on top of the built-in code, especially when it comes to coding and encoding. For this family or algorithms, all it does is inside these classes:

  1. https://github.com/jwt-dotnet/jwt/blob/main/src/JWT/Algorithms/ECDSAAlgorithmFactory.cs#L46
  2. https://github.com/jwt-dotnet/jwt/blob/main/src/JWT/Algorithms/ES256Algorithm.cs
  3. https://github.com/jwt-dotnet/jwt/blob/main/src/JWT/Algorithms/ES512Algorithm.cs

I suspect it's the BCL code which (mis) behaves the way you describe. Can you please confirm or deny that?

If that's the case, what would your recommendation how to ramp up the validation to prevent the issue from happening?

abatishchev avatar Apr 17 '25 03:04 abatishchev

Thank you again for your response and the helpful clarification.

After analyzing the implementation in more detail, I’d like to share the following findings and suggestions regarding the root cause of the security issue I reported earlier.

  1. BCL Behavior is correct

As you mentioned, the library is a thin wrapper over the .NET Base Class Library (BCL), and it is indeed expected that BCL behaves in a flexible manner. BCL’s ECDsa class allows signing and verifying data using any named curve (e.g., P-256, P-384, P-521), regardless of how the application labels the algorithm in the JWT header. So yes, BCL works as intended and does not consider or validate any JWT-related metadata such as "alg" parameter. The cryptographic operations only depend on the key material and the HashAlgorithmName.

  1. The Core Issue: Lack of Curve Validation

The actual security issue lies in the JWT algorithm class logic, not the BCL.

In the current design, the "alg" parameter from the JWT header (e.g., "ES512") is only used to instantiate the corresponding algorithm class (ES512Algorithm) via the factory. However, there is no validation to ensure that the provided ECDsa key pair actually corresponds to the expected curve (e.g., P-521 for ES512).

This leads to a critical vulnerability: an attacker can sign a JWT using a P-256 key (for ES256) and label the token as "alg": "ES512". Since the library does not check the key curve against the expected algorithm, the signature is accepted by the ES512Algorithm instance—even though it’s based on a different, weaker curve.

3.Suggestions To prevent this issue, I recommend validating the key curve size when initializing algorithm classes like ES256Algorithm and ES512Algorithm. This ensures that the algorithm implementation is cryptographically consistent with the declared alg in the JWT header.

I'd be glad to contribute.

JWTSecAPI avatar Apr 17 '25 03:04 JWTSecAPI

I'd be glad to contribute

Yes, would you please! At least, start a PR and I'll help to get it to the finish line and release a new version asap.

abatishchev avatar Apr 17 '25 22:04 abatishchev

Speaking of implementing, should this base class for all ECDsa-based algorithms perform the validation?

abatishchev avatar Apr 17 '25 22:04 abatishchev

I will submit a PR as soon as possible. In addition, I think the modification of the base class(ECDSAAlgorithm) is optional, but I think adding a common validation logic to the base class is recommended.

JWTSecAPI avatar Apr 18 '25 07:04 JWTSecAPI