Monday, June 27, 2016

An invalid security token was provided (Bad TokenType "") : workaround for Axis2 client


I recently came across this issue when trying to invoke a secured web service using Apache axis2 client application (Calling a WSO2 ESB secured web service which was secured using policy scenario 3 ). The reason for this error is that the response soap message coming from the secured web service is missing some headers in the security header section which are needed to be there to become a Basic Security Profile 1.1 compliance response.

Axis2 uses Apache Rampart to handle WS-Sec* related tasks. Rampart modules uses Apache WSS4J which has implementations for primary security standards for Web Services. Checking whether response is BPS compliance is done from this. Basic Security Profile 1.1 is an WS-I (Web Services Interoperability) specification. WSS4J implementation is done in a way to disable this check if needed. (The default is to check for compliance. but APIs are provided to disable this.) . But unfortunately Rampart does not have any configurations to disable this property . So as a result a web service client written using Axis2/Rampart can only talk to a web service which provides BSP compliance requests/responses. Apache CXF has a property to disable this. see 'ws-security.is-bsp-compliant' property in  http://cxf.apache.org/docs/ws-securitypolicy.html but not in axis2.

Following is the workaround I used to overcome this issue.

Since we cannot change the property (unless you recompile the Rampart module) , what we can do is update the payload with the missing values. Here I'm not changing any values returned with the security header, just adding a missing constant to the payload so that the header becomes BSP compliance one.

Following are the steps

1. Find what is missing in the security header.

Get the source code for Apache WSS4J . I used Apache Rampart 1.7.0 . WSS4J version for that is 1.6.16. You can download it from https://archive.apache.org/dist/ws/wss4j/ . BSP compliance is checked from the methods in this class org.apache.ws.security.str.BSPEnforcer.java . (Axis2 client error log will give you the exact method )

Following is the error I got

Caused by: org.apache.ws.security.WSSecurityException: An invalid security token was provided (Bad TokenType "")
at org.apache.ws.security.str.BSPEnforcer.checkEncryptedKeyBSPCompliance(BSPEnforcer.java:113)
at org.apache.ws.security.str.SignatureSTRParser.parseSecurityTokenReference(SignatureSTRParser.java:209)

BSPEnforcer.checkEncryptedKeyBSPCompliance() method shows   'wsse11:TokenType' attribute is missing in the 'wsse:SecurityTokenReference' element.

following is the SecurityTokenReference element from the response security header

<wsse:SecurityTokenReference
xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd"
wsu:Id="STRId-5C264936DBA85C9F91146614733766082">
     <wsse:KeyIdentifier
EncodingType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary"
ValueType="http://docs.oasis-open.org/wss/oasis-wss-soap-message-security-1.1#EncryptedKeySHA1">
ekLfmUNY3Rne2Q1YQLhLQJhu7KA=
</wsse:KeyIdentifier>
</wsse:SecurityTokenReference>

According to the BSP 1.1 spec SecurityTokenReference element should have a TokenType attribute to become a BSP compliance response.  See http://www.ws-i.org/profiles/basicsecurityprofile-1.1.html#EncryptedKeyToken  . Since this is a constant, I do not see any harm adding it to the element. Following is what the element should be to become a BSP compliance

<wsse:SecurityTokenReference 
    xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd"
    wsu:Id="STRId-5C264936DBA85C9F91146614733766082"  
    xmlns:wsse11='http://docs.oasis-open.org/wss/oasis-wss-wssecurity-secext-1.1.xsd' 
    wsse11:TokenType='http://docs.oasis-open.org/wss/oasis-wss-soap-message-security-1.1#EncryptedKey'>
            <wsse:KeyIdentifier 
               EncodingType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary" 
       ValueType="http://docs.oasis-open.org/wss/oasis-wss-soap-message-security-1.1#EncryptedKeySHA1">
ekLfmUNY3Rne2Q1YQLhLQJhu7KA=
    </wsse:KeyIdentifier>
</wsse:SecurityTokenReference>

Now we found the missing token. Lets see how we can inject that attribute to the security header

2. Update the Header with missing attribute

First you should take a look at how axis2 engine works (Basic understanding would be enough). see http://wso2.com/library/777/

As you can see, Axis2 contains two main flows (In flow to handle incoming messges and Out flow to handle requests going out from the client) and each flow consists of Phases to do some specific task in that flow . If you open the axis2.xml file comes with the Axis2 libraries and go to the 'Phases' section you would see them. In each flow you would see <phase name="Security"/> . This phase handles security related things (such as validating the security headers, etc)

As I mentioned earlier, my error happens when the axis2 client receives the response from the service.  It means response comes to the client and  message is going through the 'InFlow' and at the <phase name="Security"/> the validation fails. So I have to modify the message before it goes through Security phase.  (If the error is happening while request is sending to the service, then you have to do the modification after 'Security' phase in the 'OutFlow'. This is for scenario where backend is throwing the error )

To modify the message, you can write an axis2 handler and add it to a new phase and deploy it before the Security phase.

Modified axis2.xml to use with axis2 client

<phaseOrder type="InFlow">
         :
 :
        <phase name="PreSecurity">
        <handler name="CustomHandler" class="apache.axis2.customhanlder.CustomSecurityHandler"/> 
</phase>
        <phase name="Security"/>
         :
         :
</phaseOrder>


following is the handler code. It goes through the SOAP header element and update the SecurityTokenReference element

package apache.axis2.customhanlder;

import javax.xml.namespace.QName;

import org.apache.axiom.om.OMAbstractFactory;
import org.apache.axiom.om.OMElement;
import org.apache.axiom.om.OMFactory;
import org.apache.axiom.om.OMNamespace;
import org.apache.axiom.soap.SOAPEnvelope;
import org.apache.axiom.soap.SOAPHeader;
import org.apache.axis2.AxisFault;
import org.apache.axis2.context.MessageContext;
import org.apache.axis2.handlers.AbstractHandler;

public class CustomSecurityHandler extends AbstractHandler {   

@Override
public InvocationResponse invoke(MessageContext ctx) throws AxisFault {
SOAPEnvelope msgEnvelope = ctx.getEnvelope();
        SOAPHeader msgHeader = msgEnvelope.getHeader();      
       
OMElement securityHeader = msgHeader.getFirstChildWithName(new QName(
"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd", "Security"));
OMElement signature = securityHeader
.getFirstChildWithName(new QName("http://www.w3.org/2000/09/xmldsig#", "Signature"));
OMElement keyInfo = signature.getFirstChildWithName(new QName("http://www.w3.org/2000/09/xmldsig#", "KeyInfo"));
OMElement securityTokenReference = keyInfo.getFirstChildWithName(
new QName("http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd",
"SecurityTokenReference"));

OMFactory fac = OMAbstractFactory.getOMFactory();
OMNamespace ns = fac.createOMNamespace("http://docs.oasis-open.org/wss/oasis-wss-wssecurity-secext-1.1.xsd",
"wsse11");

//adding the missing attribute
securityTokenReference.addAttribute("TokenType",
"http://docs.oasis-open.org/wss/oasis-wss-soap-message-security-1.1#EncryptedKey", ns);

        return InvocationResponse.CONTINUE;
}
}

Deploy the built jar with the modified axis2.xml and you would be able to invoke the BSP non compliant secured web service using a BSP compliant axis2 client.

No comments:

Post a Comment