Accessing proxy services in WSO2 ESB with a security token issued by the Identity Server 2.0

This post explains how we can access a proxy service deployed in WSO2 ESB with a security token issued by the Identity Server 2.0.

This is the use case;

1. User authenticates to the Identity Server 2.0
2. User gets a Security Token from the Identity Server 2.0 - which is signed by the Identity Server's private key and encrypted by the ESB's public key.
3. User provides the security token to the ESB to access the proxy service.

Lets create the proxy service first - you simply need to copy and paste following Synapse configuration [in WSO2 ESB] - it's a simple proxy service having a Send mediator in In/Out sequences.
<?xml version="1.0" encoding="UTF-8"?>
<syn:definitions xmlns:syn="http://ws.apache.org/ns/synapse">
<syn:registry provider="org.wso2.carbon.mediation.registry.WSO2Registry">
<syn:parameter name="cachableDuration">15000</syn:parameter>
</syn:registry>
<syn:proxy name="test" transports="https http" startOnLoad="true" trace="disable">
<syn:target>
<syn:inSequence>
<syn:send/>
</syn:inSequence>
<syn:outSequence>
<syn:send/>
</syn:outSequence>
</syn:target>
</syn:proxy>
<syn:sequence name="main">
<syn:in>
<syn:log level="full"/>
<syn:filter source="get-property('To')" regex="http://localhost:9000.*">
<syn:send/>
</syn:filter>
</syn:in>
<syn:out>
<syn:send/>
</syn:out>
</syn:sequence>
<syn:sequence name="fault">
<syn:log/>
</syn:sequence>
</syn:definitions>
Now, we need to apply security to the proxy service.

Lets select 'test' [the proxy service which we just created] from the service listing page and select 'Security' from the Service Dashboard.

Now, apply "Sign and encrypt - X509 Authentication" security policy and during the wizard select wso2carbon.jks as the trust store.

Once applied the security policy - we need to modify this to consume a security token issued by the Identity Server 2.0 STS.

From the Service Dashboard of the 'test' proxy service, select 'Policies'.

Let's get the policy to override, from service.policy.xml available here.

Click on "Edit Policy" at "Service Hierarchy\Service test" and clear the content there to copy & paste the content from service.policy.xml and Save.

Now we are done - our proxy service is secured - Sign & Encrypt and accepts a security token issued by the Identity Server STS.

Since, the token issued by the Identity Server 2.0 is signed by it's private key - ESB should have it's public key to verify. In default setup - ESB's trust key store includes the public certificate of the Identity Server - so, nothing to be done here with the default setup.

Now - we need to setup Identity Server STS to issue tokens.

As the first step - you need to upload the public certificate of the ESB to the Identity Server's trust key store - since IS uses it to encrypt the security token issued.

You can get the public certificate [wso2carbon.cert.cer] of the ESB from here.

