Sunday, September 21, 2008

Configuring CXF for Web Service Security using WSS4J

In case you directly want to see samples, you can go to Examples of WS-Security.....
You can read to below to understand bits, assuming you have a sample WS application using CXF. To secure that WS application you need to do following,

Create KeyStores and TrustStores
OpenSSL and Java SE 6 keytool creates X509v3 certificates, whereas Java SE 5 or earlier keytool creates X509v1 certificate. If you already have keystores and truststores and you are not sure about version of them, you can verify the version of your X509 certificate using this blog entry I blogged sometime back. Lets create new keystores and truststores with new X509 private and public key pair.
a) Creating Client Public and Private Key in Client keystore and Server Public and Private key in Server Keystore
keytool -genkey -alias clientX509v1 -keypass keypassword -storetype jks -storepass storepassword -validity 3650 -keyAlg RSA -keystore client-keystore.jks
keytool -genkey -alias serverX509v1 -keypass keypassword -storetype jks -storepass storepassword -validity 3650 -keyAlg RSA -keystore server-keystore.jks
b) Exporting clients public key to an external file and servers public key to an external file
keytool -export -alias clientX509v1 -file client-certfile.csr -keystore client-keystore.jks -storepass storepassword -keyAlg RSA
keytool -export -alias serverX509v1 -file server-certfile.csr -keystore server-keystore.jks -storepass storepassword -keyAlg RSA
c) Import the clients public certificate from the external file to server trust store and servers public certificate to the client trust store
keytool -import -noprompt -alias clientX509v1 -file client-certfile.csr -storepass storepassword -keystore server-truststore.jks -storetype JKS
keytool -import -noprompt -alias serverX509v1 -file server-certfile.csr -storepass storepassword -keystore client-truststore.jks -storetype JKS
Now we have required keystores and truststores with X509V1 certificates.

Configure CXF Bus:
Now we need to configure In and Out configuration of the server and client on CXF using spring. WSS4J provides security implementation for CXF, for security to be applied on XML content, CXF required SAAJ Interceptor which creates the SOAP document over which WSS4J works.

On Client

<jaxws:client
name="{http://apache.org/hello_world_soap_http}TimestampSignEncrypt";
createdFromAPI="true">
<jaxws:features>
<bean class="org.apache.cxf.feature.LoggingFeature"/>
</jaxws:features>

<jaxws:outInterceptors>
<bean class="org.apache.cxf.binding.soap.saaj.SAAJOutInterceptor"/>
<ref bean="TimestampSignEncrypt_Request"/>
</jaxws:outInterceptors>

<jaxws:inInterceptors>
<ref bean="TimestampSignEncrypt_Response"/>
<bean class="org.apache.cxf.binding.soap.saaj.SAAJInInterceptor"/>
</jaxws:inInterceptors>

</jaxws:client>

On Server

<jaxws:endpoint
name="{http://apache.org/hello_world_soap_http}TimestampSignEncrypt";
createdFromAPI="true">

<jaxws:features>
<bean class="org.apache.cxf.feature.LoggingFeature"/>
</jaxws:features>

<jaxws:outInterceptors>
<bean class="org.apache.cxf.binding.soap.saaj.SAAJOutInterceptor"/>
<ref bean="TimestampSignEncrypt_Response"/>
</jaxws:outInterceptors>

<jaxws:inInterceptors>
<ref bean="TimestampSignEncrypt_Request"/>
<bean class="org.apache.cxf.binding.soap.saaj.SAAJInInterceptor"/>
</jaxws:inInterceptors>

</jaxws:endpoint>

Above is the CXF Bus configuration on server and client to configure WSS4J, Log an SAAJ interceptors for both in and out configuration.

Provide Security Configuration
Now, we need to write Security configurations on server and client for in and out interceptors.
In this example, I have written all commonly used Actions; using Timestamp, Encrypting method argument, and sigining Timestamp and/or body. The order in which they are written is the order in which these actions are performed. Also, we need to configure user (signature) and encryption user accordingly on server and client. Also, crypto configurations needs to pass about keystore and truststore related information; by signaturePropFile and encryptionPropFile properties.

