WSSecurity with jax-ws and WSS4J

Hello everyone,

In my current project I have one non functional requisite quite challenging (at least for me, a dotnetboy playing around in the java world). As usual it came when the project was half made. It said… soap messages will be encrypted, ssl is not possible.

Well, so we have a few jax-ws services done, and ¡ups! jax-ws is incomplete (one friend told me it was like wcf, nothing further away from the truth, it leaks lots of ws-* features). The thing got harder because we have lots of differents app servers (tomcat, weblogic and jboss). I would be happy having the possibility of using Metro, but I need something independent.

So googling I found the first important thing: the jax-ws handlers. Basically it is a pre and post process of a message. We can modify the message just before is sent through the wire and before it get parsed by the service. How can we modify the service with a handler?

 
@SOAPBinding 
@Stateless 
@HandlerChain(file = "../../../../../../handlers.xml") 
public class Service implements IService { 

@HandlerChain points to the file which defines the classes which will be used as a handler.

 
<?xml version="1.0" encoding="UTF-8"?>
<jws:handler-chains xmlns:jws="http://java.sun.com/xml/ns/javaee">
   <jws:handler-chain>
      <jws:handler>
         <jws:handler-class>
            es.pablocastilla.WSSecurityHandler
         </jws:handler-class>
       </jws:handler>
     </jws:handler-chain>
   </jws:handler-chains>

This classes should implement one special interface (as usual in this kind of things)

 
public class WSSecurityHandler implements      SOAPHandler<SOAPMessageContext>

So know we have something in the service that will transform an encrypted message to a normal one.

And now, how do I code and decode the messages?, well, all can be done with a few single classes:

 
public class WSSecurityHandler implements   SOAPHandler<SOAPMessageContext>, CallbackHandler {

    Properties prop;

     /**
      * Constructor initializes properties
      * */
     public WSSecurityHandler () throws Exception {

         prop = PropSingleton.getSingleton().GetProp();

     }


     /**
      * Returns special headers.
      */
     public Set<QName> getHeaders() {

         Set<QName> HEADERS = new HashSet<QName>();

         HEADERS.add(new QName(WSConstants.WSSE_NS, "Security"));
         HEADERS.add(new QName(WSConstants.WSSE11_NS, "Security"));
         HEADERS.add(new QName(WSConstants.ENC_NS, "EncryptedData"));

         return HEADERS;

     }

     /**
      * Handles the message, code and decode it depending if it is an outgoing or incoming one.
      */

     public boolean handleMessage(SOAPMessageContext messageContext) {

         try {
             // got the message from the context
             SOAPMessage msg = messageContext.getMessage();

            // is outgoing?
           Boolean isOutGoing = (Boolean) messageContext
                     .get(MessageContext.MESSAGE_OUTBOUND_PROPERTY);

             if (isOutGoing) {

                 // if it is outgoing code and sign
                 EncryptUtil.EncryptSOAPEnvelope(msg, prop);
                 EncryptUtil.SignSOAPEnvelope(msg, prop);

             } else {

                 // if it is incooming decode and check signature.
                 EncryptUtil.CheckSignAndDecode(msg, this, prop);

             }

         } catch (Exception ex) {
             ex.printStackTrace();

             throw new RuntimeException(ex.getMessage());
         }

         return true;

     }


     public boolean handleFault(SOAPMessageContext messageContext) {

         return true;
     }

     /**
      * Return the jks key, not necesary
      */

     @SuppressWarnings("deprecation")
     public void handle(Callback[] callbacks) throws IOException,

            UnsupportedCallbackException {

         String password;

         for (Callback cb : callbacks) {

             if (cb instanceof WSPasswordCallback) {

                  WSPasswordCallback pc = (WSPasswordCallback) cb;
 
                 try {

                     password = prop.getProperty("password");

                 } catch (Exception e) {


                     throw new UnsupportedCallbackException(pc,  "failure recovering the key in the properties");

                 }

                if (pc.getIdentifer() != null) {

                     pc.setPassword(password);

                 }

            }

         }

     }


     public void close(MessageContext messageContext) {

     }
}

The class which uses Apache´s WSS4J for encrypting the messages:

public class EncryptUtil{

     @SuppressWarnings( { "unchecked", "deprecation" })
     static void CheckSignatureAndDecode(SOAPMessage msg,   CallbackHandler cb,Properties prop) throws WSSecurityException,            TransformerConfigurationException, TransformerException,  SOAPException, IOException, Exception {

         WSSecurityEngine secEngine = new WSSecurityEngine();
         WSSignEnvelope signer = new WSSignEnvelope();

         String alias = prop.getProperty("alias");// login of jks file
         String password = prop.getProperty("password");// password of jks file


         signer.setUserInfo(alias, password);


         Crypto crypto = CryptoFactory.getInstance(prop);
         org.w3c.dom.Document doc = toDocument(mensaje);

  //after we set the encrypt stuff the processsecurity does all the work

         Vector v = secEngine.processSecurityHeader(doc, null, cb, crypto);
 
         if (v == null) {

             throw new Exception("Access not granted.");

         }

         //put the decoded message into the object
         updateSOAPMessage(doc, msg);
     }



     /**
      * Updates the message with the unencrypt form
      */


     private static SOAPMessage updateSOAPMessage(Document doc, SOAPMessage message) throws SOAPException {

         DOMSource domSource = new DOMSource(doc);
         message.getSOAPPart().setContent(domSource);

         return message;

     }



     /**
      * Changes the SOAPMessage to a dom.Document.
      */


