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(" Not before: " + Request.ClientCertificate.ValidFrom + "</BR>"); Response.Write(" 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();
}
}
}