Below is the security related server side configuration configured in WSS4JInInterceptor and WSS4JOutInterceptor

For Server side outgoing response to Client

<bean class="org.apache.cxf.ws.security.wss4j.WSS4JOutInterceptor" id="TimestampSignEncrypt_Response">

<constructor-arg>
<map>
<entry key="action" value="Timestamp Signature Encrypt"/>
<entry key="user" value="service"/>
<entry key="signaturePropFile"
value="server_decrypt.properties"/>
<entry key="encryptionPropFile"
value="server_sign.properties"/>
<!--entry key="signatureKeyIdentifier" value="DirectReference"/-->
<!--entry key="encryptionKeyIdentifier" value="IssuerSerial" /-->
<entry key="passwordCallbackClass"
value="com.xyz.ws.security.KeystorePasswordCallback"/>
<entry key="signatureParts"
value="{Element}{http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd}Timestamp;{Element}{http://schemas.xmlsoap.org/soap/envelope/}Body"/>
<entry key="encryptionParts"
value="{Element}{http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd}Timestamp;{Element}{http://www.w3.org/2000/09/xmldsig#}Signature;{Content}{http://schemas.xmlsoap.org/soap/envelope/}Body"/>
<!--entry key="encryptionKeyTransportAlgorithm"
value="http://www.w3.org/2001/04/xmlenc#rsa-1_5"/-->
<!--entry key="encryptionSymAlgorithm"
value="http://www.w3.org/2001/04/xmlenc#aes128-cbc"/-->
</map>

</constructor-arg>
</bean>

For Server side incoming request from client

<bean class="org.apache.cxf.ws.security.wss4j.WSS4JInInterceptor" id="TimestampSignEncrypt_Request">

<constructor-arg>
<map>
<entry key="action" value="Timestamp Signature Encrypt"/>
<entry key="signaturePropFile" value="server_sign.properties"/>
<entry key="decryptionPropFile" value="server_decrypt.properties"/>
<entry key="passwordCallbackClass"
value="com.xyz.ws.security.KeystorePasswordCallback"/>
</map>
</constructor-arg>

</bean>

Below is the client side security related configuration configured in WSS4JInInterceptor and WSS4JOutInterceptor

For Client side outgoing request to Server

<bean class="org.apache.cxf.ws.security.wss4j.WSS4JOutInterceptor" id="TimestampSignEncrypt_Request">

<constructor-arg>
<map>
<entry key="action" value="Timestamp Signature Encrypt"/>
<entry key="user" value="client"/>
<entry key="encryptionUser" value="service"/>
<entry key="signatureKeyIdentifier" value="DirectReference"/>
<entry key="signaturePropFile" value="client_sign.properties"/>
<entry key="encryptionPropFile" value="client_encrypt.properties"/>
<entry key="encryptionKeyIdentifier" value="IssuerSerial" />
<entry key="passwordCallbackClass"
value="com.xyz.ws.security.KeystorePasswordCallback"/>
<entry key="signatureParts"
value="{Element}{http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd}Timestamp;{Element}{http://schemas.xmlsoap.org/soap/envelope/}Body"/>
<entry key="encryptionParts"
value="{Element}{http://www.w3.org/2000/09/xmldsig#}Signature;{Content}{http://schemas.xmlsoap.org/soap/envelope/}Body"/>
<!--entry key="encryptionKeyTransportAlgorithm"
value="http://www.w3.org/2001/04/xmlenc#rsa-1_5"/-->
<!--entry key="encryptionSymAlgorithm"
value="http://www.w3.org/2001/04/xmlenc#aes128-cbc"/-->
</map>
</constructor-arg>

</bean>

For Client Side Incoming Response from Server:

