Skip to main content

XML Signature with JSR-105 in Java SE 6

November 21, 2006

{cs.r.title}



XML Signature technology, specified in the W3C recommendation XML-Signature Syntax and Processing, is at the foundation for solutions of message-level security in SOA. The universally adopted OASIS standard WS-Security is built upon this technology (and XML encryption). JSR-105 standardizes XML Signature technology for the Java platform, and will be part of the forthcoming Java SE 6 release. This article provides an introduction to JSR-105 based on the release candidate version of SE 6.

Data Integrity and Message Authentication

The main purpose of XML digital signature is to ensure the integrity of data. RFC 2828, Internet Security Glossary, defines integrity as "the property that data has not been changed, destroyed, or lost in an unauthorized or accidental manner." Storing or transiting the data together with a checksum can achieve data integrity in this sense. Strictly speaking, XML Signature ensures more than such integrity. It provides support for what RFC 2828 refers to as message authentication, the property "given an authentication code/protected checksum, that tampering with both the data and checksum, so as to introduce changes while seemingly preserving integrity, are still detected."

Structure of the Signature Element

XML Signature, in essence, represents a digital signature with XML semantics. The following hierarchy captures elements and attributes at top levels and their structural relationship.

   <Signature ID?>
     <SignedInfo>
       <CanonicalizationMethod/>
       <SignatureMethod/>
       (<Reference URI? >
         (<Transforms>)?
         <DigestMethod>
         <DigestValue>
       </Reference>)+
     </SignedInfo>
     <SignatureValue>
    (<KeyInfo>)?
    (<Object ID?>)*
   </Signature>

In this example, ? denotes zero or one occurrence, + denotes one or more occurrences, and * denotes zero or more occurrences. Here all elements and attributes are defined in the namespace http://www.w3.org/2000/09/XMLdsig#.

Reference is the bridge (through the URI attribute) connecting the data objects to be signed with an XML signature. An application decides on the digest method for computing the digest value of a data object and includes both as part of the corresponding Reference element.

For digest methods, the W3C recommendation requires implementations to support SHA-1. Implementations usually support other interactive, one-way hash functions as well, such as SHA-256, SHA-512, and RIPEMD 160.

In reality, we seldom compute a digest value directly from the data object itself. Usually, an application needs to apply some transformations to the data object first. For example, we may use XPath to extract only the critical elements from an XML document for signing. Or we might want to sign an XML document after some transforms with XSLT. Such transformations are specified in the Transforms element, which contains an ordered list of Transforms capturing the transform algorithm and other related information, and is included in the corresponding Reference element as well.

SignedInfo is the element to which the digital signature algorithm actually applies. The algorithm is captured through the SignatureMethod element. The W3C recommendation requires implementations to support DSA_SHA1, RSA_SHA1, and HMAC_SHA1 (by JSR-105 notations). The first two are public-key-based, and HMAC is a symmetric key cryptography algorithm.

Signing Process

In XML Signature, the signing process includes two steps. First, it calculates the digest value of each and every data object to be signed, and includes the value as part of the corresponding Reference element. In the second step, it applies a digital signature algorithm with a cryptography key to the SignedInfo element generating the signature value, and includes it as part of the resulting Signature element.

The actual mechanism of this second step varies with the digital signature algorithm, or more generally, with the category of algorithm.

In practice, a digital signature algorithm never directly operates on the given XML format of SignedInfo directly. Instead, it operates on the octet streams (8 bits) converted from the physical representation of the SignedInfo element. To allow logically equivalent representations of the SignedInfo element, the conversion to octet streams is based on the canonical form of the SignedInfo element. Two W3C specifications, Canonical XML and Exclusive XML Canonicalization, defines XML canonicalization. The details of how to obtain the canonical form of a given XML document (or element) is fairly complex. But, in general, all that XML Signature applications need to do is to choose from four defined canonicalization methods based on their contexts: EXCLUSIVE, EXCLUSIVE_WITH_COMMENTS, INCLUSIVE, and INCLUSIVE_WITH_COMMENTS (listed by JSR-105 notations). The CanonicalizationMethod element in SignedInfo captures the choice.

Core Validation of XML Signature

The mechanism by which XML Signature ensures data integrity and message authentication is built upon the core validation process. It consists of two parts: Reference validation and Signature validation.

