Search code examples
c#sslx509x509certificate2

C# Enumerate all hostnames supported by TLS certificates


I've been using System.Security.Cryptography.X509Certificates.X509Store for a while now to enumerate all the TLS certificates on the machine (to validate in code whether or not redirecting to SSL will work and warn if the certificate is missing). I have the following listing function which helps me diagnose issues like the one I'm having right now, but I can't seem to find the data I need:

        System.Security.Cryptography.X509Certificates.X509Store store = new System.Security.Cryptography.X509Certificates.X509Store(System.Security.Cryptography.X509Certificates.StoreLocation.LocalMachine);

        store.Open(System.Security.Cryptography.X509Certificates.OpenFlags.ReadOnly);

        DateTime utcNow = DateTime.UtcNow;
        foreach (System.Security.Cryptography.X509Certificates.X509Certificate2 mCert in store.Certificates)
        {
            writer.WriteStartElement("certificate");
            writer.WriteAttributeString("friendlyName", mCert.FriendlyName);
            writer.WriteAttributeString("subjectName", mCert.SubjectName.Name);
            writer.WriteAttributeString("subject", mCert.Subject);
            writer.WriteAttributeString("simpleName", mCert.GetNameInfo(System.Security.Cryptography.X509Certificates.X509NameType.SimpleName, false));
            writer.WriteAttributeString("dnsName", mCert.GetNameInfo(System.Security.Cryptography.X509Certificates.X509NameType.DnsName, false));
            writer.WriteAttributeString("certhash", mCert.GetCertHashString());
            writer.WriteAttributeString("effectivedate", mCert.GetEffectiveDateString());
            writer.WriteAttributeString("expirationdate", mCert.GetExpirationDateString());
            writer.WriteAttributeString("format", mCert.GetFormat());
            writer.WriteAttributeString("keyalgorithm", mCert.GetKeyAlgorithm());
            writer.WriteAttributeString("publickey", mCert.GetPublicKeyString());
            writer.WriteAttributeString("serialnumber", mCert.SerialNumber);
            writer.WriteAttributeString("hasprivatekey", XmlConvert.ToString(mCert.HasPrivateKey));
            writer.WriteAttributeString("issuer", mCert.Issuer);
            // NOTE: X509Certificate2 as provided by .NET uses local datetimes, so we need to convert them to the sane choice of UTC here 
            writer.WriteAttributeString("notafterutc", XmlConvert.ToString(mCert.NotAfter.ToUniversalTime(), XmlDateTimeSerializationMode.Utc));
            writer.WriteAttributeString("notbeforeutc", XmlConvert.ToString(mCert.NotBefore.ToUniversalTime(), XmlDateTimeSerializationMode.Utc));
            writer.WriteAttributeString("validnow", XmlConvert.ToString(mCert.NotBefore.ToUniversalTime() < utcNow && utcNow < mCert.NotAfter.ToUniversalTime()));
            writer.WriteAttributeString("timeuntilexpiration", XmlConvert.ToString(mCert.NotAfter.ToUniversalTime() - utcNow));
            writer.WriteAttributeString("thumbprint", mCert.Thumbprint);
            writer.WriteAttributeString("version", mCert.Version.ToString());
            writer.WriteEndElement(); // certificate
        }
        writer.WriteEndElement(); // certificates
        writer.WriteEndResponse();

Due to a desire to support a new alternative hostname on the same IP address, we recently switched to using a UCC certificate with multiple hosts. Unfortunately, the code above doesn't seem to be able to see any of the alternative host names specified in the certificates "Subject Alternative Name" field (the one UCC certificates use to specify multiple hosts), and I'm having trouble finding a property or function that gives me access to this data.

In short, does anyone know how to get the list of supported host names from the "Subject Alternative Name" field of a locally-installed certificate using C#?


Solution

  • Not as a separate list, but as a single list -- just find Subject Alternative Names extension and call .Format(bool multiLine) method:

    var sanNames = String.Empty;
    var san = mCert.Extensions["2.5.29.17"];
    if (san != null) {
        sanNames = san.Format(false);
    }
    if (!String.IsNullOrEmpty(sanNames)) {// write sanNames variable to XML}
    

    EDIT By James: Here is the full modification:

                System.Security.Cryptography.X509Certificates.X509Extension uccSan = mCert.Extensions["2.5.29.17"];
                if (uccSan != null)
                {
                    foreach (string nvp in uccSan.Format(true).Split(new string[] { Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries))
                    {
                        writer.WriteStartElement("alternateName");
                        string[] parts = nvp.Split('=');
                        string name = parts[0];
                        string value = (parts.Length > 0) ? parts[1] : null;
                        writer.WriteAttributeString("type", name);
                        writer.WriteAttributeString("value", value);
                        writer.WriteEndElement(); // alternateName
                    }
                }