0

I have a problem with X509AsymmetricSecurityKey.GetAsymmetricAlgorithm running in a standard unit test. The test has been passing for years running on .Net Framework version 4.5.2 (C#), however since upgrading the project to version 4.7.2 it has ben failing as GetAsymmetricAlgorithm returns null. The exact same code runs perfect outside the test.

X509Certificate2 cert = null;
var store = new X509Store(StoreLocation.LocalMachine);
store.Open(OpenFlags.ReadOnly);

// I'm actually using FindByThumbprint, just changing this here to protect keys
cert = store.Certificates[0];

// cert is valid X509, securityKey is valid
X509AsymmetricSecurityKey securityKey = new X509AsymmetricSecurityKey(cert);

// rsa is null
RSACryptoServiceProvider rsa = securityKey.GetAsymmetricAlgorithm(SecurityAlgorithms.RsaSha256Signature, true) as RSACryptoServiceProvider;

Same code, same certificate, running though test GetAsymmetricAlgorithm returns null, running on "live" code (class library called from WebAPI) it works perfect.

Any ideas why? I can't see anything in the docs for previous .Net version changes, nothing in the Microsoft docs.

https://learn.microsoft.com/en-us/dotnet/api/system.identitymodel.tokens.x509asymmetricsecuritykey.getasymmetricalgorithm?view=netframework-4.7.2

Thanks for any help on this.

TimTrott
  • 13
  • 4

1 Answers1

2

As Crypt32 suggested in a comment, the problem is that after you upgraded from targeting <= 4.6.2 to targeting 4.7(+) you got a "retargeting change" which says that GetAsymmetricAlgorithm is allowed to return instances of RSACng, which is the better RSA class in .NET Framework.

The best action in your code is to change the line to

RSA rsa = securityKey.GetAsymmetricAlgorithm(SecurityAlgorithms.RsaSha256Signature, true) as RSA;

Then find the places that the code no longer compiles, and change from the RSACryptoServiceProvider variant method to the new RSA (base class) methods. (e.g. SignData(byte[], object) => SignData(byte[], RSASignaturePadding)).

You really want to avoid saying RSACng or RSACryptoServiceProvider if you can help it, since there are theoretical cases where RSACng won't work, and RSACryptoServiceProvider will be returned instead (older smartcards / HSMs which have a CAPI driver, but not a CNG driver).

This particular retargeting change is the System.IdentityModel version of https://learn.microsoft.com/en-us/dotnet/framework/migration-guide/retargeting/4.5-4.7.2#wcf-transport-security-supports-certificates-stored-using-cng, which seems to have not been written down. If you need to turn this off, the setting name is Switch.System.IdentityModel.DisableCngCertificates.

bartonjs
  • 30,352
  • 2
  • 71
  • 111
  • Thanks for your answer. I initially was able to get around the problem by using `RSACryptoServiceProvider rsa = cert.PrivateKey as RSACryptoServiceProvider;` and have since updated to include your code. Marked as answer. – TimTrott Nov 29 '18 at 15:16
  • I would recommend to avoid `RSACryptoServiceProvider` explicit usage for now and forever. `cert.PrivateKey` is literally obsolete and is not recommended for use, because it doesn't support CNG keys. You really should consider @bartonjs recommendations. – Crypt32 Nov 29 '18 at 16:38
  • This solved a problem we had with a custom smartcard certificate, code couldn't access private key in 4.7.2 in case when a WCF client with WsHttpBinding and message security is used. DisableCngCertificates was the only solution that worked for us. – Lucius Dec 28 '18 at 12:21