After quite a bit of research, it turns out this is quite easy using a couple of Java libraries and ColdFusion's "CreateObject" function. Here's how I got it to work:
First download the following libraries:
xmlsec-1.4.2.jar - http://www.aleksey.com/xmlsec/
An updated version is available, but this is the one I have working
wss4j-1.5.8.jar - http://ws.apache.org/wss4j/
This is the library that does most of the work - Rampart is the portion that Axis 2 uses for this, but we needed to just utilize the signing portions for our purposes.
The libraries need to go to [cf_home]/lib or be added to your classpath. A server restart may be necessary for ColdFusion to be able to see them.
Now the jars are available for use. I created two functions to get these to work - one to do the signing and one to send the request. I have a component in which these live, but you could do the whole thing inline if you wanted to.
The one downside to this is that you'll need to construct the SOAP requests manually. This isn't hard if you use something like SOAP UI - I just grab the XML from there and modify as necessary. I place the SOAP request inside a cfxml block.
<cffunction name="addWSAuthentication" access="public" output="false" hint="I sign SOAP envelope using WS Authentication">
<cfargument name="soapEnvelope" type="string" required="true">
<cfargument name="username" type="string" required="true">
<cfargument name="password" type="string" required="false">
<cfscript>
// Create Java Objects from xmlsec and wss4j
var WSConstants = CreateObject("Java","org.apache.ws.security.WSConstants");
var msg = CreateObject("Java","org.apache.ws.security.message.WSSAddUsernameToken");
// Get Soap Envlope document for Java processing
var soapEnv = arguments.soapEnvelope;
var env = soapEnv.getDocumentElement();
var e = "";
// Set Password type to TEXT (default is DIGEST)
msg.setPasswordType(WSConstants.PASSWORD_TEXT);
// Create WS-Security SOAP header using the build method from WSAddUsernameToken
e = msg.build(env.GetOwnerDocument(),arguments.username,arguments.password);
// Add the Nonce and Created elements
msg.addNonce(e);
msg.addCreated(e);
// Return the secure xml object
return soapEnv;
</cfscript>
</cffunction>
This function sends the soap request and returns the result. The endpoint and SOAP Action can be found in the WSDL or by using SOAP UI. In SOAP UI, look at the raw post, the SOAP Action is in the headers. Also note the additional headers in the cfhttpparams - these are necessary to make the WS Authentication work.
<cffunction name="sendSoapRequest" access="public" output="false" hint="I send the SOAP request off retrun the SOAP response">
<cfargument name="endpoint" type="string" required="true">
<cfargument name="soapEnvelope" type="any" required="true">
<cfargument name="soapAction" type="string" required="false" default="">
<cfset result="">
<cfset soapenv="">
<cfhttp url="#arguments.endpoint#" method="POST" result="result">
<cfhttpparam type="Header" name="Accept-Encoding" value="deflate;q=0">
<cfhttpparam type="Header" name="TE" value="deflate;q=0">
<cfhttpparam type="header" name="SOAPAction" value="">
<cfhttpparam type="xml" value="#toString(xmlParse(arguments.soapEnvelope))#">
<cfreturn>
</cfreturn>
</cffunction>
Hopefully this will save you some time if you ever have to do WS Security in ColdFusion. It took me a couple of weeks of trial and error to get this working, but I'm now using it for two different third party APIs without a hitch.
Acknowledgments
The following were critical in helping me get this solved:
Ben Nadel's Blog - Listing All Classes In A Jar File Using ColdFusion And CFZip
Steven Erat's Blog - Workaround for CFHTTP and Compressed HTTP Response from IIS