     public static org.w3c.dom.Document toDocument(SOAPMessage soapMsg)             throws SOAPException, TransformerException {

         Source src = soapMsg.getSOAPPart().getContent();

         TransformerFactory tf = TransformerFactory.newInstance();

         Transformer transformer = tf.newTransformer();

         DOMResult result = new DOMResult();
         transformer.transform(src, result);
         return (Document) result.getNode();

     }

    /**
      * Signs  a SOAPMessage
      * 
      * @param mensaje
      * @throws Exception
      */
     @SuppressWarnings("deprecation")
     static void SignSOAPEnvelope(SOAPMessage mensaje, Properties prop) throws Exception {

         // WSSignEnvelope signs a SOAP envelope according to the
         // WS Specification (X509 profile) and adds the signature data
         // to the envelope.
         WSSignEnvelope signer = new WSSignEnvelope();
     
         String alias = prop.getProperty("alias");// "autentiaserver";
         String password = prop.getProperty("password");// "changeit";

         signer.setUserInfo(alias, password);

         Crypto crypto = CryptoFactory.getInstance(prop);

         Document doc = toDocument(mensaje);
         signer.setMustUnderstand(false);

         Document signedDoc = signer.build(doc, crypto);
         DOMSource domSource = new DOMSource(signedDoc);
         mensaje.getSOAPPart().setContent(domSource);

     }


     /**
      * Codes a SOAPMessage.
      */

     @SuppressWarnings("deprecation")
     static void EncryptSOAPEnvelope(SOAPMessage mensaje,Properties prop) throws Exception {

         // WSSignEnvelope signs a SOAP envelope according to the
         // WS Specification (X509 profile) and adds the signature data
         // to the envelope.
         WSEncryptBody encriptador = new WSEncryptBody();


         String alias = prop.getProperty("alias");
         String password = prop.getProperty("password");

         encriptador.setUserInfo(alias, password);

        Crypto crypto = CryptoFactory.getInstance(prop);

         Document doc = toDocument(mensaje);

         encriptador.setMustUnderstand(false);

         Document signedDoc = encriptador.build(doc, crypto);

         DOMSource domSource = new DOMSource(signedDoc);
         mensaje.getSOAPPart().setContent(domSource);
    }
}

All this stuff needs a jdk and some configuration. It is the one which should be place with the properties object which is seen in the classes. Some are repeated, you can improve that code 😛

 
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=password
org.apache.ws.security.crypto.merlin.keystore.alias=login
org.apache.ws.security.crypto.merlin.file=/home/webuser/certs/beaclient/keystore.jks
alias=login
password=password

So we have a jax-ws service using WSSecurity :).

libs needed can be found here:
http://ws.apache.org/wss4j/

Hope it can help you, any comment will be appreciated. I am sorry, but I have no time for developing a working example.

Regards.

Advertisements

7 comments

  1. Antonio · · Reply

    Could you put the source code of the project, please?
    I have some doubts of this example, because I cannot know where the CallBackHandler is?

    1. Sorry, I dont have it 😦

  2. /**
    * A Callback Handler implementation for the case of finding a password to access a
    * cert/private key in a keystore.
    */
    public class KeystoreCallbackHandler implements CallbackHandler {

    private Map users = new HashMap();

    public KeystoreCallbackHandler() {
    }

    public KeystoreCallbackHandler(String keystoreAlias, String keystoreAliasPassw) {
    users.put(keystoreAlias, keystoreAliasPassw);
    }

    public void handle(Callback[] callbacks)
    throws IOException, UnsupportedCallbackException {
    for (int i = 0; i < callbacks.length; i++) {
    if (callbacks[i] instanceof WSPasswordCallback) {
    WSPasswordCallback pc = (WSPasswordCallback) callbacks[i];
    pc.setPassword(users.get(pc.getIdentifier()));
    } else {
    throw new UnsupportedCallbackException(callbacks[i], "Unrecognized Callback");
    }
    }
    }
    }

    You can use it in this way

    private CallbackHandler keystoreCallbackHandler;

    this.keystoreCallbackHandler = new KeystoreCallbackHandler(keystoreAlias, keystoreAliasPassw);

    where keystoreAlias contains same value as "org.apache.ws.security.crypto.merlin.keystore.alias" from props and keystoreAliasPassw contains same value as "org.apache.ws.security.crypto.merlin.alias.password" from props too.

    Regards,

    Víctor.

  3. srini · · Reply

    I followed your code structure and once the code encounters the …Crypto crypto = CryptoFactory.getInstance(password, properties);
    I get the following error…java.lang.RuntimeException: abc123 Not Found.
    I’m using wss4j-2.1.jar. I also have an unused import for the following:-
    import org.apache.ws.security.components.crypto.Merlin;

    My properties file is as follows:
    org.apache.ws.security.components.crypto.provider=org.apache.ws.security.components.crypto.Merlin
    org.apache.ws.security.components.crypto.merlin.keystore.type=jks
    org.apache.ws.security.components.crypto.merlin.keystore.password=abc123
    org.apache.ws.security.components.crypto.merlin.keystore.alias=mzc.csd.disa.mil
    org.apache.ws.security.components.crypto.merlin.keystore.private.password=mzckey01
    org.apache.ws.security.components.crypto.merlin.file=”C:\\Users\\srinivas.sosale\\Documents\\IGCStuff2014\\IGCKeyStore.jks”

    Any ideas why the code blows up here?

    thanks,
    Srini

    1. No sorry 😦

  4. Hi, i dont understand this line:

    prop = PropSingleton.getSingleton().GetProp();

    I already have wss4j-2.1 in my project, what is missing??

    1. Ufff, sorry but i don’t remember. Could it be for taking some properties from a config file?

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: