Monday, January 16, 2012

Have you ever tried to invoke secure WebLogic web services with javaws client? - part 1

It was a beautiful sunny Monday morning in the capital - I live and work in Athens, Greece - despite the low temperature (at least for the Athenian criteria). Birds were chirping, people were whining about the known financial problems of Greece... the usual stuff... Nothing could foreshadow what would trouble me for the rest of the week.

I prepared a monstrous-in-dimensions cup of coffee and opened my - really huge and exponentially growing - task list. The task-in-focus seemed simple (ignorant me)!

Have you ever tried to invoke secure WebLogic web services with javaws client? 

The requirement was to build a java web start application that would manage PDF documents. That client should communicate with a server exchanging information like the PDF document itself and some other less significant stuff. The building blocks were there, referring to the server and client components. The actual task was to secure their communication. The server component is implemented as a web service running on WebLogic, so the secure communication would rely on WS-security.

Don't get me any wrong... I love WebLogic! Apart from the licensing policy, which requires thorough analysis and infinite amount of usually dead-end discussion, there are religious arguments against or in favor of WebLogic. I cannot say that I have been particularly involved with many application servers. I do have some professional experience with Tomcat (it's actually a Servlet container and not an full-blown application server, but anyway...), Glassfish and JBoss (not a full JavaEE6 app server, but implements the Web Profile - see the differences here). All these are perfectly OK (especially JBoss). They do their work. There are strong communities behind them that can help you with almost anything. But they are not WebLogic! It's a matter of taste... a purely subjective factor. I won't delve into much detail about the pros and cons of WebLogic or a JavaEE app server comparison (see here). I can only say one thing... WebLogic administration panel simply rocks! It's the best... extremely powerful and easy-to-use.

Leaving behind the discussion about app servers, there is a java web start application that manages PDF documents and a web service, running in WebLogic 10.3.3, that communicates with the aforementioned javaws app. What I had to do is secure the communication. Since the security requirements were very high, the best solution is to adopt message-level security (both encryption and signing).

Oracle has published a very good (but certainly not complete) documentation here. The selected ws-security policy is Wssp1.2-2007-Wss1.1-X509-Basic256.xml with both encryption and signing in all methods. I won't go into any details regarding the theory behind this... it's PKI stuff (not trivial but easy to learn).
Three things had to be implemented:
  • Configuring WebLogic web service security
  • Coding the web service
  • Coding the web service client

Configuring WebLogic web service security

The following steps need to be taken:
  • Obtain two private key and digital certificate pairs to be used by the Web services runtime. One of the pairs is used for digitally signing the SOAP message and the other for encrypting it. Although not required, Oracle recommends that you obtain two pairs that will be used only by WebLogic Web services. You must also ensure that both of the certificate's key usage matches what you are configuring them to do. For example, if you are specifying that a certificate be used for encryption, be sure that the certificate's key usage is specified as for encryption or is undefined. Otherwise, the Web services security runtime will reject the certificate. You can use the Cert Gen utility or Sun Microsystem's keytool utility (see here) to perform this step. For development purposes, the keytool utility is the easiest way to get started. The first step is to create the client & server credentials. The type of these credentials is non other than private key - X509 certificate pairs. 
  • Create, if one does not currently exist, a custom identity keystore for WebLogic Server and load the private key and digital certificate pairs you obtained in the preceding step into the identity keystore. If you have already configured WebLogic Server for SSL, then you have already created an identity keystore that you can also use in this step. You can use WebLogic's ImportPrivateKey utility and Sun Microsystem's keytool utility (see here) to perform this step. For development purposes, the keytool utility is the easiest way to get started.
  • Using the Administration Console, configure WebLogic Server to locate the keystore you created in the preceding step. If you are using a keystore that has already been configured for WebLogic Server, you do not need to perform this step.
  • Using the Administration Console, create the default Web service security configuration, which must be named default_wss. The default Web service security configuration is used by all Web services in the domain unless they have been explicitly programmed to use a different configuration. For further details, click here. Below, there is a sample of the local WebLogic configuration.
