Azure-activedirectory-identitymodel-extensions-for-dotnet: JwtSecurityTokenHandler.ValidateToken throws ObjectDisposedException

Created on 3 Jun 2020  路  5Comments  路  Source: AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet


Issue moved from dotnet/aspnetcore#22490

  • Please respond to @vlkchris.

_From @vlkchris on Wednesday, June 3, 2020 9:50:59 AM_

Describe the bug

I use RSA.Create() to create an RSA instance, and then use .ImportSubjectPublicKeyInfo to import public key.

  • The first time I call my validate token method it runs fine.
  • The second time I call it, I get SecurityTokenInvalidSignatureException (but only when I import the same key) This exception contains ObjectDisposedException
  • The third time it is ok again.
  • The fourth time it fails
    and so on.

To Reproduce

Please check the following test project. It shows how and when Exceptions occur.
https://github.com/vlkchris/JwtHelper

Exceptions (if any)

SecurityTokenInvalidSignatureException
message:
IDX10503: Signature validation failed. Keys tried: 'Microsoft.IdentityModel.Tokens.RsaSecurityKey, KeyId: '', InternalId: '-mBjPYLobIiFBJ_q6K4LP22czPQyTHkg4NrmHV4h_ZE'. , KeyId:
'.
Exceptions caught:
'System.ObjectDisposedException: Cannot access a disposed object.
Object name: 'RSA'.
at System.Security.Cryptography.RSAImplementation.RSACng.ThrowIfDisposed()
at System.Security.Cryptography.RSAImplementation.RSACng.GetDuplicatedKeyHandle()
at System.Security.Cryptography.RSAImplementation.RSACng.VerifyHash(ReadOnlySpan1 hash, ReadOnlySpan1 signature, HashAlgorithmName hashAlgorithm, RSASignaturePadding padding)
at System.Security.Cryptography.RSAImplementation.RSACng.VerifyHash(Byte[] hash, Byte[] signature, HashAlgorithmName hashAlgorithm, RSASignaturePadding padding)
at Microsoft.IdentityModel.Tokens.AsymmetricAdapter.VerifyWithRsa(Byte[] bytes, Byte[] signature)
at Microsoft.IdentityModel.Tokens.AsymmetricAdapter.Verify(Byte[] bytes, Byte[] signature)
at Microsoft.IdentityModel.Tokens.AsymmetricSignatureProvider.Verify(Byte[] input, Byte[] signature)
at System.IdentityModel.Tokens.Jwt.JwtSecurityTokenHandler.ValidateSignature(Byte[] encodedBytes, Byte[] signature, SecurityKey key, String algorithm, SecurityToken securityToken, TokenValidationParameters validationParameters)
at System.IdentityModel.Tokens.Jwt.JwtSecurityTokenHandler.ValidateSignature(String token, TokenValidationParameters validationParameters)
'.
token: '{"alg":"RS256","typ":"JWT"}.{"someclaim":"some value"}'.

Further technical details

  • ASP.NET Core version = netcoreapp3.1

  • Include the output of dotnet --info

    dotnet --info
    .NET Core SDK (reflecting any global.json):
    Version: 3.1.101
    Commit: b377529961

Runtime Environment:
OS Name: Windows
OS Version: 10.0.17134
OS Platform: Windows
RID: win10-x64
Base Path: C:\Program Files\dotnet\sdk\3.1.101\

Host (useful for support):
Version: 3.1.1
Commit: a1388f194c

.NET Core SDKs installed:
2.1.505 [C:\Program Files\dotnet\sdk]
2.1.602 [C:\Program Files\dotnet\sdk]
2.1.700-preview-009601 [C:\Program Files\dotnet\sdk]
2.1.700 [C:\Program Files\dotnet\sdk]
2.1.800-preview-009677 [C:\Program Files\dotnet\sdk]
2.1.801 [C:\Program Files\dotnet\sdk]
2.2.105 [C:\Program Files\dotnet\sdk]
3.0.100 [C:\Program Files\dotnet\sdk]
3.1.101 [C:\Program Files\dotnet\sdk]

