azure-activedirectory-identitymodel-extensions-for-dotnet icon indicating copy to clipboard operation
azure-activedirectory-identitymodel-extensions-for-dotnet copied to clipboard

[Bug] JwtSecurityTokenHandler.ValidateToken cannot validate JWS if kid is omitted from header

Open Donald-Frederick opened this issue 1 year ago • 6 comments

Which version of Microsoft.IdentityModel are you using?
Microsoft.IdentityModel.JsonWebTokens 7.0.2.0

Where is the issue?

  • [x ] M.IM.JsonWebTokens
  • [ ] M.IM.KeyVaultExtensions
  • [ ] M.IM.Logging
  • [ ] M.IM.ManagedKeyVaultSecurityKey
  • [ ] M.IM.Protocols
  • [ ] M.IM.Protocols.OpenIdConnect
  • [ ] M.IM.Protocols.SignedHttpRequest
  • [ ] M.IM.Protocols.WsFederation
  • [ ] M.IM.TestExtensions
  • [ ] M.IM.Tokens
  • [ ] M.IM.Tokens.Saml
  • [ ] M.IM.Validators
  • [ ] M.IM.Xml
  • [x ] S.IM.Tokens.Jwt
  • Other (please describe)

Is this a new or an existing app? The app is in production and I have upgraded to a new version of Microsoft.IdentityModel.*

Repro

var jwtSource = jwt.Replace("Bearer ", "");
var secretBytes = Encoding.ASCII.GetBytes(secret);

// Validate
var tokenHandler = new JwtSecurityTokenHandler();
var validationParameters = new TokenValidationParameters
{
    ValidateIssuerSigningKey = true,
    ValidateAudience = false,
    ValidateIssuer = false,
    IssuerSigningKey = new Microsoft.IdentityModel.Tokens.SymmetricSecurityKey(secretBytes)
};

tokenHandler.ValidateToken(jwtSource, validationParameters, out Microsoft.IdentityModel.Tokens.SecurityToken validatedToken);

Expected behavior I have a JWS that does not specify kid in the header coming from another system. The key is a shared secret (see example code) and is specified in the TokenValidationParameters. The JwtSecurityTokenHandler.ValidateToken method should succeed

Actual behavior Microsoft.IdentityModel.Tokens.SecurityTokenSignatureKeyNotFoundException HResult=0x80131500 Message=IDX10503: Signature validation failed. Token does not have a kid. Keys tried: '[PII of type 'System.Text.StringBuilder' is hidden. For more details, see https://aka.ms/IdentityModel/PII.]'. Number of keys in TokenValidationParameters: '1'. Number of keys in Configuration: '0'. Exceptions caught: '[PII of type 'System.Text.StringBuilder' is hidden. For more details, see https://aka.ms/IdentityModel/PII.]'. token: '[PII of type 'System.IdentityModel.Tokens.Jwt.JwtSecurityToken' is hidden. For more details, see https://aka.ms/IdentityModel/PII.]'. See https://aka.ms/IDX10503 for details. Source=System.IdentityModel.Tokens.Jwt StackTrace: at System.IdentityModel.Tokens.Jwt.JwtSecurityTokenHandler.ValidateSignature(String token, JwtSecurityToken jwtToken, TokenValidationParameters validationParameters, BaseConfiguration configuration) at System.IdentityModel.Tokens.Jwt.JwtSecurityTokenHandler.ValidateSignatureAndIssuerSecurityKey(String token, JwtSecurityToken jwtToken, TokenValidationParameters validationParameters, BaseConfiguration configuration) at System.IdentityModel.Tokens.Jwt.JwtSecurityTokenHandler.ValidateJWS(String token, TokenValidationParameters validationParameters, BaseConfiguration currentConfiguration, SecurityToken& signatureValidatedToken, ExceptionDispatchInfo& exceptionThrown) --- End of stack trace from previous location --- at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw() at System.IdentityModel.Tokens.Jwt.JwtSecurityTokenHandler.ValidateToken(String token, JwtSecurityToken outerToken, TokenValidationParameters validationParameters, SecurityToken& signatureValidatedToken) at System.IdentityModel.Tokens.Jwt.JwtSecurityTokenHandler.ValidateToken(String token, TokenValidationParameters validationParameters, SecurityToken& validatedToken)

Possible solution

Additional context / logs / screenshots / links to code This is production code that worked with Microsoft.IdentityModel.JsonWebTokens 5.6.0 Per RFC 7514 kid is optional https://datatracker.ietf.org/doc/html/rfc7515#section-4.1.4

Donald-Frederick avatar Oct 10 '23 01:10 Donald-Frederick

@Donald-Frederick we don't always guarantee that there will not be breaking changes across major versions, however, we will have a look.

brentschmaltz avatar Oct 10 '23 15:10 brentschmaltz

This is production code that worked with Microsoft.IdentityModel.JsonWebTokens 5.6.0

@Donald-Frederick Does this code break on v6.3.3 too?


I have run your code on v5.6, v6.33 and v7.0.2 and get the same error each time with my valid token. (What is interesting is that starting in V6, it catches if my token has expired first and in 5.6 does not report expiry, but drops to the error IDX10503 error).

I believe I have an invalid setup with the secret which is patently different from yours and the error is probably valid for me...but it might be good for you to check v6 by you. For there may be something other than the RFC 7514 kid is optional scenario coming into play. IMHO

SecurityTokenSignatureKeyNotFoundException: IDX10503: Signature validation failed. Token does not have a kid.

CheetahChrome avatar Oct 12 '23 21:10 CheetahChrome

@Donald-Frederick @CheetahChrome the reason 6x reports expired first, is because we changed the logic to check for low cost errors before validating the signature, which is the most expensive.

brentschmaltz avatar Oct 12 '23 23:10 brentschmaltz

@Donald-Frederick we don't always guarantee that there will not be breaking changes across major versions, however, we will have a look.

We also ran into this issue when upgrading from Microsoft.IdentityModel.* 6.27 to 6.35. I tried going up version by version and this error started at 6.31 to be specific.

The jwt header we receive looks like this. { "alg": "HS512", "typ": "JWT" }.

After some investigation I found that the issue was actually that our secret key was too short, 40 bytes instead of 64 bytes. With 6.30 and below that was not "enforced" and tokenHandler.ValidateToken accepted the shorter key and validated the jwt correctly. Starting from 6.31 you will get the "IDX10503: Signature validation failed. Token does not have a kid." error if your secret is shorter than the expected (64 bytes in my case with HS512).

I could repro the issue with the code posted by @Donald-Frederick if using a key that was too small for the given algorithm. Just adding padding makes it run on 6.35.

if (secretBytes.Length < 64)
{
    Array.Resize(ref secretBytes, 64);
}
var issuerSigningKey = new SymmetricSecurityKey(secretBytes);

I think padding the key to its expected size is correct, but would be interesting to know why it started breaking on 6.31 for smaller keys.

M4ttsson avatar Jan 24 '24 09:01 M4ttsson