The XML Signature Example

This example illustrates how to add digital signature to the SOAP request that goes out from the client and how to verify a signature when the SOAP message comes into the server. For adding signature to the SOAP messages or validating the signature contained in a SOAP message, you have to set various properties on the stub or skeleton. Please refer to the com.sssw.jbroker.web.security.XMLSignatureProperties interface for more information on what properties can be set.

The Shop Interface

The remote interface used in this example is a simple shopping interface, which has methods to list the inventory, get the price for an item, and finally order an item.

package signature;
                                                                           
import java.rmi.Remote;
import java.rmi.RemoteException;
                                                                           
public interface Shop extends Remote
{
    String[] items() throws RemoteException;
                                                                           
    float price(String item) throws NoSuchItemException, RemoteException;
                                                                           
    int order(String item) throws NoSuchItemException, RemoteException;
}

It should be noted that there is no security information present in the remote interface. This follows a proven design pattern in distributed systems that any low-level information such as security information or transactional context get handled "under the covers" by a framework.

The Shopping Client

The shopping client performs the usual steps for getting a service object from JNDI and gets a stub. Note that various properties are set on the Stub for adding digital signature to the outgoing SOAP request messages, as well as to validate the incoming SOAP response messages.

package signature;
                                                                           
import java.io.IOException;
import java.io.InputStream;
                                                                           
import java.util.Map;
import java.util.HashMap;
import java.util.Iterator;
                                                                           
import java.awt.BorderLayout;
                                                                           
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
                                                                           
import java.util.ArrayList;
                                                                           
import javax.xml.rpc.Stub;
import javax.xml.namespace.QName;
import javax.xml.rpc.handler.HandlerInfo;
                                                                           
import javax.naming.InitialContext;
                                                                           
import javax.swing.JFrame;
import javax.swing.JButton;
import javax.swing.JComboBox;
import javax.swing.JOptionPane;
                                                                           
import java.security.KeyStore;
import java.security.PrivateKey;
import java.security.cert.X509Certificate;
                                                                           
import com.sssw.jbroker.web.security.XMLSignatureProperties;
                                                                           
