Safeguard your XML-based messages

Create secure Web services with Apache XML Security

Web services are here to stay, but if you are like most software developers, you worry about the plaintext SOAP (Simple Object Access Protocol) messages being exchanged over the Web. Web services security is a hot topic today because the success of this exciting technology hinges directly upon how secure we can make it. To that end, the World Wide Web Consortium (W3C) has defined the XML Signature and XML Encryption specifications for digitally signing and encrypting XML-based communication messages, such as the SOAP messages used in Web services. Furthermore, companies such as IBM, Microsoft, and VeriSign have partnered to provide additional specifications, such as WS-Security (Web Services Security), that build upon these W3C specifications. And who hasn't heard of the Liberty Alliance Project, a consortium of companies led by Sun Microsystems to provide a standards-based single sign-on solution to Web services? In the midst of all these initiatives lies the Apache XML Security project, an open source project that currently implements the W3C XML Signature specification and will soon support the XML Encryption specification. This article serves as a tutorial to get you up to speed with this outstanding implementation.

Introducing the Apache XML Security project

The Institute for Data Communications Systems at the University of Siegen in Germany donated the XML Security project to the Apache Foundation in September 2001. As of this writing, the latest version is 1.0.4, which is the version I discuss in this article—version 1.0.5 is currently in testing and available for download from Apache. Once you download the zipped binary distribution from Apache, unzip it in your root directory (for example, C:) in a directory called xml-security-1_0_4. (yes, these instructions have a Windows slant). The Apache XML Security project requires the Java Cryptography Extensions (JCE) library, which is not included with the distribution due to US export restrictions on cryptography. I obtained my copy, jce_1_2_2.jar, from Sun's Website and placed it in the ext directory under my Java runtime (that is, the directory C:\jdk1.3\jre\lib\ext). You will also need a stable version of Xalan (2.2.0 or later), which you can also download from Apache. Ensure xalan.jar is either in your classpath or in the ext directory along with the JCE JAR. You are now ready to start signing your XML!

Note: I use J2SE (Java 2 Platform, Standard Edition) 1.3, since my client originally required that. For those of you using J2SE 1.4, which comes bundled with a beta version of Xalan, you have one more installation step: You must put the xalan.jar into a special directory in your JDK—j2sdk1.4.0/jre/lib/endorsed/xalan.jar. If you installed an out-of-the-box J2SE 1.4 (for example, on Windows 2000), the endorsed directory does not exist; you'll have to create it by hand. Putting this JAR in another location like lib/ext will not work. For more on that issue, check the Unofficial JAXP (Java API for XML Processing) FAQ. Why did Sun include a beta version of Xalan in J2SE 1.4? I have no idea.

Secure your Web services

As I mentioned previously, Web services are based upon exchanging SOAP messages, which are clear text XML messages. While clear text XML-based message exchange is fine for amateur applications, it proves unacceptable for real-world business applications that deal with sensitive data, such as your credit card number, mother's maiden name, or social security number. It goes without saying that these messages must be secured. Security in the context of message exchange between two or more parties typically implies that each message sent and received exhibits the following four characteristics: authenticity, data integrity, nonrepudiation, and privacy, or confidentiality. That definition of secure message exchange holds true even for digital messages sent over the network. For such messages, digital signatures provide the first three of the four characteristics (please see the sidebar, "What Is a Digital Signature?," for more details). Data encryption provides the fourth. As mentioned above, the Apache XML Security project is an implementation of the W3C's XML Signature specification and hence can provide authenticity, integrity, and nonrepudiation to your SOAP-based Web services.

A real-world example of Apache XML Security

To make this tutorial slightly more interesting, I discuss the Apache XML Security library in the context of the Apache Axis project. Axis is Apache's next-generation SOAP implementation and has an extremely extensible architecture (see Resources for more links and articles about Axis). Unlike the previous Apache SOAP implementation, Axis allows you to get into the engine and extend the SOAP (that is, message) processing with your own custom code in the form of handlers. I exploited that exact feature to create a custom handler that digitally signs the SOAP request message from the client just before it hits the wire, and then verifies and removes the signature on the server side. Similarly, when the server sends back a SOAP response, the handler signs it on the sever side and verifies it on the client side. The figure below shows the flow of messages between the client and the server.

Message flow between the client and the server. Click on thumbnail to view full-size image.

