Why does SignedCms.ComputeSignature() method throw "Provider's public key is invalid" exception?
This one may seem like a simple question, but it's really got me scratchi开发者_如何转开发ng my head. The trouble is that our code was working perfectly while running on the .NET framework 3.5, but now that we've switched to .NET 4.0 we're getting this error. Here's the relevant code:
SignedCms signed = new SignedCms(content, false);
CmsSigner signer = new CmsSigner(
SubjectIdentifierType.IssuerAndSerialNumber,
signingCertificate);
signed.ComputeSignature(signer);
Once again, on .NET 3.5 this works fine. But now that our project targets .NET 4.0, it throws a CryptographicException when using the exact same certificate.
[CryptographicException: Provider's public key is invalid.]
at System.Security.Cryptography.Pkcs.PkcsUtils.CreateSignerEncodeInfo(CmsSigner signer, Boolean silent)
at System.Security.Cryptography.Pkcs.SignedCms.Sign(CmsSigner signer, Boolean silent)
at System.Security.Cryptography.Pkcs.SignedCms.ComputeSignature(CmsSigner signer, Boolean silent)
at System.Security.Cryptography.Pkcs.SignedCms.ComputeSignature(CmsSigner signer)
...
Any ideas what could cause this?
UPDATE
After digging a little deeper, I've discovered that the problem only occurs when I deserialize the signing certificate myself. If I load it from the machine's store, everything works. So, evidentally, there's something wrong with my deserialization code. This code hasn't changed. The only difference is that it's now targeting .NET 4. Here's the code:
var result = new X509Certificate2(certificate);
byte[] decryptedKey;
// Long ugly code to decrypt private key omitted...
var rsa = new RSACryptoServiceProvider(new CspParameters
{
Flags = CspProviderFlags.UseMachineKeyStore
});
try
{
rsa.ImportCspBlob(decryptedKey);
result.PrivateKey = rsa;
return result;
}
catch
{
rsa.Dispose();
throw;
}
It turns out the problem was a subtle, breaking change in the RSACryptoServiceProvider. Prior to .NET 4.0, a new instance of this class would have a randomly generated key container name (of the form "CLR{GUID}"). In .NET 4.0, it leaves the key container name as null. (Or perhaps it's setting it to null because that's what I passed with the CspParameters.)
In any case, this means that each subsequent RSACryptoService provider overwrites the key pairs of any previous providers. When my code finally got around signing something, the key pair associated with the certificate had been overwritten by a different certificate's key pair. (Thus the "public key is invalid" error.)
The solution was to simply generate a random name myself. The change to my deserialization code is as follows:
var rsa = new RSACryptoServiceProvider(new CspParameters
{
Flags = CspProviderFlags.UseMachineKeyStore,
KeyContainerName = String.Format("MyPrefix {{{0}}}", Guid.NewGuid())
});
try {
rsa.PersistKeyInCsp = false; // ensure key is deleted when provider is disposed
rsa.ImportCspBlob(decryptedKey);
result.PrivateKey = rsa;
return result;
}
精彩评论