Search code examples
javaweb-serviceswcfcxfws-security

Modify XML of CXF RequestSecurityToken before it gets encrypted


I'm calling a WCF service from a Java client using Apache CXF. The service is secured using an STS at another address. I've configured the service client to call out for the security token before invoking the main service and it works (it's trying to call the STS), but the STS is expecting some extra data to be provided in the RequestSecurityToken element. The STS's policy specifies that the RequestSecurityToken be encrypted and signed before being sent up and that's what's causing me the issues. The encryption and signing is working, but I can't seem to modify the SOAP message before it gets encrypted.

I looked at this question: How To Modify The Raw XML message of an Outbound CXF Request? and while it helped a lot, the part of the XML I need to alter resides inside a part of the SOAP message that gets encrypted and signed.

I made an Interceptor and tried it on all the different phases I could find, but none of them seem to get invoked between the RequestSecurityToken being created and the encryption and signing taking place.

Is there one? Or already a facility to add extra elements to the RequestSecurityToken?

Edit for clarity:

Here's what my RST looks like now:

<wst:RequestSecurityToken xmlns:wst="http://docs.oasis-open.org/ws-sx/ws-trust/200512">
    <wst:RequestType>http://docs.oasis-open.org/ws-sx/ws-trust/200512/Issue</wst:RequestType>
    <wsp:AppliesTo xmlns:wsp="http://www.w3.org/ns/ws-policy">
        <wsa:EndpointReference xmlns:wsa="http://www.w3.org/2005/08/addressing">
            <wsa:Address>http://localhost:9085/MyService</wsa:Address>
        </wsa:EndpointReference>
    </wsp:AppliesTo>
    <wst:TokenType>http://docs.oasis-open.org/wss/oasis-wss-saml-token-profile-1.1#SAMLV1.1</wst:TokenType>
    <wst:KeyType>http://docs.oasis-open.org/ws-sx/ws-trust/200512/SymmetricKey</wst:KeyType>
    <wst:KeySize>192</wst:KeySize>
    <wst:Entropy>
        <wst:BinarySecret Type="http://docs.oasis-open.org/ws-sx/ws-trust/200512/Nonce">OlbfbuCUf3N2lNf9mhD03gfeMk0TfPI2nLWx8edlL5w=</wst:BinarySecret>
    </wst:Entropy>
    <wst:ComputedKeyAlgorithm>http://docs.oasis-open.org/ws-sx/ws-trust/200512/CK/PSHA1</wst:ComputedKeyAlgorithm>
    <wst:Renewing/>
</wst:RequestSecurityToken>

Here's what the service provider's documentation says it needs to look like roughly (notice the Credentials element near the end):

<t:RequestSecurityToken xmlns:t="http://schemas.xmlsoap.org/ws/2005/02/trust">
    <t:RequestType>http://schemas.xmlsoap.org/ws/2005/02/trust/Issue</t:RequestType>
    <wsp:AppliesTo xmlns:wsp="http://schemas.xmlsoap.org/ws/2004/09/policy">
        <EndpointReference xmlns="http://www.w3.org/2005/08/addressing">
            <Address>http://localhost:9085/MyService</Address>
        </EndpointReference>
    </wsp:AppliesTo>
    <t:Entropy>
        <t:BinarySecret u:Id="uuid-e2d08122-45ab-45cd-80d1-46de2306836b-1" Type="http://schemas.xmlsoap.org/ws/2005/02/trust/Nonce" xmlns:u="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">Ssex4V/175NCIOK1j4Mmbl47GiThOQMd</t:BinarySecret>
    </t:Entropy>
    <t:KeySize>192</t:KeySize>
    <t:TokenType>http://docs.oasis-open.org/wss/oasis-wss-saml-token-profile-1.1#SAMLV1.1</t:TokenType>
    <t:KeyType>http://schemas.xmlsoap.org/ws/2005/02/trust/SymmetricKey</t:KeyType>
    <Credentials>
        <UserName type="string">username</UserName>
        <Password type="string">password</Password>
    </Credentials>
    <t:ComputedKeyAlgorithm>http://schemas.xmlsoap.org/ws/2005/02/trust/CK/PSHA1</t:ComputedKeyAlgorithm>
</t:RequestSecurityToken>

And this is more or less my code - where would I alter the RST?:

  CXFBusFactory bf = new CXFBusFactory();
  Bus bus = bf.createBus();

  STSClient stsClient = new STSClient(bus);
  Map<String, Object> stsProperties = new HashMap<>();

  stsProperties.put(SecurityConstants.ENCRYPT_CRYPTO, stsMerlin);
  stsProperties.put(SecurityConstants.SIGNATURE_CRYPTO, stsMerlin);
  stsProperties.put(SecurityConstants.IS_BSP_COMPLIANT, "false");
  stsClient.setProperties(stsProperties);

  stsClient.setWsdlLocation("http://localhost:8090/SecurityTokenService?wsdl");
  stsClient.setServiceName("{http://tempuri.org/}Service");
  stsClient.setEndpointName("{http://tempuri.org/}Service_Port");

  stsClient.setKeySize(192);

  stsClient.getInInterceptors().add(new LoggingInInterceptor());
  stsClient.getOutInterceptors().add(new LoggingOutInterceptor());

  stsClient.setTokenType("http://docs.oasis-open.org/wss/oasis-wss-saml-token-profile-1.1#SAMLV1.1");
  stsClient.setSoap12();

  // Set the STS Client on the bus
  bus.setProperty(SecurityConstants.STS_CLIENT, stsClient);

  BusFactory.setDefaultBus(bus);
  BusFactory.setThreadDefaultBus(bus);

  MyService myService = new MyService();
  IMyService myServicePort = myService.getCustomBindingIMyService();

  Map<String, Object> ctx = ((BindingProvider)myServicePort).getRequestContext();
  ctx.put(SecurityConstants.ENCRYPT_CRYPTO, merlin);
  ctx.put(SecurityConstants.SIGNATURE_CRYPTO, merlin);
  ctx.put(SecurityConstants.IS_BSP_COMPLIANT, "false");

  myServicePort.doSomething();

Any insights appreciated.


Solution

  • So I ended up getting some help from the cxf users mailing list.

    The response was as follows:

    What I would suggest you do here is to subclass the STSClient in CXF:

    https://git-wip-us.apache.org/repos/asf?p=cxf.git;a=blob;f=rt/ws/security/src/main/java/org/apache/cxf/ws/security/trust/STSClient.java;h=afdaaeaa092460c5cbccd3f9723660ded9f12e2b;hb=HEAD

    In particular, you want to override the "issue" method here:

    https://git-wip-us.apache.org/repos/asf?p=cxf.git;a=blob;f=rt/ws/security/src/main/java/org/apache/cxf/ws/security/trust/AbstractSTSClient.java;h=4b4630e9d3fe0afab4496de0c7b0dd5df2fca292;hb=HEAD

    Just copy the existing method code + add in your own custom code at the end.

    Sure enough, I subclassed the STSClient and was able to copy-paste-override the issue method which gives you access to the W3CDOMStreamWriter that comprises the RequestSecurityToken so I was able to make any modifications I want by just doing more writer.writeStartElement(...) etc. before the final writer.writeEndElement().