Friday, February 13, 2004


Securing a Web Service - Client SSL

Now that we have a secured the server with SSL, secured the service with basic authentication and added valid users to our JAAS XML provider, we need to create a client capable of sending this information to the server using SSL.

The quick and simple expectation is that all you have to do is change the URI used in the client from http://127.0.0.1:8888/ws/EchoService to https://127.0.0.1:4443/ws/EchoService. Try it out.

If you are running on a machine with an OracleAS install on it, it probably will work. If you are on a client machine on which there is no OracleAS installed (for example, just JDeveloper) you will likely get this error:

java.lang.UnsatisfiedLinkError: no njssl9 in java.library.path
at java.lang.ClassLoader.loadLibrary(ClassLoader.java:1403)
at java.lang.Runtime.loadLibrary0(Runtime.java:788)
at java.lang.System.loadLibrary(System.java:832)
at oracle.security.ssl.OracleSSLSocketImpl.<clinit>(Unknown Source) ...

The njssl9.dll or njssl9.so files are Oracle's SSL implementation. You have two choices ... if you run the client in an OracleAS environment, you are set - this file will be found. If you aren't, you need to think about using Sun's JSSE which will help you out.

As I am running in JDeveloper which has a built in JDK, I am going to do two things to deal with this. First, switch it over to Sun's JDK for the runtime (the reason here is so that I have an client JDK representative of a non-Oracle environment - the built in JDK in JDeveloper is specific to JDeveloper) and second swap out Oracle's SSL libraries for JSSE from Sun's JDK (so you don't have the njssl9.* dependencies).

Note again I am using JDK 1.4.1 which has JSSE built in. Here are the steps:

1. Run JDeveloper using a native JDK rather than the JDK it ships with. To do this go to your project properties under the library node ((menu Project->Properties->Libraries) and create a new J2SE runtime version using <JAVA_HOME>\jdk\bin\java.exe as the entry. This will make JDeveloper use the native JDK to run applications rather than its embedded JDK. Here are some pictures showing what I have done:


2. Create a new client library within JDeveloper (menu Project->Properties->Libraries) containing all the same jars as the existing Oracle SOAP entry but replacing all the jssl-1_2.jar and javax-ssl-1_2.jar files with the JSSE library from the native Sun JDK. The existing Oracle SOAP libraries looks like this:

Oracle SOAP:

C:\jdev\jdev905.1375\jdev\lib\jdev-rt.jar;
C:\jdev\jdev905.1375\soap\lib\soap.jar;
C:\jdev\jdev905.1375\lib\xmlparserv2.jar;
C:\jdev\jdev905.1375\jlibjavax-ssl-1_2.jar;
C:\jdev905.1375\jlibjssl-1_2.jar;
C:\jdev\jdev905.1375\j2ee\home\lib\activation.jar;
C:\jdev\jdev905.1375\j2ee\home\lib\mail.jar;
C:\jdev\jdev905.1375\j2ee\home\lib\http_client.jar

My modified version looks like this - note the key change is that I replaced the Oracle SSL libraries with jsse.jar from Sun's JDK:

JSSESOAP:

C:\jdk1.4.1\jre\lib\jsse.jar;
C:\jdev\jdev905.1375\soap\lib\soap.jar;
C:\jdev\jdev905.1375\lib\xmlparserv2.jar;
C:\jdev905.1375\soap\lib\soap.jar;
C:\jdev\jdev905.1375\lib\xmlparserv2.jar;
C:\jdev\jdev905.1375\j2eehome\lib\activation.jar;
C:\jdev\jdev905.1375j2ee\home\lib\mail.jar;
C:\jdev\jdev905.1375\j2ee\home\lib\http_client.jar
C:\jdev\jdev905.1375\j2ee\home\lib\mail.jar;
C:\jdev\jdev905.1375\j2ee\home\lib\http_client.jar

Now we are almost in business. But, run the client again and you will experience no joy :-( You likely will get an error message like this:

[SOAPException: faultCode=SOAP-ENV:IOException; msg=java.security.cert.CertificateException: Couldn&apos;t find trusted certificate; targetException=javax.net.ssl.SSLHandshakeException: java.security.cert.CertificateException: Couldn't find trusted certificate

No worries! This one is easy to solve. What's happened now is that your client JDK (in this case the client is JDeveloper using a regular JDK) is looking at its list of trusted certificates and the one we created for OC4J previously is not in its trusted key store.

To fix this let's export the certificate we created before into a working file server.ser and then import it into the client keystore. Here goes:

1. Run the command:

<JAVA_HOME>\jre\bin\keytool -export -alias oc4j-sv -storepass welcome
-file server.cer -keystore server.keystore

2. Next, for the JDK we will be using to run the client (in my case the same as the server), we need to import this certificate as a trusted party into the keystore.

I could just use the default keystore, "cacerts" (located in <JDK_HOME>\jre\lib\security with a password of "changeit", as a by the by!) but because I am running both the client and server on the same JDK, I am going to create a separate client keystore.

Let's start this last bit by copying server.cer to <JAVA_HOME>\jre\lib\security:

cp <OC4J_HOME>\j2ee\server.ser <JAVA_HOME>\jre\lib\security

3. Next let's import this certificate into a new client keystore, called client.keystore. The act of importing will create the keystore:

<JAVA_HOME>\jre\bin\keytool -import -v -trustcacerts -alias oc4j-sv
-file server.cer -keystore client.keystore -keypass welcome -storepass welcome

Remember to answer "yes" to the question of whether you trust the certificate. Here is a picture of the process:

Finally, the same client we ran in this blog entry Securing a Web Service - BASIC AUTH here should again run successfully with one last change - I have to add this bit of code to my Web service stub:

System.setProperty("javax.net.ssl.keyStore",
"file:\\\C:\\jdk1.4.1\\jre\\lib\\security\\client.keystore")
System.setProperty("javax.net.ssl.keyStorePassword","welcome");

<Note-March 4>

Note, when I updated this to use client.keystore rather than cacerts, the default JDK keystore, a recent second time around I did not get it to work assuming it was a syntax problem. The only way I could get this client and the next entry on client certificates to work was to import the oc4j-sv server certificate into the "cacerts" keystore - that is:

<JAVA_HOME>\jre\bin\keytool -import -v -trustcacerts -alias oc4j-sv
-file server.cer -keystore cacerts -keypass changeit -storepass changeit

I assume it is just that I have have a syntax problem in the stub with this line:

System.setProperty("javax.net.ssl.keyStore",
"file:///C://jdk1.4.1//jre/lib//security//client.keystore");
System.setProperty("javax.net.ssl.keyStorePassword","welcome");

which is no longer required in the stub because "cacerts" is the default keystore for the JDK. I have tried quite a few variations to fix this to no avail :-( - next time I revisit this, I will try -D options on the JDK rather than System.setProperty.

</Note-March 4>

So what have we managed to do here? First, our basic authentication continues to work it is just now that when we send our password over the wire, we are using SSL to encrypt it. Further, by using JSSE we can now use any arbitrary Java client and not be dependent on Oracle's SSL libraries (though in this case I continued to use Oracle SOAP - but the heavy lifting of getting rid of the SSL dependency is done).

With SSL there is of course one more step I can do which is to use client certificates too. That is of course a topic for another day!



comment []
12:02:26 AM