<bean class="org.apache.cxf.ws.security.wss4j.WSS4JInInterceptor"
id="TimestampSignEncrypt_Response">

<constructor-arg>
<map>
<entry key="action" value="Timestamp Signature Encrypt"/>
<entry key="signaturePropFile" value="client_encrypt.properties"/>

<entry key="decryptionPropFile" value="client_sign.properties"/>
<entry key="passwordCallbackClass"
value="com.xyz.ws.security.KeystorePasswordCallback"/>
</map>
</constructor-arg>

</bean>

Contents of crypto configuration files:
You require to give information about the keystore or truststore containing X509 private or public key, keystore password and alias name for the public-private key pair.

For encryption , you require to configure public key of the receiver, hence client/server truststore contains public certificate of server/client alias which is serverx509v1/clientx509v1 respectively.

For Signature, you require to use the private key stored in client/server keystore and hence alias clientx509v1/serverx509v1 respectively.

client_encrypt.properties
org.apache.ws.security.crypto.provider=org.apache.ws.security.components.crypto.Merlin
org.apache.ws.security.crypto.merlin.keystore.type=jks
org.apache.ws.security.crypto.merlin.keystore.password=storepassword
org.apache.ws.security.crypto.merlin.keystore.alias=serverx509v1
org.apache.ws.security.crypto.merlin.file=keystore/client-truststore.jks

client_sign.properties
org.apache.ws.security.crypto.provider=org.apache.ws.security.components.crypto.Merlin org.apache.ws.security.crypto.merlin.keystore.type=jks org.apache.ws.security.crypto.merlin.keystore.password=storepassword
org.apache.ws.security.crypto.merlin.keystore.alias=clientx509v1 org.apache.ws.security.crypto.merlin.file=keystore/client-keystore.jks

server_decrypt.properties
org.apache.ws.security.crypto.provider=org.apache.ws.security.components.crypto.Merlin org.apache.ws.security.crypto.merlin.keystore.type=jks org.apache.ws.security.crypto.merlin.keystore.password=storepassword org.apache.ws.security.crypto.merlin.keystore.alias=serverx509v1 org.apache.ws.security.crypto.merlin.file=keystore/server-keystore.jks

server_sign.properties
org.apache.ws.security.crypto.provider=org.apache.ws.security.components.crypto.Merlin org.apache.ws.security.crypto.merlin.keystore.type=jks org.apache.ws.security.crypto.merlin.keystore.password=storepassword
org.apache.ws.security.crypto.merlin.keystore.alias=clientx509v1 org.apache.ws.security.crypto.merlin.file=keystore/aerver-truststore.jks

Install Bouncy Castle Security Provider
Just check the lib folder of CXF binary distribution as in CXF 2.2 onwards, CXF is having bouncy castle jar, hence no requirement to download this jar if your CXF distribution already have one.

(or else) Download the latest Bouncy Castle provider (bcprov-jdkXX-xxx.jar) for your JDK, then put it in JAVA_HOME/jre/lib/ext.Edit JAVA_HOME/jre/lib/security/java.security. Find the list of security providers:Add the Bouncy Castle provider to the list as,
security.provider.10=org.bouncycastle.jce.provider.BouncyCastleProvider {assuming 9 entries are there}