The handler uses Apache XML Security to sign the SOAP message and later verify the signature. Obviously, in this scenario, both the client and server use the Axis SOAP engine, but the client does not have to use Axis. The digital signature XML that Apache XML Security creates complies with W3C's XML Signature specification and hence can be consumed and verified by any compliant client.

The handler's mechanics and configuration reach beyond this article's scope. However, we will certainly look at the Apache XML Security-related code within it.

Canonicalization: What is it and why is it necessary?

Although not directly related to our example, XML Canonicalization is an important concept to grasp in the context of XML signatures. Consider the following two XML fragments:

Fragment 1

<book>
   <Author>Joe</Author>
   <ISBN value="12300093456"/>
</book>

Fragment 2

<book>
   <Author    >Joe</Author>
   <ISBN value="12300093456"/>
</book>

The above two XML fragments differ only in that the second fragment's Author start element has a series of whitespaces. Logically the two fragments remain equivalent. However, the digital signature for the above fragments would indicate otherwise, because digital signatures rely on the data's physical representation, not its logical meaning. The above two fragments are just two examples of logically equivalent XML. Since XML is whitespace agnostic, an infinite amount of such variations are available. For digital signatures to be more useful than hassle, there must be some way that we can account for such variations (that result in logically equivalent XML). XML Canonicalization addresses that need by resolving issues with enveloping and resulting namespace inclusions. Apache XML Security supports both inclusive and exclusive XML Canonicalization. Listing 1 shows a sample program that canonicalizes the XML Fragment 1 above:

Listing 1. CanonicalizationExample.java

import java.io.ByteArrayInputStream;
import org.w3c.dom.*;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import org.apache.xml.security.c14n.Canonicalizer;
public class CanonicalizationExample 
{
   static String input = "<book>\n\t<Author>Joe</Author>
     \n\t<ISBN value=\"12300093456\"/>\n</book>";
   public static void main(String args[]) throws Exception 
   {
      org.apache.xml.security.Init.init();
      DocumentBuilderFactory dfactory = DocumentBuilderFactory.newInstance();
      dfactory.setNamespaceAware(true);
      dfactory.setValidating(true);
      DocumentBuilder documentBuilder = dfactory.newDocumentBuilder();
      // This is to throw away all validation warnings
      documentBuilder
         .setErrorHandler(new org.apache.xml.security.utils
            .IgnoreAllErrorHandler());
      byte inputBytes[] = input.getBytes();
      Document doc = documentBuilder.parse(new ByteArrayInputStream(inputBytes));
      
      Canonicalizer c14n = Canonicalizer.getInstance(
        "http://www.w3.org/TR/2001/REC-xml-c14n-20010315#WithComments");
      byte outputBytes[] = c14n.canonicalizeSubtree(doc);
      System.out.println("Input (Uncanonicalized) Document\n" + input);
      System.out.println("\nOutput (Canonicalized) Document\n" + new String
        (outputBytes));
   }
}

Compile this example using the following command line:

javac -classpath C:\xml-security-1_0_4\build\xmlsec.jar;C:\xml-security-
   1_0_4\libs\xercesImpl.jar;C:\xml-security-1_0_4\libs\xml-apis.jar;C:\xml-
   security-1_0_4\libs\;C:\xml-security-1_0_4\libs\xmlParserAPIs.jar;C:\xml-
   security-1_0_4\libs\log4j-1.2.5.jar CanonicalizationExample.java

Run the example with the following command line:

java -classpath .;C:\xml-security-1_0_4\build\xmlsec.jar;C:\xml-security-
   1_0_4\libs\xercesImpl.jar;C:\xml-security-1_0_4\libs\xml-apis.jar;C:\xml-
   security-1_0_4\libs\;C:\xml-security-1_0_4\libs\xmlParserAPIs.jar;C:\xml-
   security-1_0_4\libs\log4j-1.2.5.jar;C:\xalan-j_2_4_0\bin\xalan.jar 
   CanonicalizationExample

Remember to substitute the directory where you unzipped the Apache XML Security binary distribution with C:\xml-security-1_0_4 in the above two command lines.

The input string in the above listing initializes to Fragment 1. The canonicalized output is shown below:

<book>
        <Author>Joe</Author>
        <ISBN value="12300093456"></ISBN>
</book>

You will notice that the self-closed ISBN tag in the fragment has expanded. One of the canonicalization rules requires the expansion of all such self-closing tags. The rules of XML Canonicalization are beyond the limited scope of this article; refer to Resources for links to more detailed discussions.

Now let's quickly look at the Apache XML Security-specific code in the above listing. Before you can invoke any of the Apache XML Security library's services, you must initialize it as shown below:

org.apache.xml.security.Init.init();

Once the library initializes, obtain a canonicalizer object by calling the static getInstance() method on the Canonicalizer class. Tell the getInstance() method what type of canonicalizer to return by passing in the appropriate URI constant. Valid values are http://www.w3.org/TR/2001/REC-xml-c14n-20010315, http://www.w3.org/TR/2001/REC-xml-c14n-20010315#WithComments, http://www.w3.org/2001/10/xml-exc-c14n#, and http://www.w3.org/2001/10/xml-exc-c14n#WithComments. The actual canonicalization is performed by calling the canonicalizeSubtree() method on the canonicalizer object, thereby passing in the DOM (Document Object Model) document. This method returns the canonicalized document in the form of a byte array.

Most of you will never directly canonicalize XML documents because the XML digital signature library that you use (such as Apache XML Security) will perform the canonicalization during the signature-creation process. Regardless, realizing that canonicalization happens under the covers is important.

Sign a SOAP message

Now that we've taken care of the basics, let's actually sign some SOAP messages. Assume we have access to a simple Web service that retrieves stock quotes from the Internet and sends back a JavaBean (serialized in the form of XML, of course) containing various pieces of information such as the current price, day's high and low, or 52-week high and low. Here's a sample SOAP message that a client could send to the Web service to retrieve stock quote information about IBM:

<?xml version="1.0" encoding="UTF-8"?>
<soapenv:Envelope xmlns:soapenv="
  http://schemas.xmlsoap.org/soap/envelope/" 
  xmlns:xsd="http://www.w3.org/2001/XMLSchema" 
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
 <soapenv:Body>
  <ns1:getPrice soapenv:encodingStyle=
    "http://schemas.xmlsoap.org/soap/encoding/" xmlns:ns1="urn:quote">
   <symbol xsi:type="xsd:string">IBM</symbol>
  </ns1:getPrice>
 </soapenv:Body>
</soapenv:Envelope>

And a typical response from the Web service could be:

<?xml version="1.0" encoding="UTF-8"?>
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" 
  xmlns:xsd="http://www.w3.org/2001/XMLSchema" 
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
 <soapenv:Body>
  <ns1:getPriceResponse soapenv:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" xmlns:ns1="urn:quote">
   <getPriceReturn href="#id0"/>
  </ns1:getPriceResponse>
  <multiRef id="id0" soapenc:root="0" 
    soapenv:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" 
    xsi:type="ns2:NasdaqQuote" xmlns:soapenc=
   "http://schemas.xmlsoap.org/soap/encoding/" xmlns:ns2="urn:NasdaqQuotes-types">
   <todaysHigh xsi:type="xsd:double">68.0</todaysHigh>
   <fiftyTwoWeekHigh xsi:type="xsd:double">126.39</fiftyTwoWeekHigh>
   <tradingStatus xsi:type="xsd:string">ACTIVE</tradingStatus>
   <totalShares xsi:type="xsd:long">1694323000</totalShares>
   <symbol xsi:type="xsd:string">IBM</symbol>
   <market xsi:type="xsd:string">NYSE</market>
   <errorText xsi:type="xsd:string"></errorText>
   <issuerWebSite xsi:type="xsd:string">http://www.ibm.com</issuerWebSite>
   <todaysLow xsi:type="xsd:double">66.58</todaysLow>
   <fiftyTwoWeekLow xsi:type="xsd:double">54.01</fiftyTwoWeekLow>
   <shareVolume xsi:type="xsd:long">8958000</shareVolume>
   <netPercentChange xsi:type="xsd:string">6.69%</netPercentChange>
   <lastSalePrice xsi:type="xsd:double">67.66</lastSalePrice>
   <netPriceChange xsi:type="xsd:double">4.24</netPriceChange>
   <previousClosePrice xsi:type="xsd:double">63.42</previousClosePrice>
  </multiRef>
 </soapenv:Body>
</soapenv:Envelope>

Now let's look at the client request message, shown below, once we install the custom Axis handler on the client side:

<?xml version="1.0" encoding="UTF-8"?>
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
 <soapenv:Body xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  <ns1:getPrice soapenv:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" xmlns:ns1="urn:quote" xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
   <symbol xmlns:ns1="urn:quote" xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" 
   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="xsd:string">IBM</symbol>
  </ns1:getPrice>
 </soapenv:Body>
