Skip to main content

XML Signature with JSR-105 in Java SE 6

November 21, 2006

alt="{cs.r.title}" border="0" align="left" hspace="10" vspace="0">






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   |