XML Digital Signature





XML Digital Signature

In the previous section, we discussed how SSL/TLS can be used for satisfying the first three communication security requirements: confidentiality, integrity, and authentication. What about the last of the four, nonrepudiation?

Suppose that you receive a purchase order for a $2,000 PC from company X. This order came via an SSL/TLS connection with certificate-based client authentication, and the client X.509 certificate verified flawlessly according to your trusted CA. Satisfied, you process the order and ship the PC. But you do not receive payment. What can you do? Even if you kept all the communication logs, including the X.509 certificate itself, you cannot prove that you actually received the order from company X, because you cannot deny the possibility that you forged the log.

Here is where a digital signature plays a key role. A digitally signed order form can be used as undeniable evidence of the order because only the person who has the private key can create the signature bit string (normally 512 to 2,048 bits). This technology is mature enough that some governments, such as those of Singapore, Japan, and the United States, have passed legislation that gives digitally signed business contracts the same legal standing as contracts signed by humans.

There are several standard digital signature formats. The most widely used is Public-Key Cryptography Standard (PKCS) #7 (http://www.rsasecurity.com/rsalabs/pkcs/index.html), whose syntax is based on Abstract Syntax Notation One (ASN.1, ITU-T Rec. X.680 (1997) | ISO/IEC 8824-1:1998). It takes a binary bit string as the data to be signed. The type of the data is irrelevant as far as the signature's validity is concerned. Any signature algorithm is expensive to apply directly to a large amount of data, so first a hash (or digest) value (typically 128 bits or 160 bits) is calculated for the entire data, and then the hash value is signed. Even a single bit change in the bit string always results in a completely different hash value, so any modification of the data invalidates the signature.

For signing XML documents, a joint working group between IETF and W3C has defined XML Digital Signature. In this section, we discuss how we can use XML Digital Signature in B2B applications using IBM's XML Security Suite for Java (XSS4J) as an implementation of XML Digital Signature.

1 XML Canonicalization

To digitally sign an XML document, you first must calculate the hash value of the document. It is possible to take an XML document as a character string (and thus a bit string) and compute its hash value. The problem with this approach is that logically the same XML document can be represented in many different ways because of XML's flexibility in character encoding, whitespace handling, and so on. If a purchase order signed by one company is processed through several applications equipped with different implementations of XML processors, the surface character string—that is, an octet string representing the XML document—might be changed during the process without the content being changed, thereby resulting in an invalid signature.

The surface string can vary without changing the content in any of several ways, including the following.

Character Encoding

The character set in an XML document is defined in the XML 1.0 Recommendation as Unicode, which is a subset of ISO/IEC 10646. However, the Recommendation allows considerable freedom in encoding. Thus the same document can be represented in different ways, depending on the character encoding used—for example, ISO 8859-1, UTF-16, UTF-8, US-ASCII, and Shift-JIS.

Handling Whitespace

The number of whitespace characters between attributes is insignificant in XML. XML processors are not required to preserve the number of spaces. Thus, the following two lines of code are treated as being exactly the same.

<order    id="C763" date="1998-11-17">
<order id="C763" date="1998-11-17">

Also, #x0D, #x0A, and #x0D#x0A are all converted into a single newline character (#0x0A).

Empty Elements

An empty element may be expressed by using either an empty tag or a pair of start and end tags—for example, <book></book> or <book/>.

Order of Attributes

The order of attributes is insignificant. For example, the following are the same:

<order id="C763" date="1998-11-17">
<order date="1998-11-17" id="C763">

To alleviate the problem of surface string deviation, the joint working group of XML Digital Signature defined XML Canonicalization (it is often abbreviated as C14N because there are 14 characters between C and N in the word "canonicalization"). XML Canonicalization (C14N) became a Recommendation in March 2001.

C14N defines a set of rules for how XML documents are represented as a character string in a standard way so that the same XML documents have exactly the same C14N representation, and different XML documents have different C14N representations.

2 XML Digital Signature Sample

Suppose company A wants to purchase a PC from company B. The order document would look like what is shown in Listing 14.2.

