Automate client certificate one-to-one mapping in IIS 6.0 using C#

Automate client certificate one-to-one mapping in IIS 6.0 using C#

  • Comments 32

In PSS, we occasionally get requests from our customers wherein they want to automatically add entries for client certificate mapping in IIS or Active Directory (AD). That is either a 1-to-1, Many-to-1 or AD mapping for the client certificate authentication for the web site. I recommend going with AD mapping because that eases the management but it finally depends upon one's need.

I am not sure but I feel there is a security breach plus annoyance when an administrator has to laboriously enter the mappings for all the accounts/certificates (I am being specific to 1-to-1/Many-to-1 here).

The concern I feel when dealing with the administrator doing it for 1-to-1 and Many-to-1 are:

a. If there are hundreds of users you need to do this manually for everyone of those accounts and it's a pain.

b. Yes, the above can be automated using a script but then the second concern that arises is that whoever is running the script has to know the passwords for all these accounts to be mapped. I think this doesn't sound good.

I have written a sample application using which users can enter the mappings themselves in the IIS's Client certificate setting, i.e. entries having the client certificate mapped to a windows account (either a local IIS or AD account) and the corresponding password.

So this is how it works:

  • User accesses this web page from their workstation which already has the client certificate installed.
  • They are authenticated over Basic with SSL.
  • Browser sends across the client certificate as part of the HTTP web request.
  • This application gathers the user account, password plus the client certificate from the incoming HTTP web request and does the mapping in IIS.

 

image

I am adding the code here in case someone may want to extract the section for automated scripting instead of using it as a web app.

This code is also attached to this post as well.

using System;
using System.Data;
using System.Configuration;
using System.Web;
using System.Web.Security;
using System.Security.Cryptography.X509Certificates;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Web.UI.HtmlControls;
using System.DirectoryServices;
 
/* This sample application is to automate One-to-One Client certificate mapping in IIS 6.0.
 * User should be able to access this site from the browser and select the client certificate
 * in their machine which will be mapped to their account on the IIS server for 1-to-1 mapping. 
 * You need to deploy this application on the IIS server which is hosting the website(s) which 
 * needs client certificate mapping, preferably under its own virtual directory.
 *
 * Important:
 * - Have the authentication for this web application configured to use Basic along with SSL.
 * - Have the "Accept client certificates" or "Require client certificates" selected under 
 *   <Website> -> Properties -> Directory Security -> Secure communications -> Edit -> Client certificates
 * - Ensure the website that we want the mapping for is mentioned in the web.config file associated with
 *   this application under <appSettings>
 * - In the Web.config file we are impersonating an Administrator account for this application. 
 *   <identity impersonate="true" userName="Administrator" password="myadminpassword"/>
 *   This is done because non-admin users cannot modify the IIS metabase. If you do not want users to map
 *   entries themselves through web page you can change this to <identity impersonate="true" />.
 *   In such a case only admins can add the mappings for their user accounts. Non-admins won't be able to 
 *   add the client mapping entries.
 *   This is valid for both domain or local Windows NT accounts.
 * - This app is written using .Net 2.0, ASP.Net 2.0 and above in mind. You should be able to make it work
 *   with ASP.Net 1.1 as well.
 * - You may prefer to run this application under its own dedicated application pool to ensure stability and security.
 * 
 * DISCLAIMER: The code is not tested for production scenarios so use it at your own risk. 
 *             In case one wants to use batch scripting etc or some kind of console app instead 
 *             of web app you can extract the code section from this page which should work fine for the job.
 */
 