Login to IS as an admin, and import the above certificate to wso2carbon.jks from Configure/Key Stores. [Yes - WSO2 ESB and IS use two different key stores - that's why we need to perform this step here]

The first part of this explains all the steps you need to setup IS STS.

Make sure, in the first step - while adding the trusted 'Endpoint Address' - select 'wso2carbon.cert' as the 'Certificate Alias'.

That's it - now let's write the client code.

You may also need to download Java Cryptography Extension (JCE) Unlimited Strength Jurisdiction Policy Files 5.0 from here and copy the two jar files from the extracted jce directory (local_policy.jar and US_export_policy.jar) to $JAVA_HOME/jre/lib/security. For JDK 6 it's here.

Also while running the client code, make sure bouncycastle jar is in the classpath.

You can download both sts.policy.xml and service.policy.xml files from here.
package org.apache.ws.axis2;

import java.util.Properties;

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.om.impl.builder.StAXOMBuilder;
import org.apache.axis2.addressing.EndpointReference;
import org.apache.axis2.client.Options;
import org.apache.axis2.client.ServiceClient;
import org.apache.axis2.context.ConfigurationContext;
import org.apache.axis2.context.ConfigurationContextFactory;
import org.apache.neethi.Policy;
import org.apache.neethi.PolicyEngine;
import org.apache.rahas.RahasConstants;
import org.apache.rahas.Token;
import org.apache.rahas.TokenStorage;
import org.apache.rahas.TrustUtil;
import org.apache.rahas.client.STSClient;
import org.apache.rampart.RampartMessageData;
import org.apache.rampart.policy.model.RampartConfig;
import org.apache.rampart.policy.model.CryptoConfig;
import org.apache.ws.secpolicy.Constants;
import org.opensaml.XML;

public class IdentitySTSClient {

/**
* @param args
*/

final static String RELYING_PARTY_SERVICE_EPR = "http://192.168.1.2:8280/services/echo";
final static String ESB_TRANS_EPR = "http://192.168.1.2:8280/services/test";
final static String STS_EPR = "https://localhost:9443/services/wso2carbon-sts";

/**
* @param args
* @throws Exception
*/
public static void main(String[] args) throws Exception {
ConfigurationContext confContext = null;
Policy stsPolicy = null;
STSClient stsClient = null;
Policy servicePolicy = null;
Token responseToken = null;
String trustStore = null;

// You need to import the Identity Server, public certificate to this key store.
// By default it's there - if you use wso2carbon.jks from [ESB_HOME]\resources\security
trustStore = "wso2carbon.jks";
// We are accessing STS over HTTPS - so need to set trustStore parameters.
System.setProperty("javax.net.ssl.trustStore", trustStore);
System.setProperty("javax.net.ssl.trustStorePassword", "wso2carbon");

// Create configuration context - you will have Rampart module engaged in the
// client.axis2.xml
confContext = ConfigurationContextFactory.createConfigurationContextFromFileSystem("repo",
"repo/conf/client.axis2.xml");

stsClient = new STSClient(confContext);

stsClient.setRstTemplate(getRSTTemplate());
stsClient.setAction(RahasConstants.WST_NS_05_02 + RahasConstants.RST_ACTION_SCT);

// This is the security policy we applied to Identity Server STS.
// You can see it by https://[IDENTITY_SERVER]/services/wso2carbon-sts?wsdl
stsPolicy = loadSTSPolicy("sts.policy.xml");

// This is the security of the relying party web service.
// This policy will accept a security token issued from Identity Server STS
servicePolicy = loadServicePolicy("service.policy.xml");

responseToken = stsClient.requestSecurityToken(servicePolicy, STS_EPR, stsPolicy,
RELYING_PARTY_SERVICE_EPR);

System.out.println(responseToken.getToken());

TokenStorage store = TrustUtil.getTokenStore(confContext);
store.add(responseToken);

ServiceClient client = new ServiceClient(confContext, null);
Options options = new Options();
options.setAction("urn:echoString");
options.setTo(new EndpointReference(RELYING_PARTY_SERVICE_EPR));
options.setProperty(org.apache.axis2.Constants.Configuration.TRANSPORT_URL, ESB_TRANS_EPR);
options.setProperty(RampartMessageData.KEY_RAMPART_POLICY,servicePolicy);
options.setProperty(RampartMessageData.KEY_CUSTOM_ISSUED_TOKEN, responseToken.getId());
client.setOptions(options);

client.engageModule("addressing");
client.engageModule("rampart");

OMElement response = client.sendReceive(getPayload("Hello world1"));
System.out.println("Response  : " + response);
}

private static Policy loadSTSPolicy(String xmlPath) throws Exception {
StAXOMBuilder builder = null;
Policy policy = null;
RampartConfig rc = null;
builder = new StAXOMBuilder(xmlPath);
policy = PolicyEngine.getPolicy(builder.getDocumentElement());
rc = new RampartConfig();
rc.setUser("admin");
// You need to have password call-back class to provide the user password
rc.setPwCbClass(PWCBHandler.class.getName());
policy.addAssertion(rc);
return policy;
}

private static Policy loadServicePolicy(String xmlPath) throws Exception {
StAXOMBuilder builder = null;
Policy policy = null;
RampartConfig rc = null;
CryptoConfig sigCryptoConfig = null;
String keystore = null;
Properties merlinProp = null;
CryptoConfig encrCryptoConfig = null;

builder = new StAXOMBuilder(xmlPath);
policy = PolicyEngine.getPolicy(builder.getDocumentElement());
rc = new RampartConfig();
rc.setUser("wso2carbon");
rc.setEncryptionUser("wso2carbon");
// You need to have password call-back class to provide the user password
rc.setPwCbClass(PWCBHandler.class.getName());  

keystore = "wso2carbon.jks";
merlinProp = new Properties();
merlinProp.put("org.apache.ws.security.crypto.merlin.keystore.type","JKS");
merlinProp.put("org.apache.ws.security.crypto.merlin.file", keystore);
merlinProp.put("org.apache.ws.security.crypto.merlin.keystore.password","wso2carbon");

sigCryptoConfig = new CryptoConfig();
sigCryptoConfig.setProvider("org.apache.ws.security.components.crypto.Merlin");
sigCryptoConfig.setProp(merlinProp);

encrCryptoConfig = new CryptoConfig();
encrCryptoConfig.setProvider("org.apache.ws.security.components.crypto.Merlin");
encrCryptoConfig.setProp(merlinProp);

rc.setSigCryptoConfig(sigCryptoConfig);
rc.setEncrCryptoConfig(encrCryptoConfig);

policy.addAssertion(rc);
return policy;
}

private static OMElement getRSTTemplate() throws Exception {
OMFactory fac = OMAbstractFactory.getOMFactory();
OMElement elem = fac.createOMElement(Constants.RST_TEMPLATE);
TrustUtil.createTokenTypeElement(RahasConstants.VERSION_05_02, elem).setText(XML.SAML_NS);
TrustUtil.createKeyTypeElement(RahasConstants.VERSION_05_02, elem,RahasConstants.KEY_TYPE_SYMM_KEY);
TrustUtil.createKeySizeElement(RahasConstants.VERSION_05_02, elem, 256);
return elem;
}

private static OMElement getPayload(String value) {
OMFactory factory = null;
OMNamespace ns = null;
OMElement elem = null;
OMElement childElem = null;

factory = OMAbstractFactory.getOMFactory();
ns = factory.createOMNamespace("http://echo.services.core.carbon.wso2.org", "ns1");
elem = factory.createOMElement("echoString", ns);
childElem = factory.createOMElement("in", null);
childElem.setText(value);
elem.addChild(childElem);
return elem;
}
}
package org.apache.ws.axis2;

import org.apache.ws.security.WSPasswordCallback;

import javax.security.auth.callback.Callback;
import javax.security.auth.callback.CallbackHandler;
import javax.security.auth.callback.UnsupportedCallbackException;

public class PWCBHandler implements CallbackHandler {

public void handle(Callback[] callbacks) throws UnsupportedCallbackException {
WSPasswordCallback cb = (WSPasswordCallback) callbacks[0];
if ("admin".equals(cb.getIdentifier())) {
cb.setPassword("admin");
} else {
cb.setPassword("wso2carbon");
}
}
}