Washing Client Certs in ColdFusion with SOAP – Part 3

Continuing from part 2, I’ve got my SOAP request working, but the solution is limited to the OS platform and I don’t like the security implications. What I would really like is a solution that works without the need of a custom tag that is OS dependent. If only CFHTTP wasn’t broken.

I decided to look at what I already had available; namely Java. ColdFusion runs on top of Java and it’s possible to create Java objects from within ColdFusion.

I’ll spare you the grueling details, but I did get caught up at one point playing around with the Java keystore and importing my client certificate into that keystore. Using things like keytool and OpenSSL I tore apart and reconstructed my client certificate and my Java keystore. Ultimately I didn’t need to do any of that and should have just left the keystore alone.

The path to enlightenment began when I found an example of Java socket operations using  the javax.net.ssl.SSLContext object. The example code I was looking at used this object to create regular sockets, not SSL-enabled sockets. I looked into this object and found a few more examples using it to create SSL connections. A bit of back and forth between these examples and the online Java API spec resulted in the discovery that I could load a PKCS12 file as a keystore and pass that to the SSLContext object then create sockets from that SSLContext. This looked promising.

After a bit of trial and error I finally had something that worked!

Here’s the relevant portion of code. I’ll go line-by-line afterwards.

<cfscript>
  // 01: create input file stream from certificate
  variables.ksf = CreateObject( "java", "java.io.FileInputStream" ).init( variables.cert_file );

  // 02: create keystore object from certificate
  variables.ks = CreateObject( "java", "java.security.KeyStore" ).getInstance( "PKCS12" );
  variables.ks.load( variables.ksf, JavaCast( "String", variables.cert_password ).toCharArray() );

  // 03: create key manager factory out of keystore
  variables.kmf = CreateObject( "java", "javax.net.ssl.KeyManagerFactory" ).getInstance( "SunX509" );
  variables.kmf.init( variables.ks, JavaCast( "String", variables.cert_password ).toCharArray() );

  // 04: create SSL context object using key manager factory
  variables.sslc = CreateObject( "java", "javax.net.ssl.SSLContext" ).getInstance( "TLS" );
  variables.sslc.init( kmf.getKeyManagers(), JavaCast( "null", "" ), JavaCast( "null", "" ) );

  // 05: get SSL socket from SSL context
  variables.factory = sslc.getSocketFactory();

  // 06: connect to the server via SSL
  variables.sock = variables.factory.createSocket( variables.server_address, variables.server_port);
  variables.sock.startHandshake();

  // 07: create input and output streams to read from and write to the socket
  variables.sout = variables.sock.getOutputStream();
  variables.out = createObject( "java", "java.io.PrintWriter" ).init( variables.sout );
  variables.sinput = variables.sock.getInputStream();
  variables.inputStreamReader = createObject( "java", "java.io.InputStreamReader" ).init( variables.sinput );
  variables.input = createObject( "java", "java.io.BufferedReader" ).init( variables.InputStreamReader );

  // 08: send the HTTP request over the socket
  variables.out.println( Trim( variables.request_header ) );
  variables.out.println();
  variables.out.println( Trim( variables.soap_envelope ) );
  variables.out.println();
  variables.out.flush();

  // 09: read the response from the server
  variables.result = "";
  do {
    variables.line = input.readLine();
    variables.lineCheck = IsDefined( "variables.line" );
    if ( variables.lineCheck ) {
      variables.result = variables.result & variables.line & Chr(13) & Chr(10);
    }
  } while ( variables.lineCheck );

  // 10: close the connection
  variables.sock.close();
</cfscript>

So let’s work through this.

01: The variable variables.cert_file contains the full path to the client certificate. This line opens that file for reading.

02: This creats a java.security.KeyStore object that expects a PKCS12 format and then loads the client certificate into the keystore. Note that the second parameter of the KeyStore.load() method is the password of the PKCS12 file. You must pass this as a character array and not as a string, which is why the password must be cast as a String object whose toCharArray() method is called to obtain that character array.

03: This creates a javax.net.ssl.KeyMangerFactory object which is used by the SSLContext object to deal with the certificates in the keystore. Note that the second parameter of the init() is the password to the certificate in character array format. You must provide the password twice, once when creating the keystore, and then again creating the key manager factory.