Listing 14.2 Purchase order document, chap14/po.xml
<?xml version="1.0" encoding="UTF-8"?>
<PurchaseOrder xmlns="urn:purchase-order">
  <Customer>
    <Name>Robert Smith</Name>
    <CustomerId>788335</CustomerId>
  </Customer>
  <Item partNum="C763">
    <ProductId>6883-JF3</ProductId>
    <Quantity>3</Quantity>
    <ShipDate>2002-09-03</ShipDate>
    <Name>ThinkPad X20</Name>
  </Item>
</PurchaseOrder>

Before sending this order to company B, company A digitally signs it with its private key so that company B can use it as an undeniable proof of the order. This is depicted in Figure.

6. Digital signature and verification

graphics/14fig06.gif

An XML Signature can be embedded in an XML document, or it can contain a signed body within the signature, or it can sign an external resource pointed to by a URL, or it can combine these. Here, we embed a signature in a document that also contains the order document. The signed document that we generate with our sample program looks like that shown in Listing 14.3.

Listing 14.3 The signed document, chap14/signed_po.xml
       <?xml version='1.0' encoding='UTF-8'?>
       <SignedPurchaseOrder>
       <PurchaseOrder id="id0" xmlns="urn:purchase-order">
         <Customer>
           <Name>Robert Smith</Name>
           <CustomerId>788335</CustomerId>
         </Customer>
         <Item partNum="C763">
           <ProductId>6883-JF3</ProductId>
           <Quantity>3</Quantity>
           <ShipDate>2002-09-03</ShipDate>
           <Name>ThinkPad X20</Name>
         </Item>
       </PurchaseOrder>
[15]   <Signature xmlns="http://www.w3.org/2000/09/xmldsig#">
[16]      <SignedInfo>
[17]        <CanonicalizationMethod Algorithm="http://www.w3.org/TR/2001/
            REC-xml-c14n-20010315"/>
[18]        <SignatureMethod Algorithm="http://www.w3.org/2000/09/
            xmldsig#rsa-sha1"/>
[19]        <Reference URI="#id0">
[20]          <DigestMethod Algorithm="http://www.w3.org/2000/09/
              xmldsig#sha1"/>
[21]          <DigestValue>UfeiscUCL7QkhZtRDLWDPWLpVlA=</DigestValue>
[22]        </Reference>
[23]   </SignedInfo>
[24]   <SignatureValue>
[25]     Ptysg8WdHI2mxwryOOt5I9r9qZm/2gNFNOJyH1Wak4nCUegRpe72tWnsigAKZ
         yopmgUSH3TG
[26]     aGGQF1BTSvk3JUUY/ljrw+5FpTpf3hgZBi7GSWf6WtXqZvMYGUKIlvR/
         421MZg7P9XRUyy37