<webservice-security>
  <name>x509AuthSignEncrWSS</name>
  <webservice-token-handler>
    <name>th1</name>
    <class-name>weblogic.xml.crypto.wss.BinarySecurityTokenHandler</class-name>
    <token-type>x509</token-type>
    <configuration-property>
      <name>UseX509ForIdentity</name>
      <encrypt-value-required>false</encrypt-value-required>
      <value>true</value>
    </configuration-property>
    <handling-order>1</handling-order>
  </webservice-token-handler>
</webservice-security>
 
  • Update the default Web services security configuration you created in the preceding step to use one of the private key and digital certificate pairs for digitally signing SOAP messages. See Specify the key pair used to sign SOAP messages in Oracle WebLogic Server Administration Console Help. In the procedure, when you create the properties used to identify the keystore and key pair, enter the exact value for the Name of each property (such as IntegrityKeyStore, IntegrityKeyStorePassword, and so on), but enter the value that identifies your own previously-created keystore and key pair in the Value fields.
  • Similarly, update the default Web services security configuration you created in a preceding step to use the second private key and digital certificate pair for encrypting SOAP messages. See Specify the key pair used to encrypt SOAP messages in Oracle WebLogic Server Administration Console Help. In the procedure, when you create the properties used to identify the keystore and key pair, enter the exact value for the Name of each property (such as ConfidentialityKeyStore. ConfidentialityKeyStorePassword, and so on), but enter the value that identifies your own previously-created keystore and key pair in the Value fields.
Coding the web service

import javax.jws.WebMethod;
import javax.jws.WebParam;
import javax.jws.WebService;
import weblogic.jws.Policies;
import weblogic.jws.Policy;

import weblogic.jws.WLHttpTransport;
import weblogic.jws.security.WssConfiguration;

@WebService()
@WssConfiguration(value="x509AuthSignEncrWSS")
@Policy(uri="Wssp1.2-2007-Wss1.1-X509-Basic256.xml")
public class MyFirstSecWS {

  @WebMethod(operationName = "sayHi")
  @Policies({
      @Policy(uri="policy:Wssp1.2-2007-SignBody.xml"),
      @Policy(uri="policy:Wssp1.2-2007-EncryptBody.xml")
  })
  public String sayHi(@WebParam(name = "name")
  String name) {
      return "Hi " + name;
  }
}
 
Nothing tricky here... very simple! We just denoted the security policy being used (Wssp1.2-2007-Wss1.1-X509-Basic256.xml) and the signing and encryption subpolicies (Wssp1.2-2007-SignBody.xml, Wssp1.2-2007-EncryptBody.xml) for the service method. Not that this is not the actual service, but just an example!!!


Coding the web service client
import java.io.FileInputStream;
import java.io.IOException;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.net.ssl.TrustManagerFactory;
import javax.xml.ws.BindingProvider;
import weblogic.security.SSL.TrustManager;

import weblogic.wsee.security.bst.ClientBSTCredentialProvider;
import weblogic.xml.crypto.wss.WSSecurityContext;
import weblogic.xml.crypto.wss.provider.CredentialProvider;

public class Main {

    public static void main(String[] args) {

// keystore that holds the client private key
String ksFile = <keystore filepath>
org.ciec.ws.client.MyFirstSecWSService service = new org.ciec.ws.client.MyFirstSecWSService();
org.ciec.ws.client.MyFirstSecWS port = service.getMyFirstSecWSPort();
//create credential provider and set it to the request context
List credProviders = new ArrayList();
//client side BinarySecurityToken credential provider -- x509
CredentialProvider cp = null;
    try {
 cp = new ClientBSTCredentialProvider(ksFile, <ksPassword>, <keyAlias>, <keyPassword>, <keystoreType - JKS or PKCS#12>,
  getPublicCertificate(<serverCertificatePath>));
    } catch (Exception ex) {
 Logger.getLogger(Main.class.getName()).log(Level.SEVERE, null, ex);
    }

credProviders.add(cp);
Map<String, Object> requestContext = ((BindingProvider) port).getRequestContext();
requestContext.put(WSSecurityContext.CREDENTIAL_PROVIDER_LIST, credProviders);
requestContext.put(WSSecurityContext.TRUST_MANAGER, new TrustManager() {
 public boolean certificateCallback(X509Certificate[] xcs, int i) {
     // validation code here that inspects the X.509 certificates being passed
     return true;
 }
    });
try {
    String response = port.sayHi("World");
    System.out.println("response = " + response);
} catch (Throwable e) {
    e.printStackTrace();
}
}