<soapenv:Header xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" 
   xmlns:xsd="http://www.w3.org/2001/XMLSchema" 
   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"><ds:Signature 
   xmlns:ds="http://www.w3.org/2000/09/xmldsig#" 
   xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" 
   xmlns:xsd="http://www.w3.org/2001/XMLSchema" 
   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<ds:SignedInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#" 
   xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" 
   xmlns:xsd="http://www.w3.org/2001/XMLSchema" 
   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<ds:CanonicalizationMethod Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-
   20010315" xmlns:ds="http://www.w3.org/2000/09/xmldsig#" 
   xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" 
   xmlns:xsd="http://www.w3.org/2001/XMLSchema" 
   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"/>
<ds:SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#dsa-sha1" 
   xmlns:ds="http://www.w3.org/2000/09/xmldsig#" 
   xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" 
   xmlns:xsd="http://www.w3.org/2001/XMLSchema" 
   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"/>
<ds:Reference URI="" xmlns:ds="http://www.w3.org/2000/09/xmldsig#" 
   xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" 
   xmlns:xsd="http://www.w3.org/2001/XMLSchema" 
   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<ds:Transforms xmlns:ds="http://www.w3.org/2000/09/xmldsig#" 
   xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" 
   xmlns:xsd="http://www.w3.org/2001/XMLSchema" 
   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<ds:Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-
   signature" xmlns:ds="http://www.w3.org/2000/09/xmldsig#" 
   xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" 
   xmlns:xsd="http://www.w3.org/2001/XMLSchema" 
   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"/>
<ds:Transform Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-
   20010315#WithComments" xmlns:ds="http://www.w3.org/2000/09/xmldsig#" 
   xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" 
   xmlns:xsd="http://www.w3.org/2001/XMLSchema" 
   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"/>
</ds:Transforms>
<ds:DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1" 
   xmlns:ds="http://www.w3.org/2000/09/xmldsig#" 
   xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" 
   xmlns:xsd="http://www.w3.org/2001/XMLSchema" 
   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"/>
<ds:DigestValue xmlns:ds="http://www.w3.org/2000/09/xmldsig#" 
   xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" 
   xmlns:xsd="http://www.w3.org/2001/XMLSchema" 
   xmlns:xsi="http://www.w3.org/2001/XMLSchema-
   instance">w1cJdTvtONxK2NTwV+uwu34ahx8=</ds:DigestValue>
</ds:Reference>
</ds:SignedInfo>
<ds:SignatureValue xmlns:ds="http://www.w3.org/2000/09/xmldsig#"
   xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" 
   xmlns:xsd="http://www.w3.org/2001/XMLSchema" 
   xmlns:xsi="http://www.w3.org/2001/XMLSchema-
   instance">kxv7mRwjSbCkfQ7Zv9pu/DFqPbALAT4755Tz8AoHAIe74TTZF5c3Vw==</ds:Sig
   natureValue>
<ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#" 
   xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" 
   xmlns:xsd="http://www.w3.org/2001/XMLSchema" 
   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<ds:X509Data xmlns:ds="http://www.w3.org/2000/09/xmldsig#" 
   xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" 
   xmlns:xsd="http://www.w3.org/2001/XMLSchema" 
   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<ds:X509Certificate xmlns:ds="http://www.w3.org/2000/09/xmldsig#" 
   xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" 
   xmlns:xsd="http://www.w3.org/2001/XMLSchema" 
   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