Reference validation verifies that each and every Reference element in an XML signature is valid. Here, the word "valid" means the following. After applying the same transforms to the corresponding data object (referred through the URI attribute in general cases), using the digest method specified in the Reference element, we should obtain an identical digest value as recorded in the Reference element. The digest value is the checksum we mentioned before. This validation provides the support for data integrity in XML Signature.

The actual mechanism of Signature validation depends on the specific signature algorithm (more precisely, the category of signature algorithms) referred in the SignedInfo element. Their common requirement is that the validating application must have possession of the corresponding cryptography key: the secret key with HMAC_SHA1 (or HMAC with other hash functions), or the public key with RSA_SHA1 and DSA_SHA1 (or RSA and DSA with other hash functions).

That References are part of the SignedInfo element is the structural mechanism for ensuring message authentication (which is a stronger security property than data integrity) in XML Signature.

Three Types of XML Signature

The W3C recommendation allows any digital data to be signed, and this includes an XML document, an XML element of a document, and the content of an XML element as particular cases.

When we talk about an XML signature, we are actually referring to an XML document, which contains the Signature (defined in the namespace http://www.w3.org/2000/09/XMLdsig#) as one element (which may be the root element). But the document may also contain other elements, among which the most important are, of course, the original data objects being signed.

Depending on how those data objects relate to the Signature element in an XML Signature document, we consider three different types of XML signatures.

  • Enveloping: The data objects are contained within the same XML document as the Signature element, and are further included in the Signature element (as sub-elements of Object, for example).

  • Enveloped: The data object is found within the same XML document as the Signature element, and actually includes the Signature as a sub-element.

  • Detached: The Signature refers to external network resources, or the data object is within the same XML document as the Signature element, but is a sibling element, or is a sub-element of its sibling element.

Some writings on XML Signature leave readers the impression that this is an exclusive classification of XML signatures; others implicitly suggest that, in enveloping or enveloped signatures, there is actually an Envelop element in the XML document containing the signature; and still others imply that for an enveloping signature, Signature is the root element of the XML document. These are all plainly wrong.

Object and KeyInfo

Before we proceed to introduce JSR-105, we need to briefly discuss two optional sub-elements of Signature: Object and KeyInfo.

The Object element in the W3C recommendation is a placeholder for an XML Signature application to carry information for several different purposes. On possibility is for an Object to contain the data object being signed for an enveloping signature. This is the scenario our example will work with.

XML Signature's KeyInfo element in can be used for signing applications to communicate information associated with the cryptographic (private) key so that validating applications can obtain the relevant (public) key. Our example will include several scenarios illustrating the use cases of KeyInfo.

Cryptography Key and Certificate

We are now ready to present our XML Signature example application.

Let us look at the following XML document, ./etc/invoice.xml:

<?XML version="1.0" encoding="UTF-8" standalone="no"?>
<invoice XMLns="http://www.company.com/accounting">
    <items>
        <item>
            <desc>Applied Cryptography</desc>
            <type>book</type>
            <unitprice>44.50</unitprice>
            <quantity>1</quantity>
        </item>
    </items>
    <creditcard>
        <number>123456789</number>
        <expiry>10/20/2009</expiry>
        <lastname>John</lastname>
        <firstname>Smith</firstname>
    </creditcard>
</invoice>

We plan to sign it with an XML signature and hope to use a signature method based on a public key.

Let us start by generating the cryptography key. For this, we can use the keytool utility that comes with the JDK. Move the command prompt to ./etc, and execute the following:

keytool -genkey -keysize 512 -sigalg DSA -dname "cn=Young Yang, ou=Architecture, o=Company, L=New York, ST=NY, c=US" -alias biz -keypass kp1234 -keystore bizkeystore -storepass sp1234 -validity 180

This creates the key store, named bizkeystore, in the working directory ./etc, and assigns it the password sp1234. It also generates a public/private key pair for the entity whose distinguished name has a common name of Young Yang, an organizational unit of Architecture, and other information. This commend uses the DSA key generation algorithm to create the public and private keys, both 512 bits long.

The above command further creates a self-signed certificate using the SHA1withDSA algorithm (DSA_SHA1 by JSR-105 notations) that includes the public key and the distinguished name information. This certificate will be valid for 180 days, and is associated with the private key in a key store entry referred to by the alias biz. The private key is assigned the password kp1234.

Our example includes a simple Java class, KeyStoreInfo, for printing to System.out the keys and certificates in a key store, and for applications to get from it the key pair, the private and public key matching criteria specified as input parameters. To try it out by printing out information contained in bizkeystore, readers may just run the Ant target ksInfo.

The following code snippet displays the method in KeyStoreInfo for retrieving a KeyPair:

public static KeyPair getKeyPair(String store, String sPass,
                                 String kPass, String alias)
           throws CertificateException,
               IOException,
              UnrecoverableKeyException,
              KeyStoreException,
              NoSuchAlgorithmException{
       KeyStore ks = loadKeyStore(store, sPass);
       KeyPair keyPair = null;
       Key key = null;
       PublicKey publicKey = null;
       PrivateKey privateKey = null;
       if (ks.containsAlias(alias)){
           key = ks.getKey(alias,kPass.toCharArray());
           if (key instanceof PrivateKey){
               Certificate cert = ks.getCertificate(alias);
               publicKey = cert.getPublicKey();
               privateKey = (PrivateKey)key;
               return new KeyPair(publicKey,privateKey);
           }else{
               return null;
           }
       } else {
           return null;
       }
   }

With a KeyPair, we can easily get the PrivateKey and PublicKey by invoking the corresponding operations, getPrivate() and getPublic().

To get a PublicKey from the KeyStore, we do not really need the key password required in the above approach, and this is what the following method achieves:

public static PublicKey getPublicKey(String store,
                                     String sPass, String alias)
          throws KeyStoreException,
              NoSuchAlgorithmException,
              CertificateException,
              IOException{
       KeyStore ks = loadKeyStore(store, sPass);
       Certificate cert = ks.getCertificate(alias);
       return cert.getPublicKey();
   }

In both code snippets, the method

KeyStore loadKeyStore(String store, String sPass)

is a utility function for instantiating a KeyStore object, and loading entries from the file system. We implement it as follows:

private static KeyStore loadKeyStore(String store, String sPass)
             throws KeyStoreException,
                    NoSuchAlgorithmException,
                    CertificateException,
                    IOException{
       KeyStore myKS = KeyStore.getInstance("JKS");
       FileInputStream fis = new FileInputStream(store);
       myKS.load(fis,sPass.toCharArray());
       fis.close();
       return myKS;
   }

The keytool supplied with the JDK can also export certificates in a key store to system files. For example, to create a biz.cer file containing the X509 certificate associated with the key entry having the alias biz, we can run the following command from the ./etcdirectory:

keytool -export -alias biz -file biz.cer -keystore bizkeystore -storepass sp1234

This certificate authenticates the public key we discussed above.

We also include in our example a Java class, CertificateInfo, for printing to System.out some of the interesting information in a certificate. To try it out, readers may run the Ant target certInfo. However, basic knowledge of DSA and RSA algorithms is required to comprehend the code and its output. Readers may safely bypass this program, and continue to read the remaining sections of this article without difficulties.

Generate an Enveloping Signature

This section discusses signing the invoice.xml file with the JSR-105 APIs and its default implementation.

Our example creates an enveloping signature, though there would only be minor differences if you were to work with a detached or enveloped signature scenario.

Let us look at the program Sign.java, which generates the XML signature for invoice.xml.

public class Sign {
    public static void main(String[] args) throws Exception {
        String input = "./etc/invoice.xml ";
        String output = "./etc/signature.xml";
        if (args.length > 2) {
            input = args[0];
            output = args[1];
        }

         // Prepare
        DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
        dbf.setNamespaceAware(true);

         // Step 1
        String providerName = System.getProperty("jsr105Provider",
                "org.jcp.XML.dsig.internal.dom.XMLDSigRI");
        XMLSignatureFactory fac = XMLSignatureFactory.getInstance("DOM",
                (Provider) Class.forName(providerName).newInstance());

         // Step 2
        Reference ref = fac.newReference("#invoice",
                fac.newDigestMethod(DigestMethod.SHA1, null));

         // Step 3
        Document XML = dbf.newDocumentBuilder().parse(new File(input));
        Node invoice = XML.getDocumentElement();
        XMLStructure content = new DOMStructure(invoice);
        XMLObject obj = fac.newXMLObject(Collections.singletonList(content),
                "invoice", null, null);

         // Step 4
        SignedInfo si = fac.newSignedInfo(fac.newCanonicalizationMethod(
                       CanonicalizationMethod.INCLUSIVE_WITH_COMMENTS,
                       (C14NMethodParameterSpec) null),
                       fac.newSignatureMethod(SignatureMethod.DSA_SHA1, null),
                       Collections.singletonList(ref));

         // Step 5, follow 5.0 or 5.1
        PrivateKey privateKey = null;

         // Scenario 5.0
        privateKey =  KeyStoreInfo.getPrivateKey("./etc/bizkeystore","sp1234",
                "kp1234", "biz");

         // Scenario 5.1, follow 5.1.1 or 5.1.2

         // Scenario 5.1.1
        // KeyPairGenerator kpg = KeyPairGenerator.getInstance("DSA");
        // kpg.initialize(512);
        // KeyPair kp = kpg.generateKeyPair();

             // Scenario 5.1.2
        // KeyPair kp = KeyStoreInfo.getKeyPair("./etc/bizkeystore", "sp1234",
        //        "kp1234", "biz");

         // Keep this line if 5.1 is followed
        // privateKey = kp.getPrivate();

          // Step 6, follow 6.0, 6.1 or 6.2

          // Scenario 6.0, also keep this line if 6.1 or 6.2 is followed
        KeyInfo ki = null;

              // Keep this line if 6.1 or 6.2 is followed
        // KeyInfoFactory kif = fac.getKeyInfoFactory();

         // Scenario 6.1
        // KeyValue kv = kif.newKeyValue(kp.getPublic());
        // ki = kif.newKeyInfo(Collections.singletonList(kv));

         // Scenario 6.2
        // CertificateFactory cf = CertificateFactory.getInstance("X.509");
        // FileInputStream fis = new FileInputStream("./etc/biz.cer");
        // java.security.cert.Certificate cert = cf.generateCertificate(fis);
        // fis.close();
        // X509Data x509d = kif.newX509Data(Collections.singletonList(cert));
        // ki = kif.newKeyInfo(Collections.singletonList(x509d));

         // Step 7
        XMLSignature signature = fac.newXMLSignature(si, ki,
                Collections.singletonList(obj), null, null);

         // Step 8
        Document doc = dbf.newDocumentBuilder().newDocument();
        DOMSignContext dsc = new DOMSignContext(privateKey, doc);

         // Step 9
        signature.sign(dsc);

         // Materialize into an xml document
        TransformerFactory tf = TransformerFactory.newInstance();
        Transformer trans = tf.newTransformer();
        trans.transform(new DOMSource(doc), new StreamResult(new FileOutputStream(output)));
    }
}

To try out this program, readers may run the Ant target sign, and it will create the XML document ./etc/signature.xml. This is the XML signature. To keep our code clean and focused, we omit all the formatting settings in parsing XML or/and transforming the DOM tree. As a result, signature.xml is a bit messy as a text file.

Let us now walk through this program to demonstrate how to sign an XML Signature in JSR-105.

The process of signing invoice.xml can be divided into nine steps.

Step 1: Load an XMLSignatureFactory instance. This factory class will be responsible for constructing almost all the major objects we need in working with XML Signature in JSR-105 APIs, except those related to KeyInfo.

Step 2: Decide on a digest method and create the reference object. We use the XMLSignatureFactory instance created in the first step to create both the DigestMethod and Reference objects.

The factory operation in XMLSignatureFactory for DigestMethod objects is:

public abstract DigestMethod newDigestMethod(String algorithm,
    DigestMethodParameterSpec params) throws NoSuchAlgorithmException,
                    InvalidAlgorithmParameterException

The params argument is for specifying parameters the digest algorithm may need. In the cases of SHA-1, SHA-256 or SHA-512, we may just use null.

For the purpose of creating a Reference object, XMLSignatureFactory provides four operations:

public abstract Reference newReference(String uri, DigestMethod dm);
public abstract Reference newReference(String uri, DigestMethod dm,
    List transforms, String type, String id);
public abstract Reference newReference(String uri, DigestMethod dm,
    List transforms, String type, String id, byte[] digestValue);
......

To fully understand the meaning of the input parameters in those operations, we need to look at the XML schema definition of Reference element in the W3C recommendation:

<element name="Reference" type="ds:ReferenceType"/>
 <complexType name="ReferenceType">
   <sequence>
     <element ref="ds:Transforms" minOccurs="0"/>
     <element ref="ds:DigestMethod"/>
     <element ref="ds:DigestValue"/>
   </sequence>
   <attribute name="Id" type="ID" use="optional"/>
   <attribute name="URI" type="anyURI" use="optional"/>
   <attribute name="Type" type="anyURI" use="optional"/>
 </complexType>

The URI attribute refers to the data object that the Reference corresponds to.

For our example, we use SHA-1 as the digest method, and use #invoice to refer to an element in the same XML Signature document that will contain the XML representation of this Reference object. The element referred by #invoice is what we are going to discuss in the next step.

Step 3: Load invoice.xml and wrap it in an XMLObject object. Not all signature generation processes require this step. XMLObject in JSR-105 models the optional Object element we briefly discussed before. The Object element has the following schema definition:

<element name="Object" type="ds:ObjectType"/>
 <complexType name="ObjectType" mixed="true">
   <sequence minOccurs="0" maxOccurs="unbounded">
     <any namespace="##any" processContents="lax"/>
   </sequence>
   <attribute name="Id" type="ID" use="optional"/>
   <attribute name="MimeType" type="string" use="optional"/>
   <attribute name="Encoding" type="anyURI" use="optional"/>
 </complexType>

XMLSignatureFactory provides the following method for creating an XMLObject instance:

public abstract XMLObject newXMLObject(List content, String id,
    String mimeType, String encoding)

We use a DOMStructure object to wrap the root node of invoice.xml. DOMStructure in JSR-105 can help import nodes from the original to-be-signed XML document to the JSR-105 runtime.

We specify #invoice as the id of the resulting Object element. The JSR-105 implementation knows the Reference object created in Step 2 refers to the invoice.xml document, because this id links them together (on the Reference side, and the URI attribute points to this id).

Step 4: Create the SignedInfo object. In the W3C recommendation, the SignedInfo element has the following schema definition:

<element name="SignedInfo" type="ds:SignedInfoType"/>
 <complexType name="SignedInfoType">
   <sequence>
     <element ref="ds:CanonicalizationMethod"/>
     <element ref="ds:SignatureMethod"/>
     <element ref="ds:Reference" maxOccurs="unbounded"/>
   </sequence>
   <attribute name="Id" type="ID" use="optional"/>
 </complexType>

To create a SignedInfo object, we need the Reference created in Step 2. We also need both an instance of CanonicalizationMethod and an instance of SignatureMethod. We refer interested readers to the specifications for an accurate description of the differences among the four XML canonicalization algorithms, and just simply point out here that after we decide on a specific algorithm, alg, the following call on an instance of XMLSignatureFactory, fac, will be able to create the CanonicalizationMethod instance:

fac.newCanonicalizationMethod(alg, null)

We can create a SignatureMethod instance by invoking the following operation defined in XMLSigantureFactory:

public abstract SignatureMethod newSignatureMethod(String algorithm,
    SignatureMethodParameterSpec params) throws NoSuchAlgorithmException,
                    InvalidAlgorithmParameterException

In our example, we have a DSA type key pair; therefore, we need to choose a DSA based algorithm such as DSA_SHA1. For DSA-SHA1, we can set the params argument to null.

To create a SignedInfo instance, XMLSignatureFactory defines two factory methods:

public abstract SignedInfo newSignedInfo(CanonicalizationMethod cm,
    SignatureMethod sm, List references);
public abstract SignedInfo newSignedInfo(CanonicalizationMethod cm,
    SignatureMethod sm, List references, String id).

The id argument in the second one will correspond to the Id attribute of the SignedInfo element in the resulting XML Signature document.

Step 5: Obtain the private key for signing.

In our example, we demonstrate three different approaches to get the private key. In the first approach, we call the getPrivateKey() method of our KeyStoreInfo class to retrieve the DSA-type private key which we created with keytool and stored in the key store bizkeystore (scenario 5.0). To obtain the private key, we may also retrieve the KeyPair from bizkeystore by calling the getKeyPair() method of KeyStoreInfo, and then call getPrivate() of the KeyPair instance (scenario 5.1.2). On the other hand, JCA provides a class named KeyPairGenerator for dynamically creating a KeyPair on the fly, and this is the scenario documented as 5.1.1 in Sign.java.

Readers should also note that JSR-105 allows obtaining the private key through a KeySelector object. We will have more to say about KeySelector in next section.

Step 6: Create a KeyInfo object. This step is optional, just as KeyInfo as an element in Signature element is optional. In scenario 6.0 of our example, we make KeyInfo to be null, and thus omit it completely from the resulting XML signature.

The W3C recommendation and JSR-105 define KeyValues of RSA and DSA types for wrapping, respectively, RSA and DSA public keys, and allow them to be the content for KeyInfo. Scenario 6.1 of our example creates a KeyValue object from the public key we generated with JDK keytool before, and puts it into a KeyInfo object. Later, when we discuss our core validation program, we will see how it uses such a KeyInfo object to retrieve the public key for signature validation.

In JSR-105, we create both KeyValue and KeyInfo objects by invoking operations in a KeyInfoFactory instance. KeyInfoFactory is responsible for creating all the major objects associated with KeyInfo such as KeyName, KeyValue, X509Data, etc. We can get a KeyInfoFactory instance in the same way we get the XMLSignatureFactory instance in Step 1. Our example gets the KeyInfoFactory instance by calling the getKeyInfoFactory() method of the XMLSignatureFactory object.

Scenario 6.2 of our example creates an X509Data object from the certificate biz.cer we exported with keytool from bizkeystore before, and puts it into a KeyInfo object as the content. Again, the core validation program we will discuss later will demonstrate how we can obtain the public key for signature validation from such a KeyInfo object.

Step 7: Create an XMLSignature object. In JSR-105, the XMLSignature interface models the Signature element of the W3C recommendation. We already saw the structure of the Signature element before. To create an XMLSiganture instance, we can invoke one of the following two methods in XMLSignatureFactory:

public abstract XMLSignature newXMLSignature(SignedInfo si, KeyInfo ki);
public abstract XMLSignature newXMLSignature(SignedInfo si, KeyInfo ki,
    List objects, String id, String signatureValueId).

The id and signatureValueId arguments in the second method will be the XML element ID in the resulting XML Signature document. In our example, the XML signature will have an Object element; therefore, we need to use this second factory method.

Step 8: Instantiate a DOMSignContext object, and register the private key with it. The XMLSignContext interface (which DOMSignContext implements) contains context information for generating XML signatures.

DOMSignContext provides in some of its constructors a way for the signing application to register the private key to be used, and this is the approach our example takes.

Before moving to the final step of the signing process, we need to point out that both XMLSignContext and DOMSignContext instances may contain information and state specific to the XML Signature structure they are used with. The JSR-105 spec states that if an XMLSignContext (or DOMSignContext) is used with different signature structures, the results are unpredictable. For instance, we should not use the same XMLSignContext (or DOMSignContext) instance to sign two different XMLSignature objects.

Step 9: Sign. The sign() operation in the XMLSignature interface signs the XMLSignature. Under the surface, the method carries out several actions, including computing the digest values for all the References based on the corresponding digest methods, and calculating the signature value based on the signature method and the private key. The signature value is captured by the embedded SignatureValue class in the XMLSignature instance, and calling getSignatureValue() of the XMLSignature instance will return the SignatureValue object populated with the resulting value.

At the end of our signing program, we marshal the XMLSignature into an XML document, signature.xml.

Core Validation of XML Signature

In the preceding section, we signed the invoice.xml document into an enveloping XML signature captured in the signature.xml file.

To validate the signature, we can use the following program: Validate.java.

public class Validate {
    public static void main(String[] args) throws Exception {

         // Step 1
        String providerName = System.getProperty("jsr105Provider",
            "org.jcp.XML.dsig.internal.dom.XMLDSigRI");
        XMLSignatureFactory fac = XMLSignatureFactory.getInstance("DOM",
            (Provider) Class.forName(providerName).newInstance());

         // Step 2
        DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
        dbf.setNamespaceAware(true);
        Document doc = dbf.newDocumentBuilder().parse(new FileInputStream(args[0]));

         // Step 3
        NodeList nl = doc.getElementsByTagNameNS(XMLSignature.xmlNS,"Signature");
        if (nl.getLength() == 0) {
            throw new Exception("Cannot find Signature element!");
        }

         // Step 4, follow 4.0, 4.1, 4.2 or 4.3

         // Scenario 4.0
        DOMValidateContext valContext = new DOMValidateContext(
                new KeyStoreKeySelector(), nl.item(0));


         // Scenario 4.1, requires 6.1 in Sign.java
        // DOMValidateContext valContext = new DOMValidateContext(
        //        new KeyValueKeySelector(), nl.item(0));

         // Scenario 4.2, requires 6.2 in Sign.java
        // KeyStore ks = KeyStore.getInstance("JKS");
        // FileInputStream fis = new FileInputStream("./etc/bizkeystore");
        // ks.load(fis,"sp1234".toCharArray());
        // fis.close();
        // X509KeySelector x509ks = new X509KeySelector(ks);
        // DOMValidateContext valContext = new DOMValidateContext(x509ks, nl.item(0));

         //Scenario 4.3
        // PublicKey pKey = KeyStoreInfo.getPublicKey("./etc/bizkeystore",
        //        "sp1234", "biz");

        // Step 5
        XMLSignature signature = fac.unmarshalXMLSignature(valContext);
        //XMLSignature signature = fac.unmarshalXMLSignature(new DOMStructure(nl.item(0)));

         // Step 6
        boolean coreValidity = signature.validate(valContext);
        // Check core validation status
        if (coreValidity == false) {
            System.err.println("Signature failed core validation!");
            boolean sv = signature.getSignatureValue().validate(valContext);
            System.out.println("Signature validation status: " + sv);
            // Check the validation status of each Reference
            Iterator i = signature.getSignedInfo().getReferences().iterator();
            for (int j = 0; i.hasNext(); j++) {
                boolean refValid = ((Reference) i.next()).validate(valContext);
                System.out.println("Reference (" + j + ") validation status: "
                        + refValid);
            }
        } else {
            System.out.println("Signature passed core validation!");
        }
    }
}

To try out this program, readers may run the Ant target validate. The program prints the core validation status to System.out. If the signature is valid, the printout is "Signature passed core validation!"; if it is not, the printout shows the validation status of both the reference and the signature, and we may thus know exactly which, if not all, of them, cause(s) the failure.

The process of validating signature.xml can be divided into six steps.

Step 1: Load an XMLSignatureFactory instance. This step is the same as in the signing program.

Step 2: Load the XML signature to be validated. In this step, we need to load the XML that contains the XML signature into memory and transfer the XML document into a DOM tree.

Step 3: Identify the Signature node in the DOM tree. Signature is defined in the namespace http://www.w3.org/2000/09/XMLdsig#, which is represented by the static variable XMLNS of the XMLSignature interface in JSR-105.

Step 4: Create a DOMValidateContext instance.

One of the most critical pieces of information in a validation context is obviously the cryptography key. We can register the public key with DOMValidateContext through two different approaches. In the first approach, if the validating application already has the public key, it may put the key directly into the context through the following constructor of DOMValidateContext:

public DOMValidateContext(Key validatingKey, Node node)

This is scenario 4.3 in our example.

The second approach is to register a KeySelector with DOMValidateContext, and let the KeySelector select the public key based on information available in the XMLSignature object to be validated. In JSR-105, KeySelector is an abstract class that defines two operations:

public abstract KeySelectorResult select(KeyInfo keyInfo, Purpose purpose,
    AlgorithmMethod method, XMLCryptoContext context)
    throws KeySelectorException
public static KeySelector singletonKeySelector(Key key)

The second operation creates a KeySelector that always returns the same key. The first operation tries to select a key that satisfies the requirements passed as inputs.

KeySelectorResult is an interface in JSR-105. The specification requires it to contain the Key selected by the KeySelector. Our example implements this interface with the SimpleKeySelectorResult class, simply wrapping the selected public key.

We implement and utilize three different KeySelectors in our example to demonstrate some of the scenarios that a validating application may work with.

In scenario 4.0, KeyStoreKeySelector retrieves the public key from a key store based on the input arguments.

In scenario 4.1, KeyValueKeySelector selects a key based on the KeyValue information in the input KeyInfo object (which should contain a KeyValue object as part of its contents; see scenario 6.1 in Sign.java).

In scenario 4.2, X509KeySelector selects a key based on the X509Data contained in the KeyInfo object (which should contain a X509Data object as part of its contents; see scenario 6.2 in Sign.java) and others. We take X509KeySelector from the Javadocs of JSR-105. The original author is Sean Mullan, and we slightly modified the private certSelect() method so that it can work with the certificate we generated with keytool.

Since KeyInfo in Signature may contain various information, obviously, an application must choose a KeySelector implementation that can work with the kind of information contained in the KeyInfos it will deal with.

Step 5: Unmarshal the Signature node into an XMLSiganture object. In previous steps, among other things, we loaded the signature.xml file into a DOM tree, identified the node that corresponded to the Signature element in the tree, and registered the node with a DOMValidateContext together with the KeySelector (or private key). To validate the XML signature, we need to unmarshal the Signature node into an XMLSignature object. We accomplish this by calling the following operation of XMLSignatureFactory:

public abstract XMLSignature unmarshalXMLSignature(XMLValidateContext context)
            throws MarshalException

Step 6: Validate the XML signature. This is accomplished by calling the validate() method of the XMLSignature instance with the DOMValidateContext as the only input argument.

The validate() method validates the XML signature according to the core validation process defined in the W3C recommendation. As mentioned before, this process includes two parts. One is validating all references. In JSR-105, this can be accomplished by invoking the validate() operation of the Reference interface with the relevant validation context as input.

The other part in core validation is signature validation, validating the signature value of the canonicalized SignedInfo element. With JSR-105, we may complete this part explicitly by invoking the validate() method of the SignatureValue object associated with the XMLSignature instance, again with the relevant validation context as input.

In our example, we use such knowledge to print out the validation status for each Reference and the SigantureValue when the XML Signature (core) validation fails, to get more detailed information on what causes the failure.

Tampering with the XML Signature

To demonstrate that the validating program does capture modifications to the generated XML signature, we can create a Tamper.java program in our example allowing us to modify the credit card number in the invoice element, or the SignatureValue element in signature.xml.

This program takes the XML Signature document and a Boolean value as arguments. When the Boolean parameter is true, the program changes the credit card number; otherwise, it modifies the signature value.

public class Tamper {
    public static void main(String[] args) throws Exception {

        String sigfile = "etc/signature.xml";

        // Flag to decide what to modify: Reference or SignatureValue
        boolean tamperRef = true ;

        if (args.length >= 2) {
            sigfile = args[0];
            tamperRef = Boolean.parseBoolean(args[1]);
        }

        File file = new File(sigfile);
        DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
        dbf.setNamespaceAware(true);
        Document signature = dbf.newDocumentBuilder().parse(file);

        if (tamperRef){
            // Modify the credit card number
            NodeList targets =
                signature.getDocumentElement().getElementsByTagName("number");
            Node number = targets.item(0);
            if (!number.getTextContent().equals("987654321")){
                number.setTextContent("987654321");
            }else{
                number.setTextContent("000000000");
            }
        }else{
            // Modify the SignatureValue (the first byte)
            BASE64Encoder en = new BASE64Encoder();
            BASE64Decoder de = new BASE64Decoder();

            NodeList sigValues =
                signature.getDocumentElement().getElementsByTagName("SignatureValue");
            Node sigValue = sigValues.item(0);
            byte[] oldValue = de.decodeBuffer(sigValue.getTextContent());
            if (oldValue[0]!= 111){
                oldValue[0] = (byte)111;
            }else{
                oldValue[0] = (byte)112;
            }
            sigValue.setTextContent(en.encode(oldValue));
        }

        TransformerFactory tf = TransformerFactory.newInstance();
        Transformer trans = tf.newTransformer();
        trans.transform(new DOMSource(signature),
                new StreamResult(new FileOutputStream(file)));
        }
}

To run it, readers may execute the tamper Ant target. After running this tampering program, if we run the validating program again, the core validation will fail, and System.out will show the status of the reference and signature validation, respectively. With the value of the second Boolean input parameter varying, reference and/or signature validation may report failure.

This completes our walkthrough of the example application.

Conclusion

Both XML Signature and JSR-105 contain a lot of details we are not able to cover in this short article, such as transforms, canonicalization methods, and the Manifest and SignatureProperties elements (both are possible sub-elements of Object). We did not really drill down to the digest and signature algorithms, either. Interested readers should consult the W3C recommendation, JSR-105 API docs, and literature in cryptography for more information.

The W3C recommendation only requires implementations to support digest and signature methods based on the SHA-1 hash function. A group of Chinese mathematicians, led by Professor Xiaoyun Wang, have challenged the security of some of the widely applied hash functions including SHA-1 (see one of the articles by Wang and comments by Bruce Schneier). While their results do not immediately mean that SHA-1 as used in XML Signature is insecure now, experts in cryptography do recommend new applications and systems to consider employing more secure algorithms.

Readers should also be aware that, even though JCP plans for JSR-105 to be released with JSE 6, JSR-105 requires all compliant implementations to support JDK 1.4 and up. Therefore, applications developed with JDK 1.4 should have no problem taking advantage of this technology.

Resources

Young Yang works in software, consulting and corporate IT as a tech lead, architect, development and project manager.
Related Topics >> Web Services and XML   |