public partial class _Default : System.Web.UI.Page 
{
    protected void Page_Load(object sender, EventArgs e)
      {
        Response.Write("<B>Client Certificate One-to-One Mapping Application:</B><BR><HR>");
        Response.Write("Serial number: " + Request.ClientCertificate.SerialNumber + "</BR><HR>");
        Response.Write("Issuer: " + Request.ClientCertificate.Issuer + "</BR><HR>");
        Response.Write("Subject Name: " + Request.ClientCertificate.Subject + "</BR><HR>");
        if (Request.ClientCertificate.IsPresent)
        {
            Response.Write("Validity<BR>");
            Response.Write("&nbsp;&nbsp;&nbsp;&nbsp;Not before: " + Request.ClientCertificate.ValidFrom + "</BR>");
            Response.Write("&nbsp;&nbsp;&nbsp;&nbsp;Not after: " + Request.ClientCertificate.ValidUntil + "</BR><HR>");
        }
        else
            Response.Write("<B>There is no client certificate sent along with the request!</B><HR>");
 
        Response.Write("Authenticated User: " + Request.ServerVariables["AUTH_USER"] + "</BR><HR>");
        Response.Write("Authentication Type: " + Request.ServerVariables["AUTH_TYPE"] + "</BR><HR>");
    }
    protected void Button1_Click(object sender, EventArgs e)
    {
        string user = Request.ServerVariables["AUTH_USER"];
        string password = Request.ServerVariables["AUTH_PASSWORD"];
        string clientCertMappingName = "Mapping for " + user;  // <--- Our One-to-One Mapping name for the entry
        HttpClientCertificate cert = Request.ClientCertificate;
        /*
          If you want to map a client certificate located on the disk instead of the one as part of the 
          HTTP Web request try the code below.
          
          X509Certificate certificate = X509Certificate2.CreateFromCertFile(@"c:\cert.cer");
          X509Certificate certificate = cert.Certificate;
          byte[] certHash = certificate.GetRawCertData();
        */
        byte[] certHash = Request.ClientCertificate.Certificate;
        try
        {
        //Get the name of the Web site for which mapping has to be done from the App settings in the web.config file.
        string friendlyWebsiteName = ConfigurationManager.AppSettings["websitename"].ToString();
 
        //Get the Site Identifier based on the friendly name of the Web Site.
        string siteId = getsiteid(friendlyWebsiteName);
 
        if (siteId != null)
            {
            string sitePath = "IIS://localhost/W3SVC/" + siteId + "/IIsCertMapper";
            using (DirectoryEntry de = new DirectoryEntry(sitePath))
            {
                de.Invoke("CreateMapping", new object[] { certHash, user, password, clientCertMappingName, true });
            }
            Response.Write("Account Mapped: <B>" + Request.ServerVariables["AUTH_USER"] + "</B></BR>");
            Response.Write("Mapping Name: <B>" + "Mapping for " + Request.ServerVariables["AUTH_USER"] + "</B></BR>");
            Response.Write("Web Site: <B>" + friendlyWebsiteName + "</B></BR>");
            }
        else
            {
            Response.Write("<B>Web Site does not have a valid Site ID. Ensure we have the correct site name in the config file for this app.</B>");
            }
        }
        
        catch (System.Runtime.InteropServices.COMException)
        {
            Response.Write("A COM exception occurred while setting up the mapping.");
        }
        catch (SystemException)
        {
            Response.Write("An error occurred while setting up the mapping.");
        }
        catch (Exception)
        {
            Response.Write("An error occurred while setting up the mapping.");
        }
       
    }
 
    public string getsiteid(string websitename)
    {
        DirectoryEntry root = new DirectoryEntry("IIS://localhost/W3SVC");
        try
        {
            string siteid = null;
            foreach (DirectoryEntry de in root.Children)
            {
                if (de.SchemaClassName == "IIsWebServer")
                {
                    if (websitename.ToUpper() == de.Properties["ServerComment"].Value.ToString().ToUpper())
                        siteid = de.Name;
                }
            }
            if (siteid == null) return null;
            return siteid;
        }
        catch
        {
            return null;
        }
        finally
        {
            root.Close();
        }
    }
}

 

Ciao

Nice weekend!