[27]     ZUzQHtmCYkBorEkEx1J4CYB0G2c=
[28]   </SignatureValue>
[29]   <KeyInfo>
[30]     <X509Data>
[31]       <X509Certificate>
[32]    MIIDGjCCAoOgAwIBAgICAQAwDQYJKo ... LMAkGA1UEBhMCSlAxETAPBgNVBAgT
[33]    CEthbmFnYXdhMQ8wDQYDVQQHEwZZYW ... TA0lCTTEMMAoGA1UECxMDVFJMMRAw
[34]    DgYDVQQDEwdUZXN0IENBMB4XDTAxMT ... xMTAwMTA3MTYxMFowUDELMAkGA1UE
[35]    BhMCSlAxETAPBgNVBAgTCEthbhMQww ... JQk0xDDAKBgNVBAsTA1RSTDESMBAG
[36]    A1UEAxMJU2lnbmF0dXJlMIGfMA0GCS ... NADCBiQKBgQCvnFQiPEJnUZnkmzoc
[37]    MjsseD8ms9HBgasZR0VOAvsbytB18d ... jBdprX+epfF4SLNP5ankfphhr9QXA
[38]    NJdCKpyF3jPoydckle7E7gI9w3Q4NO ... 7OVPqiXIDVlCH4u6GbIoJEpJ57yzx
[39]    dQIDAQABo4HzMIHwMUdEwQCMAAYDVR ... gMCwGCWCGSAGG+EIBDQQfFh1PcGVu
[40]    U1NMIEdlbmVyYXRlZCBDZXJ00ZTAdB ... UYapFv9MvQ9NNn1Q7zgzqka4XORsw
[41]    gYgGA1UdIwSBgDB+gBR7FuT9bLBzIe ... FjpGEwXzELMAkGA1UEBhMCSlAxETA
[42]    BgNVBAgTCEthbmFnYXdhMQ8wDQYDVQ ... AKBgNVBAoTA0lCTTEMMAoGA1UECxM
[43]    VFJMMRAwDgYDVQQDEwdUZXN0IENBgg ... BBQUAA4GBALFzGDXMzxJvOnCdJCMZ
[44]    2NsZdz1+wmoYyejB5J6Ch2ygdPeibM ... qr1BNlgSVqA6nyvjHsVIvgBfwx37D
[45]    hJ5hz4azpWu1X22XqyU9fUqoQUtEAd ... JXTFzzvm/3DoEiBkX/BT78YdM8eq0
[46]          </X509Certificate>
[47]        </X509Data>
[48]      </KeyInfo>
[49]    </Signature>

       </SignedPurchaseOrder>

The document has a <Signature> element in the namespace http://www.w3.org/2000/09/xmldsig# (lines 15–49). The signed order is also in this document, with an additional id attribute that is used for referring to this element (lines 3–14). This is referred to by the <Reference> element at line 19.

[19]    <Reference URI="#id0">
[20]      <DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/>
[21]      <DigestValue>UfeiscUCL7QkhZtRDLWDPWLpVlA=</DigestValue>
[22]    </Reference>

This <Reference> element means that the hash value of the canonicalized form of the XML fragment referred to by identifier id0 is UfeiscUCL7Qk ....

The signature algorithm is applied to the canonicalized form of the <SignedInfo> element (lines 16–23). The signature value is in the <SignatureValue> element (lines 24–28). Because the hash value of the <PurchaseOrder> element is in the signature scope, any change in the contents of the <PurchaseOrder> element results in a change in the C14N form of <SignedInfo>, thus invalidating the signature.

3 Signing XML Documents with XML Security Suite for Java

XSS4J is a Java library available from IBM's alphaWorks Web site (see http://www.alphaworks.ibm.com). It contains an implementation of XML Digital Signature. We use this library to implement our signature and verification samples.[5]

[5] In this section, we use XSS4J dated 2001-10-29. This version is dependent on Xerces 1.4 and Xalan 2.1. These jar files must be in the CLASSPATH, along with xss4j.jar. At the time of writing, Java Community Process is defining an API for XML Digital Signature as JSR 105. When this work is finished, the API may be different from the one described here.

Listing 14.4 shows the entire program to create an XML Digital Signature document that contains the signed XML fragment.

