azure-activedirectory-identitymodel-extensions-for-dotnet: RSACryptoServiceProviderProxy crashes on Mono

System.Security.Cryptography.CryptographicException: Keyset does not exist
  at System.Security.Cryptography.RSACryptoServiceProvider.Common (System.Security.Cryptography.CspParameters p) [0x00000] in <filename unknown>:0 
  at System.Security.Cryptography.RSACryptoServiceProvider..ctor (Int32 dwKeySize, System.Security.Cryptography.CspParameters parameters) [0x00000] in <filename unknown>:0 
  at System.Security.Cryptography.RSACryptoServiceProvider..ctor (System.Security.Cryptography.CspParameters parameters) [0x00000] in <filename unknown>:0 
  at System.IdentityModel.Tokens.RSACryptoServiceProviderProxy..ctor (System.Security.Cryptography.RSACryptoServiceProvider rsa) [0x00000] in <filename unknown>:0 
  at System.IdentityModel.Tokens.AsymmetricSignatureProvider..ctor (System.IdentityModel.Tokens.AsymmetricSecurityKey key, System.String algorithm, Boolean willCreateSignatures) [0x00000] in <filename unknown>:0 
  at System.IdentityModel.Tokens.SignatureProviderFactory.CreateProvider (System.IdentityModel.Tokens.SecurityKey key, System.String algorithm, Boolean willCreateSignatures) [0x00000] in <filename unknown>:0 
  at System.IdentityModel.Tokens.SignatureProviderFactory.CreateForSigning (System.IdentityModel.Tokens.SecurityKey key, System.String algorithm) [0x00000] in <filename unknown>:0 
  at System.IdentityModel.Tokens.JwtSecurityTokenHandler.CreateSignature (System.String inputString, System.IdentityModel.Tokens.SecurityKey key, System.String algorithm, System.IdentityModel.Tokens.SignatureProvider signatureProvider) [0x00000] in <filename unknown>:0 
  at System.IdentityModel.Tokens.JwtSecurityTokenHandler.CreateToken (System.String issuer, System.String audience, System.Security.Claims.ClaimsIdentity subject, Nullable`1 notBefore, Nullable`1 expires, System.IdentityModel.Tokens.SigningCredentials signingCredentials, System.IdentityModel.Tokens.SignatureProvider signatureProvider) [0x00000] in <filename unknown>:0 
  at AspNet.Security.OpenIdConnect.Server.OpenIdConnectServerHandler+<CreateIdentityTokenAsync>d__21.MoveNext () [0x00000] in <filename unknown>:0 

Mono doesn’t use CryptoAPI - which is Windows-specific - and always initializes CspKeyContainerInfo.ProviderType to 1, which causes RSACryptoServiceProviderProxy to create a proxy around the existing RSA provider. Sadly, it crashes on Mono.

The bug disappears when you remove csp.Flags |= CspProviderFlags.UseExistingKey; from RSACryptoServiceProviderProxy’s constructor.

/cc @brentschmaltz @tushargupta51

About this issue

  • Original URL
  • State: closed
  • Created 9 years ago
  • Comments: 45 (28 by maintainers)

Commits related to this issue

Most upvoted comments

@in10se Sorry it took so long. My solution is actually pretty wildly stitched together. The idea is that the SignatureProvider actually creates (or, back then, created) an RsaCryptoServiceProviderProxy to manage the signing and verification of RSA keys, which failed on mono. So I first wrote a CustomRsaCryptoServiceProviderProxy which would be instantiated by a CustomAsymmetricSignatureProvider instead, but in the end just skipped that part altogether by having that class’ constructor call

var x509Key = key as X509SecurityKey;
if (x509Key != null)
{
    RSACryptoServiceProvider csp;
    if (willCreateSignatures)
    {
        csp = x509Key.PrivateKey as RSACryptoServiceProvider;
    }
    else
    {
        csp = x509Key.PublicKey as RSACryptoServiceProvider;
    }

    if (Type.GetType("Mono.Runtime") != null)
    {
        _rsaCryptoServiceProvider = csp;
    }
    else
    {
        _rsaCryptoServiceProviderProxy = new CustomRsaCryptoServiceProviderProxy(csp);
    }

    return;
}

The CustomAsymmetricSignatureProvider would be implictly used in the JwtSecurityTokenHandler’s CreateJwtSecurityToken method as

var cpf = signingCredentials?.Key;
// ...
cpf.CryptoProviderFactory = new CustomSignatureProviderFactory(...);

and in the Startup as

CryptoProviderFactory.Default = new CustomSignatureProviderFactory(...);
// ...
branch.UseJwtBearerAuthentication(
    new JwtBearerOptions
    {
        TokenValidationParameters = new TokenValidationParameters
        {
            CryptoProviderFactory = new CustomSignatureProviderFactory(...)
        }
    });

Note that CryptoProviderFactory.Default currently doesn’t seem to have any effect.

No particular concern for me, as it wouldn’t work even if you fixed this issue (since the ECDsa types are missing on Mono… we’d need a Mono TFM to fix that /cc @davidfowl)

FWIW, I opted for another temporary workaround: manually instantiating RSACryptoServiceProvider with CspParameters.ProviderType = 24 to bypass RSACryptoServiceProviderProxy.

Of course, it’s terribly insecure, as you have to load the private key from an unprotected embedded resource (note that I used the same trick to “mimic” .pfx support on CoreCLR).

The more I add temporary (and ugly) workarounds for security bugs, the more I realize we’re definitely on the bleeding edge with ASP.NET 5 on Core CLR and Mono: https://github.com/PinpointTownes/AspNet.Security.OpenIdConnect.Server/commit/e405908e9ed10f6eb5b10e5d3c6cf75cd0bf4926#diff-dc37cbfe3a6d682094d1bc6bb6a22117R191 😄