How to call a Web Service from CF7 using Client Certificate based ("Bilateral") authentication (Win)

If you have to call a web service from ColdFusion 7 - and the destination web service enforces bilateral SSL authentication (i.e. the caller has to supply a client certificate to prove who they are, as well as the destination supplying a server certificate to prove who it is...) - then the options, in descending order of preference are:

  1. Check / ask really nicely / beg / etc. to see if there is a way of calling the web service without passing a client SSL certificate
  2. Use CF8 (or later) - I believe that you will still need to do the SOAP generation by hand, as cfobject / cfinvoke do not appear to allow a client certificate to be set - but you can use cfhttp and specify the clientcert and clientcertpassword attributes - which avoids having to use the WinHttp.WinHttpRequest COM object as shown below.
  3. Use the WinHttp.WinHttpRequest COM object as shown below...

ColdFusion (when running on windows) allows (and has allowed for several versions) COM objects to be called from within CF code, using the CFOBJECT tag or CreateObject() function.

Built into the following versions of windows, is the WinHTTP 5.1 COM object:

WinHTTP 5.1 is available only with Windows Server 2003, Windows XP with Service Pack 1 (SP1), and Windows 2000 Professional with Service Pack 3 (SP3). (I suspect that it may also be available in Windows 2008)

This provides similar functionality to CFHTTP - but at a lower level.  In particular it allows a client certificate to be specified, using the SetClientCetificate function.

This is not as simple as it seems - where as in CF8 using CFHTTP you just have to specify the physical location of the client certificate file - the WinHttp object requires that the client certificate is first imported into a certificate store - and then the reference to the certificate provided to the SetClientCetificate function:

ClientCertificate [in]

Specifies the location, certificate store, and subject of a client certificate

To cut a very long (well around 5 hours) story short, this is how I finally got it to work:

  • Open the Certificates Snap-in in MMC:
    • Start > Run MMC
    • File > Add/Remove Snap-in
    • Click Add...
    • Select Certificates and click Add
    • Select Computer account and click Finish
    • Select Local computer and click Finish
    • Click Close
    • Click OK
  • Expand Certificates (Local Computer) in the tree
  • Select Personal
  • Right Click on the folder, and select All Tasks > Import
  • Browse to and Select the supplied PFX file
  • Type in the password used to encrypt the key
  • Do not select the Enable strong private key protection option

Having imported the certificate, you now need to reference it within your CF Code.

First, create a reference to the WinHTTP object:

<cfobject class="WinHttp.WinHttpRequest.5.1" name="objWinHttp" action="Create" type="COM">

Next open a connection to the web service. - specifying POST verb, URL and not asynchronous.

<cfset objWinHttp.open("POST", "https://[webserviceurl.extension]", False)>

Now specify to use the client certificate to use:

<cfset objWinHttp.SetClientCertificate("LOCAL_MACHINE\My\[CN from certificate subject]")>

This was the bit I found hardest to work out...

  • The LOCAL_MACHINE bit is logical enough, and documented in several places
  • The next bit to me is not obvious at all... the Personal certificate store is referenced as My
  • The next bit is not completely obvious either - most documentation I could find specified to use the subject of the certificate - but more specifically this is the common name - CN - within the subject

Then you have to set a couple of HTTP headers:

First to specify that the content of the request (as its to a SOAP based web service) is XML:

<cfset objWinHttp.SetRequestHeader("Content-Type", "text/xml")>

Then to specify the web service action to be called.  This is whatever is listed in the WSDL as the action for the method you are calling.

<cfset objWinHttp.SetRequestHeader("SOAPAction", "[SOAPAction from the WSDL for the method you want to call]")>

And finally, to submit the SOAP request content to the web service:

<cfset objWinHttp.send("#variables.soap#")>

Note that we are passing in there a valid SOAP format XML packet, according to what is expected by the specific web service being called.

If you are struggling to parse the WSDL from the remote service in your head (what kind of geek are you!) then I can highly recommend that you download Storm - a free and open source tool for testing web services. This allows you to point at the destination web service, select the method, and specify the data you want to submit for each parameter. You can then view the Raw XML for the SOAP request - and use that as a template for the web service request you are submitting.

Having successfully submitted the request, you may want to process the response

<cfscript>
myxmldoc = XmlParse(#objWinHttp.ResponseText#);
myXMLinnerDoc = XmlParse(myxmldoc["soap:Envelope"]["soap:Body"].xmlchildren[1].xmlchildren[1].xmltext);
</cfscript>

The response is returned as a string within objWinHttp.ResponseText - which as it is a SOAP response, will be XML. The specific code for processing the response is obviously dependant on the web service - but the code above should give access the actual response within the SOAP wrapper.


Hopefully this will save someone some time - it took about 9 hours in total to work out what is and is not possible within CF7 - and having found a solution which should work, to actually work out the specific combination of steps necessary

Please note, all the code above is very much AS IS - error handling is always good - especially when there's a dependency on another service! etc. etc.

I'd be really interested to hear from anyone else who has had a similar requirement - and how they approached it. I'd especially like to see any better ways of achieving the same!

Comments (Comment Moderation is enabled. Your comment will not appear until approved.)
David Boyer's Gravatar Hi,

Great article. I encountered the same problem but with CF8 so I took the hand made soap request plus cfhttp. Good to know there's an alternative method though if my CF7 servers ever need to do this.

Here's my old blog post of my CF8 approach.
http://misterdai.wordpress.com/2009/06/24/sslv3-we...
# Posted By David Boyer | 17/02/10 08:49
Daniel Lancelot's Gravatar Hi David,

Thanks for your link- good to have confirmation that CF8 does work as expected :)

I completely agree with you that CF should support client certs in all tags that create http connections - I'd have thought that the most common use case would be when calling a web service...

One thing to note, is that this method will only work with windows - I'd be interested to see if anyone has managed to achieve this in a cross platform compatible manner - using Java for instance...
# Posted By Daniel Lancelot | 18/02/10 21:17
David Boyer's Gravatar A Java solution would be nice. I've noticed that Railo doesn't support my CF8 way of doing things so I've logged it with their uservoice site. But if we had a Java method it'd probably work everywhere ;)

http://railo.uservoice.com/forums/21016-general/su...
# Posted By David Boyer | 19/02/10 08:04
BlogCFC was created by Raymond Camden. This blog is hosted by DotLance Ltd.