04: Here we create the javax.net.ssl.SSLContext object. The getInstance() call tells what protocol to use with this object, in this case “TLS“. (Note: “SSLv3” also works. No idea which one I should be using. If one fails, try the other.) Once the object is instantiated we initialize it with a key manager from the key manager factory. The second and third parameters of the init() method could point to a trust manager and a source of randomness. It doesn’t appear that either is needed (this is what I saw in other examples) so I pass null values. Note that you have to use JavaCast() to pass a null.

05: Get a socket factory object from the SSLContext object. This factory will be used to create individual sockets using the parameters established with the SSLContext object.

06: Create a single socket from the socket factory by passing the server address (variables.server_address) and port (variables.server_port) we want to connect to. Then call startHandshake() which will create the connection to the server using SSL.

07: Create objects needed to read from and write to the socket we’ve created.

08: Here we send our SOAP envelope to the server. Note that since we’re doing everything by hand we also have to send the proper HTTP headers. Both the variables.request_header and variables. soap_envelope variables were created using the CFSAVEDCONTENT tag as shown in part 1. You want to create the envelope first so you know the correct content-length value for the HTTP header. Since I decided to stick with SOAP 1.1 the header also contains the SOAPAction field and the content-type is set to text/xml.

Here is an example of how I created the headers:

<cfsavecontent variable="variables.request_header">
POST <cfoutput>#variables.server_request_path#</cfoutput> HTTP/1.0
Host: <cfoutput>#variables.server_address#</cfoutput>
User-Agent: SOAP Washer/1.0
SOAPAction: "<cfoutput>#variables.soap_action#</cfoutput>"
Content-Type: text/xml
Content-Length: <cfoutput>#Len( Trim( variables.soap_envelope ))#</cfoutput>
</cfsavecontent>

Standard HTTP/1.0 headers. You can make the User-Agent field just about whatever you want.

Also worth mentioning is that I didn’t try to combine the two (HTTP headers and envelope) into a single variable. This is because how CFSAVEDCONTENT handles newline characters is different between ColdFusion 8 and ColdFusion 9. By keeping them separate this code is able to operate under either version.

The empty println() commands are there to send a newline. And the flush() call simply sends anything left in the buffers, thus completing the sending of our request.

09: This is where we read the response from the server. The response can only be read one line at a time, so we need a loop to keep reading one line at a time and appending it to the variable where we’ll store the entire response (variables.result).

A tricky thing working between ColdFusion and Java objects is that if you assign the return value of a Java object method to a ColdFusion variable and that return value is null then the ColdFusion variable is unset or deleted. Since the readLine() method returns null when there’s nothing more to read, the only way to know we’ve completed reading the response is to test if variables.line, where we store the return value of readLine(), still exists. If it doesn’t, we’ve reached the end of the response.

10: Close the connection

The ColdFusion variable variables.result will contain the entire response from the server, including the response HTTP headers. Meaning if you want to get a proper XML object from xmlParse() you’re going to need to first strip away those headers.

<cfset variables.temp = REFindNoCase( "content-length: ([0-9]+)", variables.result, 1, "True" )>
 <cfif variables.temp.len[1] GT 0>
  <cfset variables.length = Val( Mid( variables.result, variables.temp.pos[2], variables.temp.len[2] ))>
  <cfset variables.xmlResult = Trim( Right( RTrim( variables.result ), variables.length ))>
</cfif>

This should do the trick. Although you may want to throw in some code to strip out the status code to ensure a good transaction.

And there you have it. Handling transactions with a third-party web site using client certificates under ColdFusion using only native ColdFusion objects.

Advertisements

Washing Client Certs in ColdFusion with SOAP – Part 2

In part 1 I introduced you to basic SOAP consumption in ColdFusion. Let’s see where things go from there.

The task at hand was to integrate our system with a third-party site. I authenticate users locally, request a token from the third-party site that allows the user to access said site, and then redirect the user to that site with the token passed on the URL.

But how does the third-party site know to trust my requests and not the requests from other people? After all you don’t want people forging requests to access the third-party site as some other user. The solution for this third-party site is client certificates. A client certificate is either issued by the third-party site or you provide your public certificate to the third-party site and the third-party’s site is configured to trust the certificate. SOAP requests are then made over SSL using this certificate to confirm your identity and to encrypt the channel.

