ASP.NET and Client Certificate : Without .NET Enterprise Services
Microsoft has a fix to the System.DLL assembly that will allow an ASP.NET application to call a Web Service using the ASPNET account and SSL. You can find the notes to this fix at the following site http://support.microsoft.com/?id=817854
The architecture of the managed code is similar to the previous managed code version except there is no need to package the components in a COM+ Application. As a result all components can run in the same process space (and AppDomain). This will generally give better performance. However, you must find another way to keep your connections to the backend persisted (i.e., no Object Pooling). This is where you should consider using the ServicePoint and ServicePointManager classes.
Required Steps
1. Configure the ASPNET account to have access to the client certificate. You can accomplish this by using the winhttpcertcfg tool that comes with the WinHTTP SDK
a. winhttpcertcfg -g -c LOCAL_MACHINE\My -s MyCertificate -a ASPNET
b. Note: you can see who has access to a particular store by issuing the following command line command: winhttpcertcfg.exe -l -c LOCAL_MACHINE\My -s "mycertificate"
2. Apply the hot fix for the .NET Framework v1.0. You can find the relavant information at http://support.microsoft.com/?id=817854.
3. Export the client certificate to a file without the private key. You will not reference the client store directly; instead you will reference this file.
4. Implement the data access component using the WebResponse, WebRequest and optionally the ServicePoint and SerbvicePointManager classes.
5. Implement the ICertifciatePolicy interface.
6. Build the assembly and optionally signed the assembly with a strong name.
7. With the assembly now built and deployed to the Asp.NET application (i.e., virtual directory); you are now ready to use the certificate to communicate with the backend.
Sample Code
While this sample doesn't show it, you should build the data access component such that it is reusable (preferable via a configuration file) against any HTTP/HTTPS endpoints, etc. This sample also uses the ServicePoint and ServicePointManager classes to increase the number of persisted connections you can have against a single domain endpoint. The ServicePointManager class manages the connection for you and via the FindServicePoint method will return a connection if one already exist. If one does not exist, it will create a new connection.
The following files are associated with the data access component. The sample is meant to get the point across and as such all unnecessary code have been removed. In an enterprise version, you would have additional features such as: logging, tracing, configuration file, etc. (please email me to get a full sample).
You can test the sample by creating a client or ASP.NET web service to make a call to the MakeRequest method. Just pass the method the POST data (strData), the target URL (strURI e.g., https://
Post.cs file
using System;
using System.Net;
using System.Text;
using System.IO;
using System.Web;
using System.Security.Cryptography.X509Certificates;
namespace MyNamespace
{
public class HTTPDataAccess
{
public HTTPDataAccess( )
{
// Set a maximum of 20 connections to the host
ServicePointManager.DefaultConnectionLimit = 20;
// 1 minutes max ideal time
ServicePointManager.MaxServicePointIdleTime = 100000;
ServicePointManager.CertificatePolicy = new CertPolicy();
}
private string MakeRequest(string strData, string strURI, string strMethod)
{
// retrieve an existing connection to the specified URL (i.e., strURI) or create a new one
ServicePoint sp = ServicePointManager.FindServicePoint( strURI, null );
// create an instance of the httpWebRequest object
HttpWebRequest req = ( HttpWebRequest ) WebRequest.Create( sp.Address );
// add a client certificate to the http request object
req.ClientCertificates.Add( X509Certificate.CreateFromCertFile( @"D:\MyCertificates\dotnet.cer" ) );
// set the request method to POST, txml/xml with a 1 minute timeout
req.Method = "POST";
req.ContentType = "text/xml";
req.Timeout = 10000;
req.KeepAlive = true;
// if we have data to post, set the request stream object
if( strData != null )
{
byte[] SomeBytes = null;
SomeBytes = Encoding.UTF8.GetBytes( strData );
req.ContentLength = SomeBytes.Length;
Stream newStream = req.GetRequestStream( );
newStream.Write( SomeBytes, 0, SomeBytes.Length );
newStream.Close( );
}
else
{
req.ContentLength = 0;
}
WebResponse result = req.GetResponse( );
Stream ReceiveStream = result.GetResponseStream( );
Encoding encode = System.Text.Encoding.GetEncoding( "utf-8" );
StreamReader sr = new StreamReader( ReceiveStream, encode );
string strResponse = sr.ReadToEnd( );
sr.Close( );
result.Close( );
ReceiveStream.Close( );
return strResponse;
}
}
}
certpolicy.cs file
Same as .NET Enterprise Service Sample