MIIC9jCCArQCBDruqiowCwYHKoZIzjgEAwUAMGExCzAJBgNVBAYTAkRFMR0wGwYDVQQKExRVbml2
ZXJzaXR5IG9mIFNpZWdlbjEQMA4GA1UECxMHRkIxMk5VRTEhMB8GA1UEAxMYQ2hyaXN0aWFuIEdl
dWVyLVBvbGxtYW5uMB4XDTAxMDUwMTEyMjA1OFoXDTA2MTAyMjEyMjA1OFowYTELMAkGA1UEBhMC
REUxHTAbBgNVBAoTFFVuaXZlcnNpdHkgb2YgU2llZ2VuMRAwDgYDVQQLEwdGQjEyTlVFMSEwHwYD
VQQDExhDaHJpc3RpYW4gR2V1ZXItUG9sbG1hbm4wggG3MIIBLAYHKoZIzjgEATCCAR8CgYEA/X9T
gR11EilS30qcLuzk5/YRt1I870QAwx4/gLZRJmlFXUAiUftZPY1Y+r/F9bow9subVWzXgTuAHTRv
8mZgt2uZUKWkn5/oBHsQIsJPu6nX/rfGG/g7V+fGqKYVDwT7g/bTxR7DAjVUE1oWkTL2dfOuK2HX
Ku/yIgMZndFIAccCFQCXYFCPFSMLzLKSuYKi64QL8Fgc9QKBgQD34aCF1ps93su8q1w2uFe5eZSv
u/o66oL5V0wLPQeCZ1FZV4661FlP5nEHEIGAtEkWcSPoTCgWE7fPCTKMyKbhPBZ6i1R8jSjgo64e
K7OmdZFuo38L+iE1YvH7YnoBJDvMpPG+qFGQiaiD3+Fa5Z8GkotmXoB7VSVkAUw7/s9JKgOBhAAC
gYASWfn+G1k/nWntj9jX7Nk5JKaiLZ9BLR16eJJxqff33THLfdGs98Xmh2oRWZVh9PMV8oTP3hpR
cRipjZUZVEIqsBlOGTVLCg4H5TJ81JWOiprh+mkhClNqUr8l5Hu7FBSvQB6inryeva7j0aKNiIvK
8vfHTiUZpnyNRhkveBlM0jALBgcqhkjOOAQDBQADLwAwLAIUPDd/UmB9GeHqvGjny30Bvjt0AkUC
FA9ab72kKuB5geYGeckbBrcgPnZk
</ds:X509Certificate>
</ds:X509Data>
<ds:KeyValue xmlns:ds="http://www.w3.org/2000/09/xmldsig#" 
   xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" 
   xmlns:xsd="http://www.w3.org/2001/XMLSchema" 
   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<ds:DSAKeyValue xmlns:ds="http://www.w3.org/2000/09/xmldsig#" 
   xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" 
   xmlns:xsd="http://www.w3.org/2001/XMLSchema" 
   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<ds:P xmlns:ds="http://www.w3.org/2000/09/xmldsig#" 
   xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" 
   xmlns:xsd="http://www.w3.org/2001/XMLSchema" 
   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
/X9TgR11EilS30qcLuzk5/YRt1I870QAwx4/gLZRJmlFXUAiUftZPY1Y+r/F9bow9subVWzXgTuA
HTRv8mZgt2uZUKWkn5/oBHsQIsJPu6nX/rfGG/g7V+fGqKYVDwT7g/bTxR7DAjVUE1oWkTL2dfOu
K2HXKu/yIgMZndFIAcc=
</ds:P>
<ds:Q xmlns:ds="http://www.w3.org/2000/09/xmldsig#" 
   xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" 
   xmlns:xsd="http://www.w3.org/2001/XMLSchema" 
   xmlns:xsi="http://www.w3.org/2001/XMLSchema-
   instance">l2BQjxUjC8yykrmCouuEC/BYHPU=</ds:Q>
<ds:G xmlns:ds="http://www.w3.org/2000/09/xmldsig#" 
   xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" 
   xmlns:xsd="http://www.w3.org/2001/XMLSchema" 
   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
9+GghdabPd7LvKtcNrhXuXmUr7v6OuqC+VdMCz0HgmdRWVeOutRZT+ZxBxCBgLRJFnEj6EwoFhO3
zwkyjMim4TwWeotUfI0o4KOuHiuzpnWRbqN/C/ohNWLx+2J6ASQ7zKTxvqhRkImog9/hWuWfBpKL
Zl6Ae1UlZAFMO/7PSSo=
</ds:G>
<ds:Y xmlns:ds="http://www.w3.org/2000/09/xmldsig#" 
   xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" 
   xmlns:xsd="http://www.w3.org/2001/XMLSchema" 
   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
Eln5/htZP51p7Y/Y1+zZOSSmoi2fQS0deniScan3990xy33RrPfF5odqEVmVYfTzFfKEz94aUXEY
qY2VGVRCKrAZThk1SwoOB+UyfNSVjoqa4fppIQpTalK/JeR7uxQUr0Aeop68nr2u49GijYiLyvL3
x04lGaZ8jUYZL3gZTNI=
</ds:Y>
</ds:DSAKeyValue>
</ds:KeyValue>
</ds:KeyInfo>
</ds:Signature></soapenv:Header></soapenv:Envelope>