Starting with ColdFusion 8 CFHTTP accepts two parameters to afford the use of client certs, clientCert and clientCertPassword. The clientCert parameter points to a PKCS12 formatted file containing your public and private keys and, possibly, the certificate chain from the root certificate authority (such as VeriSign or Thawte) down to whoever issued your client certificate. The clientCertPassword parameter contains the password with which the PKCS12 file is encrypted.

A note to ColdFusion developers:
The PKCS12 must be encrypted with a password! For whatever reason (I believe a limitation in the underlying java.security.KeyStore object) your cert must have a password. This is never explicitly stated in the ColdFusion 8 documentation.

I obtained my client certificate and set out to start writing code to talk with this third-party.

The first problem I encountered was I would not be able to use the CreateObject() method covered in part 1. The reason being that there was no way to provide my client certificate to the object. So it’s back to the CFHTTP method.

The second problem I encountered had nothing to do with ColdFusion and everything to do with the documentation provided by the third-party. Turns out the header for the SOAP request changed considerably during development. I had been given some early development documents that did not reflect the current header structure. Once I realized the problem and obtained the current documentation I was able to correctly construct my SOAP request’s envelope header and…

I got another error.

But this time it wasn’t a normal SOAP request error. The CFHTTP object’s filecontent value contained nothing more than “Connection failure”. But the HTTP status code was 200, which indicates a successful request. Previous SOAP request errors would return a 403 status code. This was odd.

In searching the Adobe forums and the internet in general I found sparse comments about possible problems with CFHTTP handling SSLv3 sessions, although there wasn’t any sort of official comment or response to the few reports of this problem. I loaded up the developer edition of ColdFusion 9 on my own computer to see if perhaps this problem had been resolved with the latest copy of ColdFusion. It had not.

To confirm this as a problem with ColdFusion and not my client cert, or my SOAP request, I installed Apache and PHP locally and ran the equivalent PHP code. The PHP code worked perfectly. I started trying to do as much comparison between the two platforms as I could. I event went so far as to run a packet capture on the PHP and CF requests (pointing them at a dummy, local page that wasn’t encrypted so I could see the requests) and compared them to make sure everything was the same, which they were.

Eventually I found a post online that mentioned CFHTTP wasn’t up to the job, but a third-party custom tag written in C++ did work just fine. That custom tag is CFX_HTTP5. I downloaded a demo copy and installed it locally. How CFX_HTTP5 handles client certs is different from CFHTTP. Rather than simply pointing the tag at the client cert, I had to install the client cert into the Windows certificate store and then point the tag at the store. There is a bit of work involved with it, although nothing too difficult and it’s all covered in the CFX_HTTP5 documentation.

Once I had the tag and the certificate imported into the local Windows certificate store, I rewrote the CFHTTP call using CFX_HTTP5 and it worked! The SOAP envelope was the same, the headers were the same, the only difference between the two was the logic underlying the tags.

Something is broken with CFHTTP and it can’t be used to do some operations using client certificates. But at least there is an alternative.

However I didn’t like the alternative.

First, it’s a Windows-only solution. We’re out of luck if we’re running ColdFusion on a Linux machine.

Secondly, the client cert must be stored in a place that the ColdFusion process has permissions to access, and it is accessed without needing to know the client cert password. The result is that any person with permission to create CFM scripts that are executed under this process could authenticate against this third-party web site. In a shared hosting environment this can create a serious security issue. The only solution is to separate the process, but that probably means a separate server and a new OS license and hardware costs. If that option isn’t available you have some problems. You might register the CFX with a name that contains random characters and hope none of the users in the shared environment know how to enumerate registered custom tags. I’m not sure if that is possible, but I’m willing to bet it is.

Long story short, I don’t like the CFX_HTTP5 solution.

So, in Part 3, I go back to the drawing board.

Washing Client Certs in ColdFusion with SOAP – Part 1

