Secure web services: Signing SOAP messages using WSO2 WSAS and Apache Rampart
In my previous blog post, I demonstrated how Axis2 or WSO2 WSAS web service is deployed and invoked securely with user name token authentication. Here I'm going to invoke the same web service by signing the SOAP messages using server/client keystores instead of username tokens.
When signing soap messages in this way, we can ensure;
Non-repudiation
No tampering of the messages
However, this can not be considered as a good mechanism for confidential message transmission since the message contents are not encrypted.
Scenario
We need to invoke the default version service in secure manner. For that, we are supposed to use two different keystores for client and service. Client uses the client keystore to sign the SOAP request and server responds by signing the reply message using server keystore.
Step 1
First, we need to create two separate keystores for client and server. Java keytool can be used to generate keystores.
Create client keystore
Open a command window (or shell in linux) and type:
keytool -genkey -alias clientks -keyalg RSA -keystore C:\wssecurity\clientks.jks -storepass clientks
This will generate a keystore with necessary client certificate at the specified location.
Create server keystore
keytool -genkey -alias serverks -keyalg RSA -keystore C:\wssecurity\serverks.jks -storepass serverks
Import the client certificate to server keystore
Having two different keystores are not enough for secure message transmission. We need to import certificates of each other to ensure the message authentication.
First, we need to get the client certificate out from the client keystore as follows.
keytool -export -alias clientks -keystore C:\wssecurity\clientks.jks -file C:\wssecurity\client.cert
Next, we can import that certificate to server keystore as follows.
keytool -import -file C:\wssecurity\client.cert -keystore C:\wssecurity\serverks.jks -storepass serverks -alias client
Similarly, we can import server certificate to client keystore as follows.
Import the server certificate to client keystore
keytool -export -alias serverks -keystore C:\wssecurity\serverks.jks -file C:\wssecurity\server.cert
keytool -import -file C:\wssecurity\server.cert -keystore C:\wssecurity\clientks.jks -storepass clientks -alias server
Now, our client and server keystores are ready.
Step 2
Lets configure the server side security first. For that, you need to upload the server keystore in to WSO2 WSAS as explained below.
Access WSO2 WSAS management console using https://localhost:9443 and log in using default admin credentials (admin/admin).
Click on the 'keystores' link in the left pane of the management console. The following page will be displayed. Browse serverks.jks and enter keystore password. Then, click 'Next'.
Enter private key password of the serverks.jks (serverks). Click next button to continue. If everything is successful, you will get 'Keystore serverks.jks successfully added.' message.
Step 3
Now we need to enable security on one of the services available in WSAS management console. Select 'version' service in services and service group management page. You will be directed to the service management page of the version service. Select 'Manage security configuration' link.
Following page will be displayed where you can select 'Sign only - X509 Authentication' option.
Now you should be in the Services> version>Security Configuration> scenario2' page. Select 'serverks.jks' as the trusted certificate store and private keystore. Click on 'Apply'. If everything is successful you will get "Security scenario successfully applied" message.
You can check the security policy which has been generated by WSAS if you click on 'Edit service policies' link in the service management page.
You will see the following element there in the generated policy.
<rampart:property name="org.wso2.wsas.security.wso2wsas.crypto.keystore">serverks.jks</rampart:property>
Make sure to change the path of the serverks.jks correctly in this element. In our case it should be C:\wssecurity\serverks.jks
Thats all about the server side security configuration. Lets write a java client and invoke the service securely!
Step 4
WSAS provides a very useful code generation utility which can be used to generate a client stub easily. Select version service and click on 'Generate Client' link. You will be directed to 'Services>version>Stub generation' page. Set unpack class option to false and click on 'Generate' by leaving the other default options.
Save the generated client jar file in your file system.
Step 5
In the client side, we need to sign the SOAP message using the client keystore and we must adhere to the security policy of the server. Therefore, we need to have a client side policy which provides the path of client keystore and user who signs the request SOAP message. I will not describe all the information available in client security policy. Please download policy.xml from here and save it in your file system. In the mean time, we should have a look at the RampartConfig element in policy.xml because it is where we configure the client keystore information.
<ramp:RampartConfig xmlns:ramp="http://ws.apache.org/rampart/policy">
<ramp:user>signingclient</ramp:user>
<ramp:passwordCallbackClass>org.test.PWCBHandler
</ramp:passwordCallbackClass>
<ramp:signatureCrypto>
<ramp:crypto provider="org.apache.ws.security.components.crypto.Merlin">
<ramp:property name="org.apache.ws.security.crypto.merlin.keystore.type">JKS
</ramp:property>
<ramp:property name="org.apache.ws.security.crypto.merlin.file">C:\wssecurity\clientks.jks
</ramp:property>
<ramp:property name="org.apache.ws.security.crypto.merlin.keystore.password"
>clientks
</ramp:property>
</ramp:crypto>
</ramp:signatureCrypto>
</ramp:RampartConfig>
Here we use PWCBHandler(password call back handler) class to get the password of the user who signs the message.
public class PWCBHandler implements CallbackHandler{
public void handle(Callback[] callbacks) throws IOException,
UnsupportedCallbackException {
for (int i = 0; i < pwcb =" (WSPasswordCallback)callbacks[i];" id =" pwcb.getIdentifer();" style="font-weight: bold;">
Step 6
We are going to complete our scenario by constructing the client using the generated stub in step 4. In our client, first we need to set the system properties for SSL since we are going to access version service via https. (Please modify keystore path according to your file system)
System.setProperty("javax.net.ssl.trustStore","C:\\wsas\\wsas-2.2.1\\
wso2wsas-2.2.1\\conf\\wso2wsas.jks"); System.setProperty("javax.net.ssl.trustStorePassword", "wso2wsas");
We need to instruct our client to use rampart module, we cannot simply add a mar(module archive) to our class path. Therefore we can create a configurationContext instance using file system and use it as follows. Please make sure to have the rampart-*.mar in your repository.
ConfigurationContext ctx = ConfigurationContextFactory.createConfigurationContextFromFileSystem("C:\\wsas
\\client-repo\\", null);
Next, create an instance of version service stub with passing the above Configurationcontext as an argument in the constructor.
VersionStub stub = new VersionStub(ctx, "https://192.168.1.2:9443/services/version");
Rampart module can be engaged in client as follows.
stub._getServiceClient().engageModule("rampart");
Next, the policy.xml which has been defined in step 5 needs to be loaded. We can do that using ServiceClient options class by setting it as a property.
Options options = stub._getServiceClient().getOptions();
options.setProperty(RampartMessageData.KEY_RAMPART_POLICY, loadPolicy("C:\\wsas\\client-repo\\policy.xml"));
stub._getServiceClient().setOptions(options);
private static Policy loadPolicy(String xmlPath){
StAXOMBuilder builder = new StAXOMBuilder(xmlPath);
return PolicyEngine.getPolicy(builder.getDocumentElement());
}
Everything is ready! We can run our client now. Make sure to add jars included in WSO2WSAS_HOME/lib to your class path.
You can download the complete source of the client from here.
When signing soap messages in this way, we can ensure;
Non-repudiation
No tampering of the messages
However, this can not be considered as a good mechanism for confidential message transmission since the message contents are not encrypted.
Scenario
We need to invoke the default version service in secure manner. For that, we are supposed to use two different keystores for client and service. Client uses the client keystore to sign the SOAP request and server responds by signing the reply message using server keystore.
Step 1
First, we need to create two separate keystores for client and server. Java keytool can be used to generate keystores.
Create client keystore
Open a command window (or shell in linux) and type:
keytool -genkey -alias clientks -keyalg RSA -keystore C:\wssecurity\clientks.jks -storepass clientks
This will generate a keystore with necessary client certificate at the specified location.
Create server keystore
keytool -genkey -alias serverks -keyalg RSA -keystore C:\wssecurity\serverks.jks -storepass serverks
Import the client certificate to server keystore
Having two different keystores are not enough for secure message transmission. We need to import certificates of each other to ensure the message authentication.
First, we need to get the client certificate out from the client keystore as follows.
keytool -export -alias clientks -keystore C:\wssecurity\clientks.jks -file C:\wssecurity\client.cert
Next, we can import that certificate to server keystore as follows.
keytool -import -file C:\wssecurity\client.cert -keystore C:\wssecurity\serverks.jks -storepass serverks -alias client
Similarly, we can import server certificate to client keystore as follows.
Import the server certificate to client keystore
keytool -export -alias serverks -keystore C:\wssecurity\serverks.jks -file C:\wssecurity\server.cert
keytool -import -file C:\wssecurity\server.cert -keystore C:\wssecurity\clientks.jks -storepass clientks -alias server
Now, our client and server keystores are ready.
Step 2
Lets configure the server side security first. For that, you need to upload the server keystore in to WSO2 WSAS as explained below.
Access WSO2 WSAS management console using https://localhost:9443 and log in using default admin credentials (admin/admin).
Click on the 'keystores' link in the left pane of the management console. The following page will be displayed. Browse serverks.jks and enter keystore password. Then, click 'Next'.
Enter private key password of the serverks.jks (serverks). Click next button to continue. If everything is successful, you will get 'Keystore serverks.jks successfully added.' message.
Step 3
Now we need to enable security on one of the services available in WSAS management console. Select 'version' service in services and service group management page. You will be directed to the service management page of the version service. Select 'Manage security configuration' link.
Following page will be displayed where you can select 'Sign only - X509 Authentication' option.
Now you should be in the Services> version>Security Configuration> scenario2' page. Select 'serverks.jks' as the trusted certificate store and private keystore. Click on 'Apply'. If everything is successful you will get "Security scenario successfully applied" message.
You can check the security policy which has been generated by WSAS if you click on 'Edit service policies' link in the service management page.
You will see the following element there in the generated policy.
Make sure to change the path of the serverks.jks correctly in this element. In our case it should be C:\wssecurity\serverks.jks
Thats all about the server side security configuration. Lets write a java client and invoke the service securely!
Step 4
WSAS provides a very useful code generation utility which can be used to generate a client stub easily. Select version service and click on 'Generate Client' link. You will be directed to 'Services>version>Stub generation' page. Set unpack class option to false and click on 'Generate' by leaving the other default options.
Save the generated client jar file in your file system.
Step 5
In the client side, we need to sign the SOAP message using the client keystore and we must adhere to the security policy of the server. Therefore, we need to have a client side policy which provides the path of client keystore and user who signs the request SOAP message. I will not describe all the information available in client security policy. Please download policy.xml from here and save it in your file system. In the mean time, we should have a look at the RampartConfig element in policy.xml because it is where we configure the client keystore information.
<ramp:RampartConfig xmlns:ramp="http://ws.apache.org/rampart/policy">
<ramp:user>signingclient</ramp:user>
<ramp:passwordCallbackClass>org.test.PWCBHandler
</ramp:passwordCallbackClass>
<ramp:signatureCrypto>
<ramp:crypto provider="org.apache.ws.security.components.crypto.Merlin">
<ramp:property name="org.apache.ws.security.crypto.merlin.keystore.type">JKS
</ramp:property>
<ramp:property name="org.apache.ws.security.crypto.merlin.file">C:\wssecurity\clientks.jks
</ramp:property>
<ramp:property name="org.apache.ws.security.crypto.merlin.keystore.password"
>clientks
</ramp:property>
</ramp:crypto>
</ramp:signatureCrypto>
</ramp:RampartConfig>
Here we use PWCBHandler(password call back handler) class to get the password of the user who signs the message.
public class PWCBHandler implements CallbackHandler{
public void handle(Callback[] callbacks) throws IOException,
UnsupportedCallbackException {
for (int i = 0; i < pwcb =" (WSPasswordCallback)callbacks[i];" id =" pwcb.getIdentifer();" style="font-weight: bold;">
Step 6
We are going to complete our scenario by constructing the client using the generated stub in step 4. In our client, first we need to set the system properties for SSL since we are going to access version service via https. (Please modify keystore path according to your file system)
System.setProperty("javax.net.ssl.trustStore","C:\\wsas\\wsas-2.2.1\\
wso2wsas-2.2.1\\conf\\wso2wsas.jks"); System.setProperty("javax.net.ssl.trustStorePassword", "wso2wsas");
We need to instruct our client to use rampart module, we cannot simply add a mar(module archive) to our class path. Therefore we can create a configurationContext instance using file system and use it as follows. Please make sure to have the rampart-*.mar in your repository.
ConfigurationContext ctx = ConfigurationContextFactory.createConfigurationContextFromFileSystem("C:\\wsas
\\client-repo\\", null);
Next, create an instance of version service stub with passing the above Configurationcontext as an argument in the constructor.
VersionStub stub = new VersionStub(ctx, "https://192.168.1.2:9443/services/version");
Rampart module can be engaged in client as follows.
stub._getServiceClient().engageModule("rampart");
Next, the policy.xml which has been defined in step 5 needs to be loaded. We can do that using ServiceClient options class by setting it as a property.
Options options = stub._getServiceClient().getOptions();
options.setProperty(RampartMessageData.KEY_RAMPART_POLICY, loadPolicy("C:\\wsas\\client-repo\\policy.xml"));
stub._getServiceClient().setOptions(options);
private static Policy loadPolicy(String xmlPath){
StAXOMBuilder builder = new StAXOMBuilder(xmlPath);
return PolicyEngine.getPolicy(builder.getDocumentElement());
}
Everything is ready! We can run our client now. Make sure to add jars included in WSO2WSAS_HOME/lib to your class path.
You can download the complete source of the client from here.
Comments
import org.wso2.wsas.client.VersionStub;
cannot be resolved.
I have imported all the needed libriries.
http://wso2.org/project/wsas/java/2.2.1/docs/apidocs/index.html