As discussed above, the custom handler signs the SOAP message using Apache XML Security. Also note that the handler inserts the XML corresponding to the digital signature in the SOAP message's header section (by the way, this is similar to the WS-Security specification proposal from IBM, Microsoft, and VeriSign). I will not go into details about the XML Signature elements as Ray Djajadinata covers this in "Yes, You Can Secure Your Web Services Documents, Part 2" (JavaWorld, October 2002). The response message from the server (with the custom handler installed) resembles the request message; that is, the SOAP body is canonicalized and the digital signature is in the header.

Now let's sign a SOAP message using Apache XML Security. Listing 2 shows how:

Listing 2. SignExample.java

import java.io.*;
import java.lang.reflect.*;
import java.security.*;
import java.security.cert.*;
import java.util.*;
import javax.xml.transform.TransformerException;
import org.apache.xpath.XPathAPI;
import org.w3c.dom.*;
import org.apache.xml.security.algorithms.MessageDigestAlgorithm;
import org.apache.xml.security.c14n.*;
import org.apache.xml.security.exceptions.XMLSecurityException;
import org.apache.xml.security.signature.*;
import org.apache.xml.security.keys.*;
import org.apache.xml.security.keys.content.*;
import org.apache.xml.security.keys.content.x509.*;
import org.apache.xml.security.keys.keyresolver.*;
import org.apache.xml.security.keys.storage.*;
import org.apache.xml.security.keys.storage.implementations.*;
import org.apache.xml.security.utils.*;
import org.apache.xml.security.transforms.*;
import org.apache.xml.security.Init;
import  org.apache.xml.serialize.*;
public class SignExample
{
   public static void main(String unused[]) throws Exception 
   {
      // Initialize the library
      org.apache.xml.security.Init.init();
      // The file from which we will load the request SOAP message
      String fileName = "Request.xml";
      // Store the signed request here
      String signatureFileName = "SignedRequest.xml";
      // Keystore parameters
      String keystoreType = "JKS";
      String keystoreFile = "C:/xml-security-1_0_4/data/org/apache/xml/security/
        samples/input/keystore.jks";
      String keystorePass = "xmlsecurity";
      String privateKeyAlias = "test";
      String privateKeyPass = "xmlsecurity";
      String certificateAlias = "test";      
      // Load the keystore
      KeyStore ks = KeyStore.getInstance(keystoreType);
      FileInputStream fis = new FileInputStream(keystoreFile);
      ks.load(fis, keystorePass.toCharArray());
      // And get the private key that will be used to sign the request
      PrivateKey privateKey = (PrivateKey) ks.getKey(privateKeyAlias, 
        privateKeyPass.toCharArray());
      // Load the SOAP request
      javax.xml.parsers.DocumentBuilderFactory dbf =
         javax.xml.parsers.DocumentBuilderFactory.newInstance();
      dbf.setNamespaceAware(true);
      dbf.setAttribute("http://xml.org/sax/features/namespaces", Boolean.TRUE);
      javax.xml.parsers.DocumentBuilder db = dbf.newDocumentBuilder();
      db.setErrorHandler(new org.apache.xml.security.utils.IgnoreAllErrorHandler());
      org.w3c.dom.Document doc = db.parse(new java.io.FileInputStream(new 
        File(fileName)));
      // Look for the SOAP header
      Element headerElement = null;
      NodeList nodes = doc.getElementsByTagNameNS
        ("http://schemas.xmlsoap.org/soap/envelope/","Header");
      if(nodes.getLength() == 0)
      {
         System.out.println("Adding a SOAP Header Element");
         headerElement = doc.createElementNS
           ("http://schemas.xmlsoap.org/soap/envelope/","Header");
         nodes = doc.getElementsByTagNameNS
          ("http://schemas.xmlsoap.org/soap/envelope/","Envelope");
         if(nodes != null)
         {
            Element envelopeElement = (Element)nodes.item(0);
            headerElement.setPrefix(envelopeElement.getPrefix());
            envelopeElement.appendChild(headerElement);
         }
      }
      else
      {
         System.out.println("Found " + nodes.getLength() + " SOAP Header 
           elements.");
         headerElement = (Element)nodes.item(0);
      }
   
      // Create an XMLSignature instance       
      XMLSignature sig = new XMLSignature(doc,"",XMLSignature.ALGO_ID_SIGNATURE_DSA);
      headerElement.appendChild(sig.getElement());
      // Specify the transforms
      Transforms transforms = new Transforms(doc);
      transforms.addTransform(Transforms.TRANSFORM_ENVELOPED_SIGNATURE);
      transforms.addTransform(Transforms.TRANSFORM_C14N_WITH_COMMENTS);
      sig.addDocument("", transforms, org.apache.xml.security.utils.Constants.
        ALGO_ID_DIGEST_SHA1);
      // Add the certificate and public key information from the keystore;
      // this will be needed by the verifier
      X509Certificate cert = (X509Certificate) ks.getCertificate
       (certificateAlias);
      sig.addKeyInfo(cert);
      sig.addKeyInfo(cert.getPublicKey());
      System.out.println("Starting to sign SOAP Request");
      sig.sign(privateKey);
      System.out.println("Finished signing");
      // Dump the signed request to file
      FileOutputStream f = new FileOutputStream(new File(signatureFileName));
      XMLUtils.outputDOMc14nWithComments(doc, f);
      f.close();
      System.out.println("Wrote signature to " + signatureFileName);
   }
}

Copy the SOAP request XML shown below into a file called Request.xml in the same directory:

<?xml version="1.0" encoding="UTF-8"?>
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" 
  xmlns:xsd="http://www.w3.org/2001/XMLSchema" 
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
 <soapenv:Body>
  <ns1:getPrice soapenv:encodingStyle=
    "http://schemas.xmlsoap.org/soap/encoding/" xmlns:ns1="urn:quote">
   <symbol xsi:type="xsd:string">IBM</symbol>
  </ns1:getPrice>
 </soapenv:Body>
</soapenv:Envelope>

Now compile and run this example just as you did CanonicalizeExample.java. Look at the resulting SignedRequest.xml file. Pay particular attention to the newly inserted SOAP header section; the digital signature is inserted there. Now let's dissect the source code. Here's what we did:

