Skip to content
New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

OpenIdConnectHandler can't decrypt token with RSA-OAEP-256 and A128GCM #60367

Open
1 task done
saasen opened this issue Feb 13, 2025 · 6 comments
Open
1 task done

OpenIdConnectHandler can't decrypt token with RSA-OAEP-256 and A128GCM #60367

saasen opened this issue Feb 13, 2025 · 6 comments

Comments

@saasen
Copy link

saasen commented Feb 13, 2025

Is there an existing issue for this?

  • I have searched the existing issues

Describe the bug

Platform: macOS Sequoia Version 15.3
Chip: Apple M2 Max
.NET version: 6 and 8 (read below for more information)
Relevant packages: Microsoft.AspNetCore.Authentication.OpenIdConnect v6.0.36 and v8.0.13

I'm trying to connect and authenticate users through an OIDC server. I'm getting a valid ID-token in response from the OIDC server. However, I'm struggling with getting decryption of the token to work.

Relevant OIDC setup code:

var privateKey = new JsonWebKey(File.ReadAllText("private-key.json"));

.AddOpenIdConnect("oidc", config =>
{
    config.ResponseType = "code";
    config.TokenValidationParameters = new TokenValidationParameters
    {
        TokenDecryptionKeys = new[] { privateKey }
    };

    // Given this is .NET 8, you can fallback to this one here. When using .NET 6, it's already the default
    // config.UseSecurityTokenValidator = true;

    config.Scope.Clear();
    config.Scope.Add("openid");
    config.Scope.Add("profile");
   
    config.UsePkce = true;
    config.SaveTokens = true;
    config.GetClaimsFromUserInfoEndpoint = true;
});

If I use .NET 6, I'm guessing the SecurityTokenValidator will be used, and I get the following error message:

IDX10609: Decryption failed. No Keys tried: token: '<redacted>'.