Listing 14.4 Digitally signing an XML document, chap14/EnvelopedSignature.java
       package chap14;

       import share.util.MyErrorHandler;
       import com.ibm.xml.dsig.TemplateGenerator;
       import com.ibm.xml.dsig.SignatureContext;
       import com.ibm.xml.dsig.XSignature;
       import com.ibm.xml.dsig.util.AdHocIDResolver;
       import com.ibm.xml.dsig.Reference;
       import com.ibm.xml.dsig.Transform;
       import com.ibm.xml.dsig.KeyInfo;
       import com.ibm.xml.dsig.SignatureMethod;
       import com.ibm.xml.dsig.Canonicalizer;
       import com.ibm.xml.dsig.TransformException;
       import com.ibm.xml.dsig.XSignatureException;
       import com.ibm.xml.dsig.SignatureStructureException;

       import com.ibm.dom.util.ToXMLVisitor;
       import org.w3c.dom.Document;
       import org.w3c.dom.Element;
       import javax.xml.parsers.DocumentBuilder;
       import javax.xml.parsers.DocumentBuilderFactory;
       import java.security.Key;
       import java.security.KeyStore;
       import java.security.KeyStoreException;
       import java.security.NoSuchAlgorithmException;
       import java.security.NoSuchProviderException;
       import java.security.InvalidKeyException;
       import java.security.UnrecoverableKeyException;
       import java.security.cert.CertificateException;
       import java.security.cert.X509Certificate;
       import java.io.FileInputStream;
       import java.io.Writer;
       import java.io.OutputStreamWriter;
       import java.io.IOException;
       import java.util.Hashtable;

       public class EnvelopedSignature {

           private static DocumentBuilderFactory factory;
           private static DocumentBuilder builder;

           public static Element signIt(Document doc,
                                        String id2sign,
                                        String keystorepath,
                                        char[] storepass,
                                        String alias,
                                        char[] keypass)
                throws IOException, KeyStoreException,
                       NoSuchAlgorithmException,
                SignatureStructureException, CertificateException,
                UnrecoverableKeyException, NoSuchProviderException,
                InvalidKeyException, XSignatureException, TransformException {

                    // 1. Prepare key
[55]                KeyStore keystore = KeyStore.getInstance("JKS");
[56]                keystore.load(new
                          FileInputStream(keystorepath), storepass);
[57]                X509Certificate cert = (X509Certificate)keystore.
                    getCertificate(alias);
[58]                Key key =
                          keystore.getKey(alias, keypass); // a private key
[59]                if (key == null) {
[60]                    System.err.println("Could not get a key: "+alias);
[61]                    System.exit(1);
[62]                }
[63]
[64]                String signatureMethod = "";
[65]                if (key.getAlgorithm().equals("RSA")) {
[66]                    signatureMethod = SignatureMethod.RSA;
[67]                } else if (key.getAlgorithm().equals("DSA")) {
[68]                    signatureMethod = SignatureMethod.DSA;
[69]                } else {
[70]                    System.err.println(
                            "Unknown key algorithm"+key.getAlgorithm());
[71]                    System.exit(1);
[72]                }

                    //
                    // 2. Prepare Signature element
                    //
                    TemplateGenerator
                         signatureGen = new TemplateGenerator(doc,
                         XSignature.SHA1,
                                                Canonicalizer.W3C2,
                                                signatureMethod);
                    Reference ref = signatureGen.createReference(id2sign);
                    signatureGen.addReference(ref);

                    Element signatureElement =
                             signatureGen.getSignatureElement();
                    KeyInfo keyInfo = new KeyInfo();
                    KeyInfo.X509Data x509data = new KeyInfo.X509Data();
                    x509data.setCertificate(cert);
                    x509data.setParameters(cert, false, false, false);
                    keyInfo.setX509Data(new KeyInfo.X509Data[] { x509data });
                    keyInfo.insertTo(signatureElement);

                    //
                    // 3. Sign
                    //
[96]                SignatureContext sigContext = new SignatureContext();
[97]                sigContext.setIDResolver(new AdHocIDResolver(doc));
[98]                sigContext.sign(signatureElement, key);
                    return signatureElement;
            }

            public static void main(String[] argv) throws Exception {
                if (argv.length < 5) {
                    System.err.println("Usage: chap15.EnvelopedSignature
                    keystore storepass keyalias  keypass file");
                    System.exit(1);
                }
                String keystorepath = argv[0];
                char[] storepass = argv[1].toCharArray();
                String alias = argv[2];
                char[] keypass = argv[3].toCharArray();

                factory = DocumentBuilderFactory.newInstance();
                factory.setNamespaceAware(true);
                builder = factory.newDocumentBuilder();
                builder.setErrorHandler(new MyErrorHandler());
                Document doc = builder.parse(argv[4]);
                //
                // Wrap the root element with <SignedPurchaseOrder>
                //
                Element root = doc.getDocumentElement();
                doc.removeChild(root);
                Element signedRoot =
                         doc.createElementNS(root.getNamespaceURI(),
                                              "Signed"+root.getNodeName());
                doc.appendChild(signedRoot);
                signedRoot.appendChild(root);
                root.setAttribute("id","id0");
                Element signature =
                     signIt(doc,"#id0",keystorepath,storepass,alias,keypass);
                signedRoot.appendChild(signature);
                //
                // Output
                //
                try {
                    Writer wr = new OutputStreamWriter(System.out, "UTF-8");
                    wr.write("<?xml version='1.0' encoding='UTF-8'?>\n");
                    new ToXMLVisitor(wr).traverse(signedRoot);
                    wr.close();
                } catch (Exception ex) {
                    ex.printStackTrace();
                }
            }
        }

The heart of this program is the following three lines. Line 96 creates a signature context, an object responsible for generating a signature. The invocation of the method sign(signatureElement,Key) in line 97 performs the signing operation.

[96]    SignatureContext sigContext = new SignatureContext();
[97]    sigContext.setIDResolver(new AdHocIDResolver(doc));
[98]    sigContext.sign(signatureElement, key);

All the necessary parameters are included in the signatureElement variable. This is a skeleton DOM structure that represents a complete <Signature> structure except for the values of the <DigestValue> element and <SignatureValue> element. This structure, before signing, looks like this.

<Signature xmlns="http://www.w3.org/2000/09/xmldsig#">
  <SignedInfo>
    <CanonicalizationMethod Algorithm="http://www.w3.org/TR/2001/
    REC-xml-c14n-20010315"/>
    <SignatureMethod Algorithm="http://www.w3.org/2000/09/
    xmldsig#dsa-sha1"/>
    <Reference URI="#Res0">
      <DigestMethod Algorithm="http://www.w3.org/2000/09/
      xmldsig#sha1"/>
      <DigestValue/>
    </Reference>
  </SignedInfo>
  <SignatureValue/>
  <KeyInfo>
    <X509Data>
      <X509Certificate>
         :
      </X509Certificate>
    </X509Data>
  </KeyInfo>
</Signature>

Thus, numerous parameters necessary for XML Digital Signature can be passed as a single DOM structure. To assist in creating this structure, a helper class, TemplateGenerator, is provided. Parameters appearing in SignedInfo, such as the signature algorithm, references to the resources to be signed, and information appearing in <KeyInfo>, are all set to a TemplateGenerator object.

Alternatively, the template DOM structure can be read from an external XML file. The program would have been simpler in that case.

Since a <Reference> element uses an intradocument reference in the form of #id, we need to provide a way to resolve such a reference to yield an element reference. The interface IDResolver is provided for this purpose. We used AdHocIDResolver, which is provided as a utility class in XSS4J.

To use the private key that we have prepared for this sample program, we need to retrieve it from the keystore. Lines 55–72 retrieve the key specified by a keystore filename and a key alias, and then extract the pertinent information, such as the key algorithm and the associated certificate.

To run this program for signing the file po.xml, execute the following command.

R:\samples>java chap14/EnvelopedSignature chap14/keystore/signature.
ks changeit signature changeit chap14/po.xml

The result should look like Listing 14.3.[6]

[6] We inserted a few new lines for readability.

4 Verifying XML Digital Signature with XML Security Suite for Java

When a company receives a signed document, it must verify the signature to determine whether it is authentic. Let us develop a Java program to verify an XML Signature. To verify a signature, we should obtain the signer's certificate issued by a trusted CA. Usually, this is done by accessing a certificate directory published by a CA. In our private PKI, however, we assume that the signer certificate is already installed in the keystore of the verifier. This configuration is practical as long as the number of certificates you need to manage is small.

Listing 14.5 shows a verification program using XSS4J.

Listing 14.5 A signature-verification program, chap14/VerifySignature.java
       package chap14;

       import share.util.MyErrorHandler;
       import com.ibm.xml.dsig.SignatureContext;
       import com.ibm.xml.dsig.XSignature;
       import com.ibm.xml.dsig.Validity;
       import com.ibm.xml.dsig.SignatureStructureException;
       import com.ibm.xml.dsig.TransformException;
       import com.ibm.xml.dsig.util.AdHocIDResolver;

       import javax.xml.parsers.DocumentBuilder;
       import javax.xml.parsers.DocumentBuilderFactory;
       import org.w3c.dom.Document;
       import org.w3c.dom.Element;
       import org.w3c.dom.NodeList;
       import java.io.FileInputStream;
       import java.io.IOException;
       import java.security.Key;
       import java.security.KeyStore;
       import java.security.KeyStoreException;
       import java.security.NoSuchAlgorithmException;
       import java.security.NoSuchProviderException;
       import java.security.UnrecoverableKeyException;
       import java.security.SignatureException;
       import java.security.InvalidKeyException;
       import java.security.cert.X509Certificate;
       import java.security.cert.CertificateException;

       public class VerifySignature {

           static boolean verifyIt(Document doc,
                                   Element e,
                                   String keystorepath,
                                   char[] storepass,
                                   String alias)
               throws IOException, KeyStoreException, NoSuchAlgorithmException,
               SignatureStructureException, CertificateException,
               UnrecoverableKeyException, NoSuchProviderException,
               InvalidKeyException, SignatureException, TransformException {

                   // 1. Prepare key
                   KeyStore keystore = KeyStore.getInstance("JKS");
                   keystore.load(new FileInputStream(keystorepath), storepass);
                   X509Certificate cert = (X509Certificate)keystore.
                   getCertificate(alias);
                   Key key = cert.getPublicKey(); // a private key
                   if (key == null) {
                       System.err.println("Could not get a key: "+alias);
                       System.exit(1);
                   }

[51]               SignatureContext sigContext = new SignatureContext();
[52]               sigContext.setIDResolver(new AdHocIDResolver(doc));
[53]               Validity validity = sigContext.verify(e,key);
[54]
[55]               return validity.getCoreValidity();
           }

           public static void main(String[] argv) throws Exception {
               DocumentBuilderFactory factory;
               DocumentBuilder builder;

               if (argv.length < 4) {
                   System.err.println("Usage: VerifySignature keystore
                   storepass keyalias file");
                   System.exit(1);
               }
               String keystorepath = argv[0];
               char[] storepass = argv[1].toCharArray();
               String alias = argv[2];
               factory = DocumentBuilderFactory.newInstance();
               factory.setNamespaceAware(true);
               builder = factory.newDocumentBuilder();
               builder.setErrorHandler(new MyErrorHandler());
               Document doc = builder.parse(argv[3]);

               NodeList nodeList = doc.getElementsByTagNameNS(XSignature.
               XMLDSIG_NAMESPACE, "Signature");
               if (nodeList.getLength() == 0) {
                   System.err.println("The specified document has no
                   Signature element.");
                   System.exit(1);
               }
               Element signature = (Element)nodeList.item(0);

               if (verifyIt(doc, signature, keystorepath, storepass, alias)) {
                   System.out.println("Signature verify");
               } else {
                   System.out.println("Signature does not verify");
               }
           }
       }

The focus of this program is lines 51–55. In a similar way to the signing program, we create a SignatureContext object and let it validate a <Signature> element by calling the verify() method. This method returns a Validity object, which contains the information on the verification. To know whether the verification was successful, we need to call the getCoreValidity() method.

[51]    SignatureContext sigContext = new SignatureContext();
[52]    sigContext.setIDResolver(new AdHocIDResolver(doc));
[53]    Validity validity = sigContext.verify(e,key);
[54]
[55]    return validity.getCoreValidity();

To run this program for verifying the signature in the file signed_po.xml using our prefabricated keystore, chap14/keystore/verification.ks, enter the following command.

R:\samples>java chap14/VerifySignature chap14/keystore/verification.
ks changeit signature chap14/signed_po.xml

Try slightly modifying signed_po.xml and see whether the signature verifies. If you change any content in the <PurchaseOrder> element, such as modifying the quantity from 3 to 4, you will notice that the signature no longer verifies. On the other hand, nonessential changes, such as adding extra whitespace between attributes, do not affect the verifiability of the signature.


     Python   SQL   Java   php   Perl 
     game development   web development   internet   *nix   graphics   hardware 
     telecommunications   C++ 
     Flash   Active Directory   Windows