  1. Initialized the Apache XML Security library.
  2. Loaded the keystore. This keystore is created using the keytool utility that ships with the JDK. For more information on keytool, refer to your JDK documentation.
  3. Loaded and parsed the SOAP XML request from Request.xml. Obviously, in the case of the Axis handler, we obtained the SOAP request from the Axis engine.
  4. Looked for a SOAP header section. If none was found, we created a new one.
  5. Created an instance of the class XMLSignature, passing in the parsed SOAP document. We later used the DSA (Digital Signature Algorithm) to sign the SOAP message.
  6. Added the necessary transforms, which also appeared in the signed XML under the Transforms element. We specified two transforms: one specifying an enveloped signature and the other specifying the canonicalization method used prior to signing.
  7. Specified the certificate and public key to use for signing. This information will be included in the signed XML's KeyInfo section.
  8. Signed the SOAP message by calling the sign() method on the XMLSignature class instance. This method takes the private key to use for signing as a parameter.
  9. Dumped the signed request to a file called SignedRequest.xml to use in our next section.

Verify a signed SOAP message

A signed message's recipient verifies the signature to ensure the message has not been tampered with during transit as well as to authenticate its origin. Listing 3 illustrates how to accomplish that task using Apache XML Security:

Listing 3. VerifyExample.java

import java.io.*;
import java.lang.reflect.*;
import java.security.PublicKey;
import java.security.cert.*;
import java.util.*;
import javax.xml.transform.TransformerException;
import org.apache.xpath.XPathAPI;
import org.w3c.dom.*;
import org.apache.xml.security.c14n.*;
import org.apache.xml.security.exceptions.XMLSecurityException;
import org.apache.xml.security.signature.*;
import org.apache.xml.security.keys.*;
import org.apache.xml.security.keys.content.*;
import org.apache.xml.security.keys.content.x509.*;
import org.apache.xml.security.keys.keyresolver.*;
import org.apache.xml.security.keys.storage.*;
import org.apache.xml.security.keys.storage.implementations.*;
import org.apache.xml.security.utils.*;
import org.apache.xml.security.Init;
public class VerifyExample 
{
   public static void main(String unused[]) 
   {
      org.apache.xml.security.Init.init();
      String signatureFileName = "SignedRequest.xml";
      javax.xml.parsers.DocumentBuilderFactory dbf =
      javax.xml.parsers.DocumentBuilderFactory.newInstance();
      dbf.setNamespaceAware(true);
      dbf.setAttribute("http://xml.org/sax/features/namespaces", Boolean.TRUE);
      try
      {
         org.apache.xml.security.Init.init();
         File f = new File(signatureFileName);
         System.out.println("Verifying " + signatureFileName);
         javax.xml.parsers.DocumentBuilder db = dbf.newDocumentBuilder();
         db.setErrorHandler(new org.apache.xml.security.utils.IgnoreAllErrorHandler());
         org.w3c.dom.Document doc = db.parse(new java.io.FileInputStream(f));
         Element sigElement = null;
         NodeList nodes = doc.getElementsByTagNameNS(
           org.apache.xml.security.utils.Constants.SignatureSpecNS,"Signature");
         if(nodes.getLength() != 0)
         {
            System.out.println("Found " + nodes.getLength() + " Signature 
              elements.");
            sigElement = (Element)nodes.item(0);
            XMLSignature signature = new XMLSignature(sigElement,"");
            KeyInfo ki = signature.getKeyInfo();
            if(ki != null)
            {
               if(ki.containsX509Data())
               {
                  System.out.println("Could find a X509Data element in the 
                    KeyInfo");
               }
               X509Certificate cert = signature.getKeyInfo().getX509Certificate();
               if(cert != null)
               {
                  System.out.println("The XML signature is " + 
                     (signature.checkSignatureValue(cert) ? "valid (good)" : 
                     "invalid !!!!! (bad)"));
               }
               else
               {
                  System.out.println("Did not find a Certificate");
                  PublicKey pk = signature.getKeyInfo().getPublicKey();
                  if(pk != null)
                  {
                     System.out.println("The XML signatur is " + 
                        (signature.checkSignatureValue(pk) ? "valid (good)" : 
                        "invalid !!!!! (bad)"));
                  }
                  else
                  {
                     System.out.println("Did not find a public key, so I can't 
                       check the signature");
                  }
               }
            }
            else
            {
               System.out.println("Did not find a KeyInfo");
            }
         }
      }
      catch(Exception e)
      {
         e.printStackTrace();
      }
   }
}

Compile and run this example just as you did the previous examples. Here is a step-by-step breakdown of the actions we completed:

  1. Initialized the Apache XML Security library.
  2. Loaded and parsed the SOAP XML request from SignedRequest.xml. Obviously, in the case of the Axis handler, we obtained the signed SOAP request from the Axis engine.
  3. Looked for a Signature element (in the namespace http://www.w3.org/2000/09/xmldsig#).
  4. If a Signature element was found, extracted the public key information, which would have been in the form of a public key value, a key name, or a digital certificate.
  5. Used the public key to verify the signature by calling the checkSignatureValue() method on the XMLSignature class instance, which was initialized from Step 3's Signature element.

Here's a question to consider: Did the Apache XML Security library really canonicalize the SOAP request before signing it? There's only one way to find out...let's experiment. Open SignedRequest.xml and insert a whitespace in the symbol element—that is, between the text symbol and the closing bracket >—and save your change. Run VerifyExample again. You should still get a message telling you the signature is valid.

Now open SignedRequest.xml and change IBM to IBN; save your change and run VerifyExample. This time, you should get a message telling you the signature is not valid. So the signature does seem resistant to changes that fail to modify the SOAP request's logical meaning, but detects changes inconsistent with the original logical meaning. Therefore, yes, the library did canonicalize the SOAP request prior to signing it, as we expected.

Start signing your SOAP messages

In this hands-on tutorial, I gave you a whirlwind tour of Apache XML Security and put it in the context of a real-world example by discussing a use of this library with Axis. By using Apache XML Security with Axis to sign SOAP messages, you gain three of the four secure messaging characteristics mentioned above: namely authentication of origin, nonrepudiation, and message integrity. To gain the fourth characteristic of privacy, you must use XML Encryption. Soon Apache XML Security will support that as well.

Also, keep an eye out for announcements related to Java Specification Requests (JSRs) 105 and 106, which the Java Community Process (JCP) is currently working on. The proposed final draft of JSR 105, XML Digital Signature APIs, will define a standard set of APIs for XML digital signatures services. And for those worried developers out there, yes, Apache is on JSR 105's expert group. JSR 106, XML Digital Encryption APIs, will define a standard set of APIs for XML digital encryption services.

Tarak Modi has been architecting scalable, high-performance, distributed applications for more than seven years and is currently a senior specialist with North Highland, a management and technology consulting company. His professional experience includes hardcore C++ and Java programming; working with Microsoft technologies such as COM (Component Object Model), MTS (Microsoft Transaction Server), and COM+; Java-based technologies including J2EE; and CORBA. He is also coauthor of Professional Java Web Services (Wrox Press, 2002; ISBN 1861003757). To find out more about him, visit his personal Website at http://www.tekNirvana.com.

Learn more about this topic

Join the discussion
Be the first to comment on this article. Our Commenting Policies
See more