Recently I was asked to look into integrating our systems with an external application via the third-party’s single sign-on system. The way it works is simple enough. We would have an application that authenticates the user through our system and then sends a request to the third-party asking for a token to sign the user into their system. The third-party would return the token that I then give to the end-user and redirect them to the third-party web site. This token is how the third-party would authenticate the user into their system. (The token is nothing more than a long string of characters that is passed on the URL of the redirect.)

The method of obtaining the token is also fairly simple. The application submits a SOAP request over an SSL session to the third-party’s authentication server and that server would respond with the token. SOAP, for all the technical specs and other crap, is very straightforward. It’s a simple XML document consisting of a root element called ENVELOPE which contains two children called HEADER and BODY. The header isn’t always required and the body typically contains elements with the names of various fields the SOAP function your calling requires with each element containing the value of that parameter. Very straightforward.

A SOAP request in ColdFusion couldn’t be simpler, especially with the CFSAVECONTENT tag. Simply construct your envelope inside a CFSAVECONTENT tag and then use CFHTTP to submit the request. It looks a little something like this:

<cfsavecontent variable="variables.soap">
<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
  <soap:Body>
    <GetCurrentTime xmlns="http://ws.historicaloptiondata.com/" />
  </soap:Body>
</soap:Envelope>
</cfsavecontent>

<cfhttp
  url="http://ws.historicaloptiondata.com/Service.asmx"
  method="POST"
>
  <cfhttpparam type="header" name="SOAPAction" value="""http://ws.historicaloptiondata.com/GetCurrentTime""" />
  <cfhttpparam type="header" name="Content-Length" value="#Len( Trim( variables.soap ))#" />
  <cfhttpparam type="xml" value="#Trim( variables.soap )#" />
</cfhttp>

<cfdump var="#xmlParse( cfhttp.filecontent )#" />

You should be able to plug this code into a CFM file and run it without having to touch a thing and you should see the CFDUMP of an XML object. (I say “should” because xmlParse() seems to first try and open a file with the name of the content of the passed variable and, when that fails, treat the passed value as an XML document itself. This can trigger errors and make it unusable if you employ any sort of file operation restrictions on your server. In which case modify the code to remove the xmlParse() call and just dump the cfhttp.filecontent.)

A few notes about this code SOAP in general.

  • This is an example of a SOAP 1.1 request. There is another, slightly different format known as SOAP 1.2. The major differences between the two are that the content-type for 1.1 is text/xml, but for 1.2 it is application/soap+xml. Also the SOAPAction HTTP header is no longer needed in 1.2.
  • The SOAPAction HTTP header’s value must be wrapped in double-quotation marks. And sometimes the first character after the open quotes will need to be a pound (#) symbol. This means ColdFusion programmers will need to be certain they escape these special characters in their values.
  • If the CFHTTPPARAM type “xml” is present, CFHTTP automatically sets the content-type to text/xml. I am not sure if it’s possible to override this, but I believe not, therefore you’re almost always going to have to stick with SOAP 1.1 if you’re using CFHTTP for your SOAP requests.
  • You must Trim() the variables.soap variable! The newline at the beginning of the value, which exists because there is a newline immediately after after CFSAVEDCONTENT tag (for visual formatting purposes) will make the XML document you’re sending an invalid XML document and result in errors.

Now comes the WSDL file. A WSDL file is an XML documents that describes the functions and parameters of said functions available through a SOAP service. The CreateObject() function has a “webservice” object type which will consume a WSDL file and create an object with all the available functions offered by the SOAP service. All the stuff with XML and ENVELOPES and CFHTTP becomes transparent and, as it turns out, SOAP can be simpler than my previous example. The above code can be reduced to the following using CreateObject():

<cfset ws = CreateObject( "webservice", "http://ws.historicaloptiondata.com/Service.asmx?WSDL" ) />
<cfset ws.getCurrentTime() />
<cfdump var="#GetSOAPResponse( ws )#" />

This makes this life much simpler for ColdFusion programmers. No need to worry about what version of SOAP you’re using or what URLs you need to submit your request to, the formatting of your SOAP envelope, the SOAPAction HTTP header variable, etc. It’s all taken care of for you by ColdFusion.

So this integration I was asked to do should be a piece of cake, right?

Wrong.

Simple SOAP is simple. Complex SOAP… well, you’ll see in Part 2.