If I use .NET 8, the JsonWebTokenHandler is used by default, and I get the following error message (which makes sense, since that was changed in .NET 7 or 8:

IDX10618: Key unwrap failed using decryption Keys: 'Microsoft.IdentityModel.Tokens.JsonWebKey, Use: 'enc',  Kid: '<redacted>', Kty: 'RSA', InternalId: '<redacted>'.
      '.
      Exceptions caught:
       ''.
      token: '<redacted>'.

I'm not sure why this isn't working? And the error message when using SecurityTokenValidator doesn't really make sense to me, but maybe it doesn't support this enc og alg so it skips using this decryption key?

This I've checked:

  • I'm pretty sure the private key I have locally is correct. It got all the needed parts which are required
  • The token I get from the OIDC server has the same kid as the private key I have

Another problem is that the token as part of the exception generated is corrupt (it only has 4 parts). Not sure why that happens, and/or if it's misleading.

Example header of token:

{
  "kid": "<redacted>",
  "cty": "JWT",
  "enc": "A128GCM",
  "alg": "RSA-OAEP-256"
}

Expected Behavior

I would expect the token to be correctly decrypted.

Steps To Reproduce

I would love to provide a reproduction, but I don't have any OIDC server I can use for that, and I would need to expose a private key as well...

Exceptions (if any)

  1. IDX10609: Decryption failed. No Keys tried: token: '<redacted>'.
IDX10618: Key unwrap failed using decryption Keys: 'Microsoft.IdentityModel.Tokens.JsonWebKey, Use: 'enc',  Kid: '<redacted>', Kty: 'RSA', InternalId: '<redacted>'.
      '.
      Exceptions caught:
       ''.
      token: '<redacted>'.

.NET Version

6 and 8

Anything else?

IDE: JetBrains Rider 2024.3.5

> dotnet --info

.NET SDK:
 Version:           8.0.100
 Commit:            57efcf1350
 Workload version:  8.0.100-manifests.6c33ef20

Runtime Environment:
 OS Name:     Mac OS X
 OS Version:  15.3
 OS Platform: Darwin
 RID:         osx-arm64
 Base Path:   /usr/local/share/dotnet/sdk/8.0.100/

.NET workloads installed:
 Workload version: 8.0.100-manifests.6c33ef20
There are no installed workloads to display.

Host:
  Version:      8.0.0
  Architecture: arm64
  Commit:       5535e31a71

.NET SDKs installed:
  6.0.411 [/usr/local/share/dotnet/sdk]
  6.0.417 [/usr/local/share/dotnet/sdk]
  6.0.425 [/usr/local/share/dotnet/sdk]
  7.0.305 [/usr/local/share/dotnet/sdk]
  8.0.100 [/usr/local/share/dotnet/sdk]

.NET runtimes installed:
  Microsoft.AspNetCore.App 6.0.19 [/usr/local/share/dotnet/shared/Microsoft.AspNetCore.App]
  Microsoft.AspNetCore.App 6.0.25 [/usr/local/share/dotnet/shared/Microsoft.AspNetCore.App]
  Microsoft.AspNetCore.App 6.0.33 [/usr/local/share/dotnet/shared/Microsoft.AspNetCore.App]
  Microsoft.AspNetCore.App 7.0.8 [/usr/local/share/dotnet/shared/Microsoft.AspNetCore.App]
  Microsoft.AspNetCore.App 8.0.0 [/usr/local/share/dotnet/shared/Microsoft.AspNetCore.App]
  Microsoft.NETCore.App 6.0.19 [/usr/local/share/dotnet/shared/Microsoft.NETCore.App]
  Microsoft.NETCore.App 6.0.25 [/usr/local/share/dotnet/shared/Microsoft.NETCore.App]
  Microsoft.NETCore.App 6.0.33 [/usr/local/share/dotnet/shared/Microsoft.NETCore.App]
  Microsoft.NETCore.App 7.0.8 [/usr/local/share/dotnet/shared/Microsoft.NETCore.App]
  Microsoft.NETCore.App 8.0.0 [/usr/local/share/dotnet/shared/Microsoft.NETCore.App]

Other architectures found:
  None

Environment variables:
  Not set

global.json file:
  /Users/s/code/company/oidc-test/global.json

Learn more:
  https://aka.ms/dotnet/info

Download .NET:
  https://aka.ms/dotnet/download

@saasen saasen changed the title OpenIdConnectHandler not working with RSA-OAEP-256 and A128GCM OpenIdConnectHandler can't decrypt RSA-OAEP-256 and A128GCM Feb 13, 2025
@saasen saasen changed the title OpenIdConnectHandler can't decrypt RSA-OAEP-256 and A128GCM OpenIdConnectHandler can't decrypt token with RSA-OAEP-256 and A128GCM Feb 13, 2025
@saasen
Copy link
Author

saasen commented Feb 14, 2025

I have narrowed the problem down to where it fails. It seems like in the SupportedAlgorithms class in Microsoft.IdentityModel.Tokens it contains what RSA encryption algorithms it supports. It does support RSA-OAEP, but my token says RSA-OAEP-256. Isn't RSA-OAEP considered unsafe? RSA-OAEP-256 uses SHA-256. Is there any workaround I can do to make this work myself? If not, is this something you are considering supporting?

// Type: Microsoft.IdentityModel.Tokens.SupportedAlgorithms
// Assembly: Microsoft.IdentityModel.Tokens, Version=8.0.1.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35

@saasen
Copy link
Author

saasen commented Feb 14, 2025

I tried adding support for decrypting RSA-OAEP-256 myself by implementing a custom ICryptoProvider. I also changed from passing a JsonWebKey into the TokenValidationParameters like this:

config.TokenValidationParameters = new TokenValidationParameters
{
    TokenDecryptionKeys = new[] { rsaSec }
};

I also changed to use my custom ICryptoProvider:

privateKey.CryptoProviderFactory.CustomCryptoProvider = new OAEP256CryptoProvider();

Here's the implementation of my custom ICryptoProvider (taken from AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet#1293):

public class OAEP256CryptoProvider : ICryptoProvider
{
    public const string OAEP_256 = "RSA-OAEP-256";

    public bool IsSupportedAlgorithm(string algorithm, params object[] args)
    {
        return (algorithm == OAEP_256);
    }

    public object Create(string algorithm, params object[] args)
    {
        return new RsaOaepKeyWrapProvider(args[0] as SecurityKey, algorithm);
    }

    public void Release(object cryptoInstance)
    {
    }

    private class RsaOaepKeyWrapProvider : KeyWrapProvider
    {
        public RsaOaepKeyWrapProvider(SecurityKey key, string algorithm)
        {
            Key = (RsaSecurityKey) key;
            Algorithm = algorithm;
        }

        protected override void Dispose(bool disposing)
        {
        }

        public override byte[] UnwrapKey(byte[] keyBytes)
        {
            return Key.Rsa.Decrypt(keyBytes, RSAEncryptionPadding.OaepSHA256);
        }

        public override byte[] WrapKey(byte[] keyBytes)
        {
            return Key.Rsa.Encrypt(keyBytes, RSAEncryptionPadding.OaepSHA256);
        }

        public override string Algorithm { get; }
        public override string Context { get; set; }
        public override RsaSecurityKey Key { get; }
    }
}

The code runs, but fails when unwrapping the key in some interop code:

Microsoft.AspNetCore.Authentication.OpenIdConnect.OpenIdConnectHandler[17]
      Exception occurred while processing message.
      Microsoft.IdentityModel.Tokens.SecurityTokenKeyWrapException: IDX10618: Key unwrap failed using decryption Keys: 'Microsoft.IdentityModel.Tokens.RsaSecurityKey, KeyId: '<redacted>', InternalId: '<redacted>'.
      '.
      Exceptions caught:
       'Interop+AppleCrypto+AppleCFErrorCryptographicException: The operation couldn’t be completed. (OSStatus error -50 - RSAdecrypt wrong input (err -27))
         at Interop.AppleCrypto.ExecuteTransform(ReadOnlySpan`1 source, SecKeyTransform transform)
         at Interop.AppleCrypto.RsaDecrypt(SafeSecKeyRefHandle privateKey, Byte[] data, RSAEncryptionPadding padding)
         at OAEP256CryptoProvider.RsaOaepKeyWrapProvider.UnwrapKey(Byte[] keyBytes) in /Users/s/code/help/signicat-oidc-test/SignicatOidcExample/RsaOaep256Provider.cs:line 40
         at Microsoft.IdentityModel.JsonWebTokens.JsonWebTokenHandler.GetContentEncryptionKeys(JsonWebToken jwtToken, TokenValidationParameters validationParameters, BaseConfiguration configuration)
      '.
      token: '<redacted>'.

Not sure if this helps...

@saasen
Copy link
Author

saasen commented Feb 15, 2025

I got a little further by generating a new pair of private/public JWKs. I also added the following in my launchSettings.json:

"DYLD_LIBRARY_PATH": "/opt/homebrew/opt/openssl@3/lib"

This helped, but now I have a different error. For some reason it's trying to load BCrypt.dll? That won't work on Mac, will it?

IDX10603: Decryption failed. Keys tried: 'Microsoft.IdentityModel.Tokens.SymmetricSecurityKey, KeyId: '', InternalId: 'fmIqRm1c3l_jwDj0DWm_njH32y6hChS-lHNecA82-1A'.
      '.
      Exceptions caught:
       'System.TypeInitializationException: The type initializer for 'Microsoft.IdentityModel.Tokens.AesGcm' threw an exception.
       ---> System.DllNotFoundException: Unable to load shared library 'BCrypt.dll' or one of its dependencies. In order to help diagnose loading problems, consider setting the DYLD_PRINT_LIBRARIES environment variable: 
      dlopen(/usr/local/share/dotnet/shared/Microsoft.NETCore.App/9.0.2/BCrypt.dll.dylib, 0x0001): tried: '/opt/homebrew/opt/openssl@3/lib/BCrypt.dll.dylib' (no such file), '/usr/local/share/dotnet/shared/Microsoft.NETCore.App/9.0.2/BCrypt.dll.dylib' (no such file), '/System/Volumes/Preboot/Cryptexes/OS/usr/local/share/dotnet/shared/Microsoft.NETCore.App/9.0.2/BCrypt.dll.dylib' (no such file), '/usr/local/share/dotnet/shared/Microsoft.NETCore.App/9.0.2/BCrypt.dll.dylib' (no such file)
      dlopen(/Users/s/code/d/e/f/bin/Debug/net9.0/BCrypt.dll.dylib, 0x0001): tried: '/opt/homebrew/opt/openssl@3/lib/BCrypt.dll.dylib' (no such file), '/Users/s/code/d/e/SignicatOidcExample/bin/Debug/net9.0/BCrypt.dll.dylib' (no such file), '/System/Volumes/Preboot/Cryptexes/OS/Users/s/code/d/e/SignicatOidcExample/bin/Debug/net9.0/BCrypt.dll.dylib' (no such file), '/Users/s/code/d/e/SignicatOidcExample/bin/Debug/net9.0/BCrypt.dll.dylib' (no such file)
      dlopen(BCrypt.dll.dylib, 0x0001): tried: '/opt/homebrew/opt/openssl@3/lib/BCrypt.dll.dylib' (no such file), 'BCrypt.dll.dylib' (no such file), '/System/Volumes/Preboot/Cryptexes/OSBCrypt.dll.dylib' (no such file), '/usr/lib/BCrypt.dll.dylib' (no such file, not in dyld cache), 'BCrypt.dll.dylib' (no such file)
      dlopen(/usr/local/share/dotnet/shared/Microsoft.NETCore.App/9.0.2/libBCrypt.dll.dylib, 0x0001): tried: '/opt/homebrew/opt/openssl@3/lib/libBCrypt.dll.dylib' (no such file), '/usr/local/share/dotnet/shared/Microsoft.NETCore.App/9.0.2/libBCrypt.dll.dylib' (no such file), '/System/Volumes/Preboot/Cryptexes/OS/usr/local/share/dotnet/shared/Microsoft.NETCore.App/9.0.2/libBCrypt.dll.dylib' (no such file), '/usr/local/share/dotnet/shared/Microsoft.NETCore.App/9.0.2/libBCrypt.dll.dylib' (no such file)
      dlopen(/Users/s/code/d/e/SignicatOidcExample/bin/Debug/net9.0/libBCrypt.dll.dylib, 0x0001): tried: '/opt/homebrew/opt/openssl@3/lib/libBCrypt.dll.dylib' (no such file), '/Users/s/code/d/e/SignicatOidcExample/bin/Debug/net9.0/libBCrypt.dll.dylib' (no such file), '/System/Volumes/Preboot/Cryptexes/OS/Users/s/code/d/e/SignicatOidcExample/bin/Debug/net9.0/libBCrypt.dll.dylib' (no such file), '/Users/s/code/d/e/SignicatOidcExample/bin/Debug/net9.0/libBCrypt.dll.dylib' (no such file)
      dlopen(libBCrypt.dll.dylib, 0x0001): tried: '/opt/homebrew/opt/openssl@3/lib/libBCrypt.dll.dylib' (no such file), 'libBCrypt.dll.dylib' (no such file), '/System/Volumes/Preboot/Cryptexes/OSlibBCrypt.dll.dylib' (no such file), '/usr/lib/libBCrypt.dll.dylib' (no such file, not in dyld cache), 'libBCrypt.dll.dylib' (no such file)
      dlopen(/usr/local/share/dotnet/shared/Microsoft.NETCore.App/9.0.2/BCrypt.dll, 0x0001): tried: '/opt/homebrew/opt/openssl@3/lib/BCrypt.dll' (no such file), '/usr/local/share/dotnet/shared/Microsoft.NETCore.App/9.0.2/BCrypt.dll' (no such file), '/System/Volumes/Preboot/Cryptexes/OS/usr/local/share/dotnet/shared/Microsoft.NETCore.App/9.0.2/BCrypt.dll' (no such file), '/usr/local/share/dotnet/shared/Microsoft.NETCore.App/9.0.2/BCrypt.dll' (no such file)
      dlopen(/Users/s/code/d/e/SignicatOidcExample/bin/Debug/net9.0/BCrypt.dll, 0x0001): tried: '/opt/homebrew/opt/openssl@3/lib/BCrypt.dll' (no such file), '/Users/s/code/d/e/SignicatOidcExample/bin/Debug/net9.0/BCrypt.dll' (no such file), '/System/Volumes/Preboot/Cryptexes/OS/Users/s/code/d/e/SignicatOidcExample/bin/Debug/net9.0/BCrypt.dll' (no such file), '/Users/s/code/d/e/SignicatOidcExample/bin/Debug/net9.0/BCrypt.dll' (no such file)
      dlopen(BCrypt.dll, 0x0001): tried: '/opt/homebrew/opt/openssl@3/lib/BCrypt.dll' (no such file), 'BCrypt.dll' (no such file), '/System/Volumes/Preboot/Cryptexes/OSBCrypt.dll' (no such file), '/usr/lib/BCrypt.dll' (no such file, not in dyld cache), 'BCrypt.dll' (no such file)
      dlopen(/usr/local/share/dotnet/shared/Microsoft.NETCore.App/9.0.2/libBCrypt.dll, 0x0001): tried: '/opt/homebrew/opt/openssl@3/lib/libBCrypt.dll' (no such file), '/usr/local/share/dotnet/shared/Microsoft.NETCore.App/9.0.2/libBCrypt.dll' (no such file), '/System/Volumes/Preboot/Cryptexes/OS/usr/local/share/dotnet/shared/Microsoft.NETCore.App/9.0.2/libBCrypt.dll' (no such file), '/usr/local/share/dotnet/shared/Microsoft.NETCore.App/9.0.2/libBCrypt.dll' (no such file)
      dlopen(/Users/s/code/d/e/SignicatOidcExample/bin/Debug/net9.0/libBCrypt.dll, 0x0001): tried: '/opt/homebrew/opt/openssl@3/lib/libBCrypt.dll' (no such file), '/Users/s/code/d/e/SignicatOidcExample/bin/Debug/net9.0/libBCrypt.dll' (no such file), '/System/Volumes/Preboot/Cryptexes/OS/Users/s/code/d/e/SignicatOidcExample/bin/Debug/net9.0/libBCrypt.dll' (no such file), '/Users/s/code/d/e/SignicatOidcExample/bin/Debug/net9.0/libBCrypt.dll' (no such file)
      dlopen(libBCrypt.dll, 0x0001): tried: '/opt/homebrew/opt/openssl@3/lib/libBCrypt.dll' (no such file), 'libBCrypt.dll' (no such file), '/System/Volumes/Preboot/Cryptexes/OSlibBCrypt.dll' (no such file), '/usr/lib/libBCrypt.dll' (no such file, not in dyld cache), 'libBCrypt.dll' (no such file)
      
         at Microsoft.IdentityModel.Tokens.Interop.BCrypt.BCryptOpenAlgorithmProvider(SafeAlgorithmHandle& phAlgorithm, String pszAlgId, String pszImplementation, Int32 dwFlags)
         at Microsoft.IdentityModel.Tokens.Cng.BCryptOpenAlgorithmProvider(String pszAlgId, String pszImplementation, OpenAlgorithmProviderFlags dwFlags)
         at Microsoft.IdentityModel.Tokens.AesBCryptModes.<>c__DisplayClass0_0.<OpenAesAlgorithm>b__0()
         at System.Lazy`1.ViaFactory(LazyThreadSafetyMode mode)
         at System.Lazy`1.ExecutionAndPublication(LazyHelper executionAndPublication, Boolean useDefaultConstructor)
         at System.Lazy`1.CreateValue()
         at Microsoft.IdentityModel.Tokens.AesGcm..cctor()
         --- End of inner exception stack trace ---
         at Microsoft.IdentityModel.Tokens.AesGcm.ImportKey(Byte[] key)
         at Microsoft.IdentityModel.Tokens.AesGcm..ctor(Byte[] key)
         at Microsoft.IdentityModel.Tokens.AuthenticatedEncryptionProvider.CreateAesGcmInstance()
         at Microsoft.IdentityModel.Tokens.DisposableObjectPool`1.CreateInstance()
         at Microsoft.IdentityModel.Tokens.DisposableObjectPool`1.Allocate()
         at Microsoft.IdentityModel.Tokens.AuthenticatedEncryptionProvider.DecryptWithAesGcm(Byte[] ciphertext, Byte[] authenticatedData, Byte[] iv, Byte[] authenticationTag)
         at Microsoft.IdentityModel.Tokens.AuthenticatedEncryptionProvider.Decrypt(Byte[] ciphertext, Byte[] authenticatedData, Byte[] iv, Byte[] authenticationTag)
         at Microsoft.IdentityModel.JsonWebTokens.JwtTokenUtilities.DecryptToken(CryptoProviderFactory cryptoProviderFactory, SecurityKey key, String encAlg, Byte[] ciphertext, Byte[] headerAscii, Byte[] initializationVector, Byte[] authenticationTag)
         at Microsoft.IdentityModel.JsonWebTokens.JwtTokenUtilities.DecryptJwtToken(SecurityToken securityToken, TokenValidationParameters validationParameters, JwtTokenDecryptionParameters decryptionParameters)
      '.
      token: 'null'

@saasen
Copy link
Author

saasen commented Feb 15, 2025

Based on the cyrptography documentation it says AES-GCM is supported and OAEP (I'm not sure why I can't see 256 in that table), so I don't understand what is not working. Shouldn't this work on Mac on .NET 6 and above?

@saasen
Copy link
Author

saasen commented Feb 15, 2025

I managed to get it working by implementing my own AES128GCM AuthenticatedEncryptionProvider:

private class Net8AlgorithmProvider : AuthenticatedEncryptionProvider
{
    public Net8AlgorithmProvider(SecurityKey key, string algorithm) : base(key, algorithm)
    {
    }
    
    protected override bool IsSupportedAlgorithm(SecurityKey key, string algorithm)
    {
        return algorithm == SecurityAlgorithms.Aes128Gcm;
    }

    public override AuthenticatedEncryptionResult Encrypt(byte[] plaintext, byte[] authenticatedData, byte[]? iv)
    {
        throw new NotImplementedException();
    }

    public override byte[] Decrypt(byte[] ciphertext, byte[] authenticatedData, byte[] iv, byte[] authenticationTag)
    {
        var clearBytes = new byte[ciphertext.Length];
        using var aesGcm = new AesGcm(GetKeyBytes(Key));
        aesGcm.Decrypt(iv, ciphertext, authenticationTag, clearBytes, authenticatedData);
        return clearBytes;
    }
}

I had to expand the ICryptoProvider like follows:

public class OAEP256CryptoProvider : ICryptoProvider
{
    public const string OAEP_256 = "RSA-OAEP-256";
    public const string AES_128 = "A128GCM";

    public bool IsSupportedAlgorithm(string algorithm, params object[] args)
    {
        return algorithm is OAEP_256 or SecurityAlgorithms.Aes128Gcm;
    }

    public object Create(string algorithm, params object[] args)
    {
        if (algorithm == OAEP_256)
        {
            return new RsaOaepKeyWrapProvider(args[0] as SecurityKey, algorithm);
        }
        else if (algorithm == SecurityAlgorithms.Aes128Gcm)
        {
            return new Net8AlgorithmProvider((SecurityKey)args[0], algorithm);
        }
        else
        {
            return null;
        }
    }

    public void Release(object cryptoInstance)
    {
    }
}

I would love to understand why I need to do this, and why it's not working natively...

@saasen
Copy link
Author

saasen commented Feb 15, 2025

Also, based on this, it should work on Mac?

# for free to join this conversation on GitHub. Already have an account? # to comment
Projects
None yet
Development

No branches or pull requests

1 participant