Search code examples
c#performanceperfview

Performance impact due to store.Open and store.Certificates.Find methods in X509Store


private static X509Certificate2 FindCertificate(string certificateSubject)
{
    const StoreName StoreName = StoreName.My;
    const StoreLocation StoreLocation = StoreLocation.LocalMachine;

    var store = new X509Store(StoreName, StoreLocation); 
    try
    {
        store.Open(OpenFlags.ReadOnly);

        // Find with the FindBySubjectName does fetch all the certs partially matching the subject name.
        // Hence, further filter for the certs that match the exact subject name.
        List<X509Certificate2> clientCertificates =
            store.Certificates.Find(X509FindType.FindBySubjectName, certificateSubject, validOnly: true)
                .Cast<X509Certificate2>()
                .Where(c => string.Equals(
                    c.Subject.Split(',').First().Trim(),
                    string.Concat("CN=", certificateSubject).Trim(),
                    StringComparison.OrdinalIgnoreCase)).ToList();

        if (!clientCertificates.Any())
        {
            throw new InvalidDataException(
                string.Format(CultureInfo.InvariantCulture, "Certificate {0} not found in the store {1}.", certificateSubject, StoreLocation.LocalMachine));
        }

        X509Certificate2 result = null;
        foreach (X509Certificate2 cert in clientCertificates)
        {
            DateTime now = DateTime.Now;
            DateTime effectiveDate = DateTime.Parse(cert.GetEffectiveDateString(), CultureInfo.CurrentCulture);
            DateTime expirationDate = DateTime.Parse(cert.GetExpirationDateString(), CultureInfo.CurrentCulture);
            if (effectiveDate <= now && expirationDate.Subtract(now) >= TimeSpan.FromDays(1))
            {
                result = cert;
                break;
            }
        }

        return result;
    }
    finally
    {
        store.Close();
    }
}

I have this code in my library and everytime a new request is created it calls this method. So basically is the requests per second is 1000 then it will be called 1000 times. When i was using the PerfView tool i noticed that 35% of the CPU is used by this method. Biggest culprit being store.Open and store.Certificates.Find method.

Anybody else has found out similar issues in their code. Also if you can share what did you do to resolve the performance impact due to this.


Solution

  • As long as the target system does not have an overwhelmingly large number of certificates installed, you can skip the call to X509Store's .Find() method. In my experience it does not perform very well and you are already doing the necessary filtering for your target subjectName afterwards.

    Also, don't loop through the collection of X509Certificate2 twice! If you just want the first matching certificate that meets all of your criteria, you can simplify things to a single LINQ statement like this:

    X509Certificate2 cert =
        store.Certificates.Cast<X509Certificate2>()
            .FirstOrDefault(xc =>
                xc.Subject.Equals("CN=" + certificateSubject, StringComparison.OrdinalIgnoreCase)
                && xc.NotAfter >= DateTime.Now.AddDays(-1)
                && xc.NotBefore <= DateTime.Now);
    

    (Note that depending on your usage and certificates, you may or may not need to modify the above to split the subject on commas as your original code does).

    Finally, as Wiktor Zychla mentioned, if your target machine does not have a great number of certificates installed, you could cache the entire list of certificates by calling store.Certificates.Cast<X509Certificate2>().ToList(), or if you have a limited number of subjectNames that you search for, it might be more efficient to simply cache the result of this method using a key derived from subjectName and an expiration based on the NotAfter property.