I have a C# windows form application. The user types in message, subject, to, and selects a signing certificate from a drop down to sign the email as well using X509Certificate2
class.
Here is how the snippet for how the dropdown (ComboBox SigningCertList) is populated:
try
{
X509Certificate2[] certs;
certs = CryptoHelper.GetSigningCertificateList();
SigningCertList.Items.AddRange(certs);
SigningCertList.ValueMember = "SerialNumber";
SigningCertList.DisplayMember = "FriendlyName";
SigningCertList.SelectedIndexChanged += new System.EventHandler(SigningCertList_SelectedIndexChanged);
SigningCertList.SelectedItem = 0;
}
Symptoms are odd. The combobox will show my signing certificate (installed from a p12 file). However, if I load the Windows Certificates MMC snapin, I cannot find it when doing a search. Upon reinstalling the certificate, I see it in the Windows Certificates MMC snapin, and now duplicated in the dropdown. Only the second (or last / recent) signing cert in the list actually signs it.
So how can I ensure X509Certificate2
class does not return duplicate signing certificates?
Here is the GetSigningCertificateList() method below: `public static X509Certificate2[] GetSigningCertificateList() { var list = new List();
int matches = 0;
X509Store localStore = new X509Store(StoreLocation.LocalMachine);
localStore.Open(OpenFlags.ReadOnly | OpenFlags.OpenExistingOnly);
try
{
foreach (X509Certificate2 cert in localStore.Certificates)
{
foreach (X509Extension extension in cert.Extensions)
{
X509KeyUsageExtension usageExtension = extension as X509KeyUsageExtension;
if (usageExtension != null)
{
bool matchesUsageRequirements = ((X509KeyUsageFlags.DigitalSignature & usageExtension.KeyUsages) == X509KeyUsageFlags.DigitalSignature);
if (matchesUsageRequirements)
{
list.Add(cert);
matches += 1;
}
}
}
}
}
finally
{
localStore.Close();
}
X509Store userStore = new X509Store(StoreLocation.CurrentUser);
userStore.Open(OpenFlags.ReadOnly | OpenFlags.OpenExistingOnly);
try
{
foreach (X509Certificate2 cert in userStore.Certificates)
{
foreach (X509Extension extension in cert.Extensions)
{
X509KeyUsageExtension usageExtension = extension as X509KeyUsageExtension;
if (usageExtension != null)
{
bool matchesUsageRequirements = ((X509KeyUsageFlags.DigitalSignature & usageExtension.KeyUsages) == X509KeyUsageFlags.DigitalSignature);
if ((matchesUsageRequirements) && cert.FriendlyName.IndexOf("MYcompanyname.",0) >= 0)
{
list.Add(cert);
matches += 1;
}
}
}
}
}
finally
{
userStore.Close();
}
return list.ToArray();
}
}`
You mention that you don't see a cert in MMC, but do in your app; and that when you install the cert via MMC it shows up twice. This suggests that you're using MMC to view the user My store (or the computer My store) but the certificate in question is normally present in the other location.
Once the certificate has been registered in two different stores (same store name, different location => different store) then Windows no longer considers it to be a duplicate (for one, the two instances can have different private key permissions). So while there's a duplicate to your application, there's not (intrinsically) to Windows or .NET.
You can prevent duplicates by standard dedup tactics, such as using a HashSet<X509Certificate2> instead of a List<X509Certificate2>. The default .Equals check (which is performed by the default comparator) will match if the issuer and serial number are the same. That should be unique as long as your certificates come from a public CA; but private PKI could recycle serial numbers or not guarantee uniqueness. If you're concerned you could use a custom comparator which uses whatever match logic you like.
So the easy dedup is to replace list = new List<X509Certificate2>()
with list = new HashSet<X509Certificate2>()
(though you should probably change the variable name).
A HashSet keeps only the first of the collisions; so if you want LocalMachine to be preferred you've already achieved that. If CurrentUser should win, you may want to switch your blocks around.
Two other things of note:
If a certificate has no key usage extension at all it's considered valid for all usages. Your code doesn't do that. (If you know that a "correct" cert in your application always will then there's no problem)
X509Store.Certificates returns new objects every call; you could reduce finalizations by calling Dispose on the certificates you don't return (or Reset for .NET 4.5.2 and below).