.NET Core runtimes installed:
Microsoft.AspNetCore.All 2.1.9 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.All]
Microsoft.AspNetCore.All 2.1.11 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.All]
Microsoft.AspNetCore.All 2.1.12 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.All]
Microsoft.AspNetCore.All 2.1.13 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.All]
Microsoft.AspNetCore.All 2.2.3 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.All]
Microsoft.AspNetCore.App 2.1.9 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
Microsoft.AspNetCore.App 2.1.11 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
Microsoft.AspNetCore.App 2.1.12 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
Microsoft.AspNetCore.App 2.1.13 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
Microsoft.AspNetCore.App 2.2.3 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
Microsoft.AspNetCore.App 3.0.0 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
Microsoft.AspNetCore.App 3.1.1 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
Microsoft.NETCore.App 2.1.9 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
Microsoft.NETCore.App 2.1.11 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
Microsoft.NETCore.App 2.1.12 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
Microsoft.NETCore.App 2.1.13 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
Microsoft.NETCore.App 2.2.3 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
Microsoft.NETCore.App 3.0.0 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
Microsoft.NETCore.App 3.1.1 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
Microsoft.WindowsDesktop.App 3.0.0 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App]
Microsoft.WindowsDesktop.App 3.1.1 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App]

To install additional .NET Core runtimes or SDKs:
https://aka.ms/dotnet-download

  • The IDE (VS / VS Code/ VS4Mac) you're running on, and it's version
    Visual Studio Professional 16.3.5
Customer reported Question

Most helpful comment

Hi @vlkchris, the issue here is that IdentityModel by default caches signature providers, together with and keys which you disposed.

In your sample, first validation succeeds, and IdentityModel adds a signature provider (with your key) to the cache.
In the second validation, IdentityModel recognizes the key and uses a cached signature provider to validate a token, but the key of that provider is now disposed.


There are a few possible workarounds for this issue:

  1. Don't dispose your RSA key here.

  2. Don't cache a signature provider for a key, by replacing this line with:

SecurityKey key = new RsaSecurityKey(rsa)
{
    CryptoProviderFactory = new CryptoProviderFactory()
    {
        CacheSignatureProviders = false
    }
};
  1. Don't cache a signature provider associated with keys from a TokenValidationParameters object, by adding the following here:
var validationParameters = new TokenValidationParameters
{
...
    CryptoProviderFactory = new CryptoProviderFactory()
    {
        CacheSignatureProviders = false
    }
...
};
  1. Turn off caching of signature providers at the app level by adding this line to startup:
CryptoProviderFactory.DefaultCacheSignatureProviders = false;

All 5 comments

Hi @vlkchris, the issue here is that IdentityModel by default caches signature providers, together with and keys which you disposed.

In your sample, first validation succeeds, and IdentityModel adds a signature provider (with your key) to the cache.
In the second validation, IdentityModel recognizes the key and uses a cached signature provider to validate a token, but the key of that provider is now disposed.


There are a few possible workarounds for this issue:

  1. Don't dispose your RSA key here.

  2. Don't cache a signature provider for a key, by replacing this line with:

SecurityKey key = new RsaSecurityKey(rsa)
{
    CryptoProviderFactory = new CryptoProviderFactory()
    {
        CacheSignatureProviders = false
    }
};
  1. Don't cache a signature provider associated with keys from a TokenValidationParameters object, by adding the following here:
var validationParameters = new TokenValidationParameters
{
...
    CryptoProviderFactory = new CryptoProviderFactory()
    {
        CacheSignatureProviders = false
    }
...
};
  1. Turn off caching of signature providers at the app level by adding this line to startup:
CryptoProviderFactory.DefaultCacheSignatureProviders = false;

Hi George,

Thank you for this explanation and te proposed workarounds.
I was already working with solution 1 and that is working fine, but I was worried about not disposing the object. Lots of examples on RSA contain a using statement, so I assumed that not disposing would be a bad idea.
Can you elaborate a bit on how this provider caching works? Am I right in assuming that these cached entries will never expire? I would prefer that cached entries would expire after not being used for a configured amount of time.

Thanks,
Chris

@vlkchris - Apologies for the delay in getting a response to you.

Crypto provider caching is introduced as a feature in 5.2.2 to improve perf in some of the hot paths.
In 5.5.0, caching of signature providers was enabled by default (#1129).

You are correct, cached entries will not expire by default and that is a great candidate for an improvement. I opened up a ticket #1447.

In the meantime, please use one of the workarounds described above.

@GeoK - No worries about the delay, there is no urgency on my side.
I am happy using workaround #1 for now, and if necessary I'll disable caching.
I'll be on the lookout for any development on this, subscribed to #1447

@GeoK @Tratcher @vlkchris I am closing this as we are looking at this holistically in: https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/issues/1434

Was this page helpful?
0 / 5 - 0 ratings