    public static X509Certificate getPublicCertificate(String path) throws IOException, CertificateException {
      FileInputStream is = new FileInputStream(path);
      CertificateFactory cf = CertificateFactory.getInstance("X.509");
      X509Certificate cert = (X509Certificate) cf.generateCertificate(is);
      return cert;
    }
}
 
It's not a proof of the PoincarĂ© conjecture... it's a simple piece of code defining some security attributes in respect to the web service port. Two things are established: the client's identity and trust resolution. Well... in the code shown above only the first one is trully established, defining all appropriate information for accessing the client's private key. This is done by creating a ClientBSTCredentialProvider object, which (according to javaDoc) creates client BST credential provider for the indicated keystore, certificate alias and server certificate. There are many signatures for the ClientBSTCredentialProvider constructor but only use the other that also specifies the server certificate as well, otherwise nothing will work! Unfortunately I could not find it in the oracle's documentation and took me some time to figure out...
Client handles server identification by defining  a TrustManager implementation with a proper callback function being responsible for returning true or false based on the  validity of theserver certificate it will be fed with. In the code shown above we simply trust everything (which of course we shouldn't).

Almost all the information mentioned up to this point is available through the oracle's online documentation. Everything worked just fine when the client was tested in NetBeans. I should also state here that I use NetBeans instead of Eclipse, without any concrete arguments - just a matter of choice. I have been using Eclipse for a number of years in the past. It's perfectly OK and to be honest with better look-n-feel than NetBeans!

As far as the client classpath is concerned, I used weblogic.jar which can be found in WL_HOME/server/lib. Oracle states that wlfullclient.jar should be used instead for WebLogic 10+. I'll go into that in a bit... Generally, there are numerous clients for WebLogic server components. The analysis of them can be found here.

The problems begun when I tried to make a truly stand-alone client in a workstation with no WebLogic installation... In my next post I will share my torture... Stay tuned for some purely technical headaches! 
“Never memorize something that you can look up” - Albert Einstein

2 comments:

  1. Hi,

    I've developed a java client like this which runs on weblogic 10.0.

    When I try to call this client I'm receiving the following error:

    uploadPort crated successfully.
    <7-nov-2012 22.22.12 CET>
    <7-nov-2012 22.22.12 CET>
    <7-nov-2012 22.22.13 CET>
    <7-nov-2012 22.22.13 CET>
    <7-nov-2012 22.22.13 CET>
    javax.xml.ws.WebServiceException: javax.net.ssl.SSLKeyException: [Security:09047
    7]Certificate chain received from secureproctest.terna.it - 193.108.204.38 was n
    ot trusted causing SSL handshake failure.
    at com.sun.xml.ws.transport.http.client.HttpClientTransport.checkRespons
    eCode(HttpClientTransport.java:233)
    at com.sun.xml.ws.transport.http.client.HttpClientTransport.getHeaders(H
    ttpClientTransport.java:145)
    at com.sun.xml.ws.transport.http.client.HttpTransportPipe.process(HttpTr
    ansportPipe.java:116)
    at com.sun.xml.ws.protocol.soap.ClientMUPipe.process(ClientMUPipe.java:5
    8)
    at com.sun.xml.ws.handler.HandlerPipe.process(HandlerPipe.java:107)
    at com.sun.xml.ws.handler.HandlerPipe.process(HandlerPipe.java:107)

    What I'm doing wrong ?

    Do you need my code ?

    Regards,

    Fa

    ReplyDelete
    Replies
    1. Hi there,

      judging from the error message ("Certificate chain received from secureproctest.terna.it - 193.108.204.38 was not trusted causing SSL handshake failure"), it's obvious that the chain of trust is not established, meaning that the server has presented an invalid certificate. The term "invalid" refers to the server's certificate not being present in your truststore. If you take a look at the web service client code, written above, you may see this line:

      cp = new ClientBSTCredentialProvider(ksFile,... getPublicCertificate(<serverCertificatePath>));

      Note that the last argument refers to the server certificate pathname (something like: "/tmp/server.cer"). I think this should do the trick.

      Cheers,
      Paul.

      P.S. Italian?

      Delete