Install JCE Unlimited Strength Jurisdiction Policy Files
Due to import control restrictions for some countries, the Java CryptographyExtension (JCE) policy files shipped with the J2SE DevelopmentKit and the J2SE Runtime Environment allow strong but limited cryptography to be used. These files are located at
/lib/security/local_policy.jar
/lib/security/US_export_policy.jar
An unlimited strength version of these files indicating no restrictions on cryptographic strengths is available on the JDK web site for those living in eligible countries.
Unlimited strength Jurisdiction Policy Files may be downloaded from the JavaSoft's web site.Here is the web address:http://java.sun.com/javase/downloads/index.jsp#docs
(At the end of the page under "Other Downloads" section).
Open the readme.txt and follow the instructions:Basically replace two existing jar for the two new ones((US_export_policy.jar and local_policy.jar )

Run your example by passing configuration files we just made

CXF can discover XML configuration files which you have written. For both web service clients and servers, the default location that CXF will look for a configuration for is "/cxf.xml" on the class path.Pass your server and client config files to the Java call to the server and client class as argument
If you wish to override this location, you can specify a command line property: -Dcxf.config.file=some_other_config.xml. This custom configuration file is also expected to be on the class path.
To use a url as the configuration location, specify as follows: -Dcxf.config.file.url=config_file_url
-Dcxf.config.file.url=client.xml
-Dcxf.config.file.url=server.xml

You may get an exception like below:

Caused by: java.io.IOException: exception decrypting data - java.security.Invali dKeyException: Illegal key size at org.bouncycastle.jce.provider.JDKPKCS12KeyStore.cryptData(Unknown Sou rce) at org.bouncycastle.jce.provider.JDKPKCS12KeyStore.engineLoad(Unknown So urce) at java.security.KeyStore.load(KeyStore.java:1185) at org.apache.ws.security.components.crypto.AbstractCrypto.load(Abstract Crypto.java:168) ... 38 more

Don't worry, you just require to put in unlimited strength JCE files in your jdk/jre. Read and follow steps in #Install JCE Unlimited Strength Jurisdiction Policy Files header just above. They will get resolved once you follow steps.

You may get java.security.UnrecoverableKeyException: Cannot recover key - It happens when your storepassword and keypassword are different. Though I think that is a valid case, but as a workaround you can make both passwords the same. Generate the keystores once again, export the certificates and import them into truststore once again. All commands except below two remains the same.

keytool -genkey -alias clientX509v1 -keypass storepassword -storetype jks -storepass storepassword -validity 3650 -keyAlg RSA -keystore client-keystore.jks
keytool -genkey -alias serverX509v1 -keypass storepassword -storetype jks -storepass storepassword -validity 3650 -keyAlg RSA -keystore server-keystore.jks

I hope now you would have seen secured in and out SOAP messages from client and server. :)
Do please let me know if you face some errors, exceptions and also your comments.

6 comments:

Anonymous said...

Hi Mayank,
I am a newbie to wss4j and after reading your blog tried to implemented the ws security mechanism in my project.
But i am facing a problem.
"org.apache.ws.security.WSSecurityException: An error was discovered processing the Security> header"

Exception trace isn't giving any clue, but I would list out my points as follows:-
1. I am trying to check a simple helloworld service and client.
2. When I passed the request through soapUI giving all the required "user token" information i got the response through soap ui, but as i tried to do so through a simple client i am not able to do so.
3. So the problem i ustand is client is not able to add security elements nor able to unstand it back.
4. I tried all the wriiten things in the blog.

5. Initially I reied it with x509 certificates but then i tried it simply using usertoken but out of luck.

Please help me in this ASAP.
Thanking you for this.

Regards,
Nikheel Ranjan
ranjan.nikheel@gmail.com

Mayank Mishra said...

Can you please give me either whole exception trace or the example what you implemented. It will be helpful to me to help you better to run the sample.

With Regards,
Mayank

t@c0 said...

Hi Mayank,

Please help me to figure out why signature doesn't work for my settings.

I always got "Caused by: org.apache.ws.security.WSSecurityException: General security error (No certificates for user serverx509v1 were found for signature)" in tomcat's log.

Following is my settings and content in jks files.

t@c0 said...

property and jks files can be downloaded from this URL:
https://www.dropbox.com/sh/knqnsd9rjcby51a/FcEH0Le1Ei

Anonymous said...

I am also getting the same exception. Did you resolve this issue? If so, can you share the resolution?

Anonymous said...

To t@c0

When i applied your example on :
https://www.dropbox.com/sh/knqnsd9rjcby51a/FcEH0Le1Ei

it worked well.

I'm asking if you can post the hall instructions to create your jks files please.