问题
I am decrypting SMIME.P7M attachments in emails. I currently have the following
EnvelopedCms envDate = new EnvelopedCms(new ContentInfo(data));
envDate.Decode(data);
RecipientInfoCollection recips = envDate.RecipientInfos;
RecipientInfo recipin = recips[0];
X509Certificate2 x509_2 = LoadCertificate2(StoreLocation.CurrentUser, (SubjectIdentifier)recipin.RecipientIdentifier);
And the load certificates looks like this
public static X509Certificate2 LoadCertificate2(StoreLocation storeLocation, SubjectIdentifier identifier)
{
X509Store store = new X509Store(storeLocation);
store.Open(OpenFlags.ReadOnly);
X509Certificate2Collection certCollection = store.Certificates;
X509Certificate2 x509 = null;
X509IssuerSerial issuerSerial;
if (identifier.Type == SubjectIdentifierType.IssuerAndSerialNumber)
{
issuerSerial = (X509IssuerSerial)identifier.Value;
}
foreach (X509Certificate2 c in certCollection)
{
Console.WriteLine("{0}Valid Date: {1}{0}", Environment.NewLine, c.NotBefore);
if (c.SerialNumber == issuerSerial.SerialNumber && c.Issuer == issuerSerial.IssuerName)
{
x509 = c;
break;
}
}
if (x509 == null)
Console.WriteLine("A x509 certificate for was not found");
store.Close();
return x509;
}
The above code only get the first recipient RecipientInfo recipin = recips[0]; however is the most efficient method to get the appropriate certificate to loop through each recipient and check the store for the SubjectIdentifier?
Once obtaining the correct certificate I use this
X509Certificate2Collection col = new X509Certificate2Collection(x509_2);
envDate.Decrypt(col);
decData = envDate.ContentInfo.Content;
This prompts for the PIN associated with the privatekey of the certificate, how can I add the PIN prior to calling decrypt so there is no prompt?
回答1:
The EnvelopedCms class in .NET Framework doesn't have any way to let you easily programmatically apply a PIN (or other unlocking mechanism); particularly so if the certificate exists in the CurrentUser\My or LocalMachine\My stores (because those are searched prior to any certificates in the extraStore collection).
On .NET Framework 4.7+ you could accomplish it in a very roundabout way for CNG-accessible keys provided that the certificate is not also in the CurrentUser\My or LocalMachine\My stores:
CngKey key = ExerciseLeftToTheReader();
key.SetProperty(new CngProperty("SmartCardPin", pin, CngPropertyOptions.None));
X509Certificate2 cert = DifferentExerciseLeftToTheReader();
// You need to use tmpCert because this won't do good things if the certificate
// already knows about/how-to-find its associated private key
using (key)
using (X509Certificate2 tmpCert = new X509Certificate2(cert.RawData))
{
// Need to NOT read the HasPrivateKey property until after the property set. Debugger beware.
NativeMethods.CertSetCertificateContextProperty(
tmpCert.Handle,
CERT_NCRYPT_KEY_HANDLE_PROP_ID,
CERT_SET_PROPERTY_INHIBIT_PERSIST_FLAG,
key.Handle);
envelopedCms.Decrypt(new X509Certificate2Collection(tmpCert));
}
(And, of course, you need to define the P/Invoke for CertSetCertificateContextProperty)
In .NET Core 3.0 this becomes easier (preview 2 is currently available with this feature)... though it does leave you with the burden of determining which RecipientInfo is you, and which key goes with it:
RecipientInfo recipientInfo = FigureOutWhichOneYouCanMatch();
CngKey key = ExerciseLeftToTheReader();
key.SetProperty(new CngProperty("SmartCardPin", pin, CngPropertyOptions.None));
using (key)
using (RSA rsa = new RSACng(key))
{
envelopedCms.Decrypt(recipientInfo, rsa);
}
来源:https://stackoverflow.com/questions/54801472/envelopedcms-how-to-properly-identify-certificate-to-decrypt-and-not-request-a-p