public class Client
{
    public static void main(String[] args) throws Exception
    {
    |   //get the shop service
    |   InitialContext ctx = new InitialContext();
    |   ShopService service = (ShopService)
    |       ctx.lookup("xmlrpc:soap:signature.ShopService");
    |                                                                      
    |   final Shop stub = service.getShopPort();
    |                                                                      
    |   //set this property to sign outgoing messages
    |   ((Stub)stub)._setProperty(XMLSignatureProperties.SIGN_OUTGOING, Boolean.TRUE);
    |                                                                      
    |   //set this property to validate incoming messages
    |   ((Stub)stub)._setProperty(XMLSignatureProperties.VALIDATE_INCOMING, Boolean.TRUE);
    |                                                                      
    |   //get signature information from key store
    |   char[] secret = "secret".toCharArray();
    |   KeyStore ks = KeyStore.getInstance("JKS");
    |   ClassLoader loader = Thread.currentThread().getContextClassLoader();
    |   InputStream is = loader.getResourceAsStream("keystore.jks");
    |   if (is == null)
    |       throw new IOException("failed to load key store resource");
    |   ks.load(is, secret);
    |   is.close();
    |                                                                      
    |   //signature method
    |   ((Stub)stub)._setProperty(XMLSignatureProperties.SIGNATURE_METHOD_URI, 
    |       XMLSignatureProperties.ALGO_ID_SIGNATURE_DSA);
    |                                                                      
    |   //digest method     
    |   ((Stub)stub)._setProperty(XMLSignatureProperties.DIGEST_URI, 
    |       XMLSignatureProperties.ALGO_ID_DIGEST_SHA1);
    |                                                                      
    |   //transforms
    |   String[] tsfms = new String[] { 
    |   |   XMLSignatureProperties.TRANSFORM_ENVELOPED_SIGNATURE, 
    |   };
    |   ((Stub)stub)._setProperty(XMLSignatureProperties.TRANSFORM_URIS, tsfms);
    |                                                                      
    |   //canonicalization method
    |   ((Stub)stub)._setProperty(
    |       XMLSignatureProperties.CANONICALIZATION_METHOD_URI,
    |       XMLSignatureProperties.ALGO_ID_C14N_OMIT_COMMENTS);
    |                                                                      
    |   //key info certificate
    |   X509Certificate cert = (X509Certificate) ks.getCertificate("test");
    |   ((Stub)stub)._setProperty(XMLSignatureProperties.CERTIFICATE, 
    |       cert.getEncoded());
    |                                                                      
    |   //validation public key
    |   ((Stub)stub)._setProperty(XMLSignatureProperties.VALIDATION_PUBLIC_KEY,
    |       cert.getPublicKey().getEncoded());
    |                                                                      
    |   //signing key
    |   PrivateKey privateKey = (PrivateKey) ks.getKey("test", secret);
    |   byte[] prkba = privateKey.getEncoded();
    |   ((Stub)stub)._setProperty(XMLSignatureProperties.PRIVATE_KEY, prkba);
    |                                                                      
    |   //set the end point URL
    |   ((Stub)stub)._setProperty("javax.xml.rpc.service.endpoint.address",
    |       args.length > 0 ? args[0] : "http://localhost:9090/shop");
    |                                                                      
    |   //create a frame, combo box and button
    |   final JFrame frame = new JFrame("Shop Client");
    |   final JComboBox combo = new JComboBox(stub.items());
    |   frame.getContentPane().add(combo);
    |   JButton button = new JButton("Buy");
    |   frame.getContentPane().add(button, BorderLayout.EAST);
    |                                                                      
    |   //do something when user presses button
    |   button.addActionListener(new ActionListener() {
    |   |   public void actionPerformed(ActionEvent event) {
    |   |   |   String item = (String) combo.getSelectedItem();
    |   |   |   try {
    |   |   |   |   int order = stub.order(item);
    |   |   |   |   JOptionPane.showMessageDialog(frame, "order number " +
    |   |   |   |       order, "Confirmation", JOptionPane.INFORMATION_MESSAGE);
    |   |   |   } catch (Exception ex) { ex.printStackTrace(); }
    |   |   }
    |   });
    |                                                                      
    |   //exit on frame close
    |   frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    |                                                                      
    |   //pack and display
    |   frame.pack();
    |   frame.setVisible(true);
    }
                                                                           
}

The Shop Implementation

The shop server provides a very minimal implementation of the methods in the remote interface. This is deliberate since this example is meant to illustrate the use of digital signature handlers and not how to write a shopping application.

package signature;
                                                                           
import java.io.InputStream;
import java.io.IOException;
                                                                           
import java.util.Map;
import java.util.HashMap;
import java.util.ArrayList;
                                                                           
import java.rmi.RemoteException;
                                                                           
import javax.servlet.ServletException;
                                                                           
import javax.xml.rpc.handler.HandlerInfo;
                                                                           
import java.security.KeyStore;
import java.security.PrivateKey;
import java.security.cert.X509Certificate;
                                                                           
import com.sssw.jbroker.web.security.XMLSignatureProperties;
                                                                           
