Monday, October 06, 2008

C# Web Request Using Client Certificate

Recently worked on this project where a service was accessible over http via client certificate. This is no biggie however took me 4-5 hrs to figure it out as the issue was not with code associated with importing client certificate but with another portion of the code that I overlooked. You know how it goes, stupid mistakes here and there and boom there goes your day. Thinking that the error was with the client certificate code I searched through the anals of Google pages to figure it out and various attempts/learnings made a worth to blog out. BTW kudos to the Fiddler ( tool which allowed me to sniff the http outgoing traffic helping me debug the issue.

So the http service I was trying to access is a secure one and my org was issued a client certificate. There are three ways that I tried to attach a client certificate with the request.

1) Providing direct links to cert files: You can do this via this sample code. Pretty self explanatory. The additional cert you see below is the intermediate certificate agency cert that I had to additionally download.

X509Certificate2Collection certCollect = new X509Certificate2Collection();

X509Certificate2 cert = new X509Certificate2(@"C:\Users\...\x509_verisign-certificate.cer");

X509Certificate2 cert1 = new X509Certificate2(@"C:\Users
\..\Certificate_From_SErvice.cer.pfx", "{client certificate password}");



2) Exported combined cert file (.cer.p7b format): If you have imported the client/intermediate certificate onto your computer then you can export combined certificate (with all intermediariy certificate) + private key into a .cer.p7b file format. You can use this one file instead of the two used above. Here the code to make it work:

X509Certificate2Collection certCollect = new X509Certificate2Collection();

FileInfo file = new FileInfo(@"C:\Users\...\exported_certificate.cer.p7b");

BinaryReader br = new BinaryReader(File.OpenRead(file.FullName));

byte[] raw = br.ReadBytes((int)file.Length);

SignedCms cms = new SignedCms();



3) Referencing a imported certificate on the workstation: You can reference a imported certificate using the below code. There a various ways to uniquely identify a certificate using 'subject name' or 'serial id' etc. I have accessed it via the subject name.

X509Store store = new X509Store("MY", StoreLocation.CurrentUser);

store.Open(OpenFlags.ReadOnly | OpenFlags.OpenExistingOnly);

X509Certificate2Collection collection = (X509Certificate2Collection)store.Certificates;

X509Certificate2Collection fcollection =
(X509Certificate2Collection)collection.Find(X509FindType.FindBySubjectName, "{certificate name}", false);

X509Certificate2Collection icollection =
(X509Certificate2Collection)collection.Find(X509FindType.FindBySubjectName, "VeriSign
Class 1 Individual Subscriber CA - G2", false);

X509Certificate2Enumerator certEnum = fcollection.GetEnumerator();


X509Certificate2 primCertFromStore = certEnum.Current;

certEnum = icollection.GetEnumerator();


X509Certificate2 issuerCertFromStore = certEnum.Current;



// use the below code to test if the retrieved certificate is the one you intend to.

You can add the certificate to the request using the below code sample:

// Handle any certificate errors on the certificate from the server.
string uri = "request uri";

HttpWebRequest request = (HttpWebRequest)WebRequest.Create(uri);

request.ProtocolVersion = HttpVersion.Version11;

request.KeepAlive = true;

request.Method = "POST";

request.Accept = "*/*";


string str = "POST DATA";

byte[] postBytes = Encoding.UTF8.GetBytes(str);

request.ContentType = "TEXT/XML";

request.UserAgent = "Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; .NET CLR
2.0.50727; .NET CLR 1.1.4322; InfoPath.1)";

request.ContentLength = postBytes.Length;

Stream requestStream = request.GetRequestStream();

requestStream.Write(postBytes, 0, postBytes.Length);


HttpWebResponse response = (HttpWebResponse)request.GetResponse();