Attachment: Code.zip
Leave a Comment
  • Please add 1 and 4 and type the answer here:
  • Post
  • PingBack from http://asp-net-hosting.simplynetdev.com/automate-client-certificate-one-to-one-mapping-in-iis-60-using-c/

  • How do you get the client certificate?  When I goto my dev site where your code is, it says no client certificate was sent.  So, how do I force my client certificate to be sent?

  • Brad, this means for some reason your clients are unable to send client certificates to the server. Please check my other posts around issues that may casue client certs not to be sent. You can search by "client certificate" tag.

  • Hi Saurabh,

    It's a very helpful article. Since new users don't have AD account, I can't use automapping. How do I import the public certifcate file (.cer) into specific folder in IIS? (the certificate file should be imported to IIS automatically when user requests a new account. then admin will create an AD account and map the cert in IIS maually) Thanks for your help.

  • Tin,

    I am not sure if I got the requirement correctly.

    For mapping the client cert to a specific account you need to have that .cer file imported/copied locally to the IIS server. If you have access to the .cer file you can manually copy it to the IIS server and once copied you can do the mapping.

    Let me know if this answers your questions or if you were looking for some automated way of doing all this.

  • Hi Saurabh,

    Thanks for your quick response. I do not have access to user's .cer file. I have a (SharePoint) new user request form where I can check the validity of user's CAC. Is there a way to import/copy into a temp folder in IIS server by script (when user submit the request)?

    Once the .cer file is in IIS, I can then create a new AD account and map the new account with the imported .cer file.

    Thanks.

  • Tin,

    Just wnat to confirm your requirement:

    -- User is sending HTTP request to the IIS web app which requires client certificate as part of the web request.

    -- Client Certificate is being sent as part of the web request and during this process you want to save the .cer format of the client cert coming from the Web request on the IIS server as a .cer file.

    Right?

  • Yes. It's exactly what I need. Thanks.

  • Tin, let me know if this works out for you. It's just a sample code but you should get the gist. You can modify it accordingly.

    We read the client cert from the incoming http web request and save the content in the .cer format, which you can later use for manually mapping to individual users.

    ========XXX========

    protected void Create_Cert_Button(object sender, EventArgs e)

       {

           //Name of the Directory where the Certificate will be stored for users. The name will be based on the Login name.

           string rootPath = "C:\\";

           string directoryToPutCertIn = Request.ServerVariables["AUTH_USER"].Replace('\\', '-');

           //Get the Client certificate from the Web Request.

           byte[] certHash = Request.ClientCertificate.Certificate;

           //Convert the based64 encode byte array into string.

           string cert = Convert.ToBase64String(certHash);

           //Check for a directory per user. If not then create one.

           //here in this example certificate is stored in the form C:\domain-user\mycert.cer

           if(!Directory.Exists(rootPath + directoryToPutCertIn))

               Directory.CreateDirectory(rootPath + directoryToPutCertIn);

           StreamWriter sw = new StreamWriter(rootPath + directoryToPutCertIn + "\\mycert.cer",false, System.Text.Encoding.ASCII);

           //Begin writing the certificate into the stream.

           sw.WriteLine("-----BEGIN CERTIFICATE-----");

           sw.WriteLine(cert);

           sw.WriteLine("-----END CERTIFICATE-----");

           sw.Close();

       }

    Hope this helps!

  • Thanks so much. You are the savior!

  • I implemented the same thing on our site.  It's interesting to see another person's take on it.  Thanks for posting the code.

  • We basically do the same thing but I have one more little problem.  Is there a way to have a stand alone script to import a CER file into IIS and do a one-to-one mapping.  I have tried to get it to work using IISCertMapper but keep getting ASN.1 errors on the import.  I can open the CER file, read the Cert blob but the script reports an ASN1 error and dies.

  • Mike, are you looking for some scripting language like VBScript etc or will a C# code be okay?

  • I was looking at and using VBScript if possible but I have not had much luck with it so far.  Can C# be run independently without the use of something like CS-SCript?

  • I did try a few things using VBScript and i fear it may not be as simple as it appears to be. You may have to go via pure native way using C/C++ and IIS Admin objects etc to achieve the same. I will see if i can find something on this. The issue here is that we are trying to read the cert blob from a file instead of a web request.

Page 1 of 3 (32 items) 123