public class ShopImpl extends Shop_ServiceSkeleton
{
    public void init() throws ServletException
    {
    |   super.init();
    |                                                                      
    |   try {
    |   |                                                                  
    |   |   //set this property to sign outgoing messages
    |   |   _setProperty(XMLSignatureProperties.SIGN_OUTGOING, Boolean.TRUE);
    |   |                                                                  
    |   |   //set this property to validate incoming messages
    |   |   _setProperty(XMLSignatureProperties.VALIDATE_INCOMING, Boolean.TRUE);
    |   |                                                                  
    |   |   //get signature information from key store
    |   |   char[] secret = "secret".toCharArray();
    |   |   KeyStore ks = KeyStore.getInstance("JKS");
    |   |   ClassLoader loader = Thread.currentThread().getContextClassLoader();
    |   |   InputStream is = loader.getResourceAsStream("keystore.jks");
    |   |   if (is == null)
    |   |       throw new IOException("failed to load key store resource");
    |   |   ks.load(is, secret);
    |   |   is.close();
    |   |                                                                  
    |   |                                                                  
    |   |   //signature method
    |   |   _setProperty(XMLSignatureProperties.SIGNATURE_METHOD_URI, 
    |   |       XMLSignatureProperties.ALGO_ID_SIGNATURE_DSA);
    |   |                                                                  
    |   |   //digest method         
    |   |   _setProperty(XMLSignatureProperties.DIGEST_URI, 
    |   |       XMLSignatureProperties.ALGO_ID_DIGEST_SHA1);
    |   |                                                                  
    |   |   //transforms
    |   |   String[] tsfms = new String[] { 
    |   |   |   XMLSignatureProperties.TRANSFORM_ENVELOPED_SIGNATURE, 
    |   |   };
    |   |   _setProperty(XMLSignatureProperties.TRANSFORM_URIS, tsfms);
    |   |                                                                  
    |   |   //canonicalization method
    |   |   _setProperty(XMLSignatureProperties.CANONICALIZATION_METHOD_URI,
    |   |       XMLSignatureProperties.ALGO_ID_C14N_OMIT_COMMENTS);
    |   |                                                                  
    |   |   //key info certificate
    |   |   X509Certificate cert = (X509Certificate) ks.getCertificate("test");
    |   |   _setProperty(XMLSignatureProperties.CERTIFICATE, cert.getEncoded());
    |   |                                                                  
    |   |   //signing key
    |   |   PrivateKey privateKey = (PrivateKey) ks.getKey("test", secret);
    |   |   byte[] prkba = privateKey.getEncoded();
    |   |   _setProperty(XMLSignatureProperties.PRIVATE_KEY, prkba);
    |   |                                                                  
    |   } catch (Exception ex) {
    |   |   throw new javax.servlet.ServletException(ex.getMessage());
    |   }
    |                                                                      
    }
                                                                           
    public float price(String item)
        throws NoSuchItemException, RemoteException
    {
    |   String price = (String) _inventory.get(item);
    |   if (price == null) throw new NoSuchItemException(item);
    |   return Float.valueOf(price).floatValue();
    }
                                                                           
    public String[] items()
        throws RemoteException
    {
    |   String[] items = new String[_inventory.size()];
    |   return (String[]) _inventory.keySet().toArray(items);
    }
                                                                           
    public int order(String item)
        throws NoSuchItemException, RemoteException
    {
    |   return _orderNo++;
    }
                                                                           
    private int _orderNo;
                                                                           
    private static Map _inventory = new HashMap();
                                                                           
    static {
    |   _inventory.put("milk", "2.19");
    |   _inventory.put("apple", "1.19");
    |   _inventory.put("juice", "2.49");
    |   _inventory.put("candy", "1.49");
    |   _inventory.put("soap", "1.79");
    }
}

The most interesting part of the server is the init method, which sets properties for digital signature such as signature method, digest URI, etc. Once the properties are set the runtime adds a signature to the outgoing SOAP message (i.e. the response). It also validates the signature on incoming request SOAP messages.

Note that if you already have a handler chain, setting the XML digital signature property will cause this handler chain to be modified. On the client, the handler that signs the message will always be the last handler that gets to run. For validation of response messages coming back to the client, the signature handler will run first. On the server side things are reversed, i.e. the validation handler runs first on incoming messages and last when dealing with the response message.

Running the Example

In order to run the example, a couple of things need to be present in your environment:

If you're using JDK 1.2 or 1.3, you also need to modify the build script to include JCE on the CLASSPATHhere.

If you're using JDK 1.4 you need to ensure that a good version of Xalan is copied into the jre/lib/endorsed directory. Xalan 2.2.0 or later is known to work with the XML Signature toolkit. Please refer to the README for additional instructions on compiling, deploying and running the example.



Copyright © 2003, 2004 Novell, Inc. All rights reserved. Copyright © 2001, 2002, 2003 SilverStream Software, LLC. All rights reserved.