The XML Signature Example

This example illustrates how to add digital signature to the SOAP request which goes out from the client and how to verify a signature is verified 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/Skeleton. Please refer to com.sssw.jbroker.web.security.XMLSignatureProperties interface for more information on what all properties can be set and what are there types.

1 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.

2 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);
    }
                                                                           
}

3 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 namely the response. It also validates the signature on incoming request SOAP messages.

4 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 CLASSPATH. You can download JCE from Sun's Java Site.

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.
Copyright © 2000-2003, Novell, Inc. All rights reserved.