7

I found on the internet only way to got all the certificates from the iis and i do it like the following (c#):

var store = new X509Store(StoreName.My, StoreLocation.LocalMachine);
store.Open(OpenFlags.ReadOnly);
store.Certificates;

Now I try to get a specific certificate of specific binding, how can I do it in C#?

Erez
  • 87
  • 1
  • 6

4 Answers4

8

The certificates themselves hold absolutely no information about the bindings used in IIS, so you cannot retrieve the certificates from the machine and expect them to have anything related to IIS. You would need to query that information from IIS.

To do this, you will need add a reference to the library that can be found under %windir%\system32\inetsrv\Microsoft.Web.Administration.dll (note: IIS 7 or newer must be installed). After this, you can do something like the following to get the certificate:

ServerManager manager = new ServerManager();
Site yourSite = manager.Sites["yourSiteName"];

X509Certificate2 yourCertificate = null;

foreach (Binding binding in yourSite.Bindings)
{
    if (binding.Protocol == "https" && binding.EndPoint.ToString() == "127.0.0.1" /*your binding IP*/)
    {
        var store = new X509Store(StoreName.My, StoreLocation.LocalMachine);
        store.Open(OpenFlags.ReadOnly);
        yourCertificate = store.Certificates.Find(X509FindType.FindByThumbprint, ToHex(binding.CertificateHash), true)[0];
        break;
    }
}

public static string ToHex(byte[] ba)
{
    var hex = new StringBuilder(ba.Length * 2);
    foreach (byte b in ba) 
    {
        hex.AppendFormat("{0:x2}", b);
    }

    return hex.ToString();
}
Camilo Terevinto
  • 31,141
  • 6
  • 88
  • 120
  • That NuGet package is not Microsoft official and should be avoided. – Lex Li Dec 31 '15 at 14:20
  • that field can be set to anyone. Check the publisher and you will see. This assembly should always be added from IIS installation folder. – Lex Li Dec 31 '15 at 14:23
  • 1
    the full path is `%windir%\system32\inetsrv\Microsoft.Web.Administration.dll`. Use that please. – Lex Li Dec 31 '15 at 14:26
  • 1
    @LexLi done. I find it weird that Microsoft hasn't done this easier. – Camilo Terevinto Dec 31 '15 at 14:28
  • 1
    Microsoft does not reject those NuGet packages, so they mess things up. I have a blog post for MWA, https://blog.lextudio.com/2015/05/whats-microsoft-web-administration-and-the-horrible-facts-you-should-know/ – Lex Li Dec 31 '15 at 16:25
  • 1
    Hi, thanks a lot for the answer. the line: yourCertificate = store.Certificates.Find(X509FindType.FindByThumbprint, System.Convert.ToBase64String(binding.CertificateHash), true)[0]; returns to me the following exeption: Index was out of range. Must be non-negative and less than the size of the collection. Parameter name: index. do you know why and how to fix it? – Erez Dec 31 '15 at 17:25
  • @Erez that's because there was no certificates found by that ThumbPrint. Play with that until you find a Find method that searches using some of the properties you have – Camilo Terevinto Dec 31 '15 at 17:30
5

I think Camilo's answer has a small problem. As far as I can see (tested it) the code to find the certificate does not work, because System.Convert.ToBase64String(binding.CertificateHash) does not return a valid certificate thumbprint.

My version:

    /// <summary>
    /// Returns the https certificate used for a given local IIS website.
    /// </summary>
    /// <param name="sWebsite">Website url, e.g., "https://myserver.company.com"</param>
    /// <returns>certificate, null if not found</returns>
    private X509Certificate2 FindIisHttpsCert(string sWebsite)
    {
      Uri uriWebsite = new Uri(sWebsite);
      using (ServerManager sm = new ServerManager())
      {
        string sBindingPort = string.Format(":{0}:", uriWebsite.Port);
        Binding bdBestMatch = null;
        foreach (Site s in sm.Sites)
        {
          foreach (Binding bd in s.Bindings)
          {
            if (bd.BindingInformation.IndexOf(sBindingPort) >= 0)
            {
              string sBindingHostInfo = bd.BindingInformation.Substring(bd.BindingInformation.LastIndexOf(':') + 1);
              if (uriWebsite.Host.IndexOf(sBindingHostInfo, StringComparison.InvariantCultureIgnoreCase) == 0)
              {
                if ((bd.Protocol == "https") && ((bdBestMatch == null) || (bdBestMatch.BindingInformation.Length < bd.BindingInformation.Length)))
                  bdBestMatch = bd;
              }
            }
          }
        }
        if (bdBestMatch != null)
        {
          StringBuilder sbThumbPrint = new StringBuilder();
          for (int i = 0; i < bdBestMatch.CertificateHash.Length; i++)
            sbThumbPrint.AppendFormat("{0:X2}", bdBestMatch.CertificateHash[i]);

          X509Store store = new X509Store(bdBestMatch.CertificateStoreName, StoreLocation.LocalMachine);
          store.Open(OpenFlags.ReadOnly);
          X509Certificate2Collection coll = store.Certificates.Find(X509FindType.FindByThumbprint, sbThumbPrint.ToString(), true);
          if (coll.Count > 0)
            return coll[0];
        }
      }
      return null; // if no matching site was found
    }

This function also works if multiple https sites are hosted on the same server (tested) and should work if the site uses a port other than 443 (not tested). To get Binding info, %windir%\system32\inetsrv\Microsoft.Web.Administration.dll is used, as in Camilo's answer.

Berend Engelbrecht
  • 1,420
  • 15
  • 11
  • I experience the same error, and your code works perfectly! – Maestro May 22 '17 at 11:52
  • For my case, System.Convert.ToBase64String returns a valid thumbprint for a certificate belonging to workgroup. For one of the certificates belonging to a domain, it does not work for me. I resorted to String.Format("{0:X2}") suggested by Berend to make it work for the domain certificate. Not sure if it's because of the domain. – remondo Jan 13 '23 at 09:58
  • Update: a certificate I generated for an IP address does not work for me also using Convert.ToBase64String(). Maybe it's because of the 'dots' in the certificate CN. I don't know. I will be using 0:X2 in my final code. – remondo Jan 13 '23 at 10:16
0

I had tried the solution, but ran into problems NOT finding the certificate. Ended up being that the cert store needs to be properly specified based on the binding:

ServerManager manager = new ServerManager();
Site yourSite = manager.Sites["yourSiteName"];

X509Certificate2 yourCertificate = null;

foreach (Binding binding in yourSite.Bindings)
{
    if (binding.Protocol == "https" && binding.EndPoint.ToString() == "127.0.0.1" /*your binding IP*/)
    {
        var store = new X509Store(binding.CertificateStoreName, StoreLocation.LocalMachine);
        store.Open(OpenFlags.ReadOnly);
        var certs = store.Certificates.Find(X509FindType.FindByThumbprint, ToHex(binding.CertificateHash), true);

        if (certs.Count > 0)
            yourCertificate = certs[0];
        break;
    }
}

public static string ToHex(byte[] ba)
{
    var hex = new StringBuilder(ba.Length * 2);
    foreach (byte b in ba) 
    {
        hex.AppendFormat("{0:x2}", b);
    }

    return hex.ToString();
}
Jake
  • 554
  • 3
  • 10
-1

The following link should help:

basically store.Certificates returns a collection of all certificates in the particular store, you can then search through for the one you want. The link shows how to do this if you know the subject name of the certificate you want.

How to get X509Certificate from certificate store and generate xml signature data?

Community
  • 1
  • 1
C. Knight
  • 739
  • 2
  • 5
  • 20