In 2001, Sean Mullan of Sun Microsystems and Anthony Nadalin of IBM jointly intro- duced JSR 105: XML Digital Signature APIs (http://www.jcp.org/en/jsr/detail?id=105) to support the XML Signatures standard in Java. According to this JSR’s web page, it “defines and incorporates a standard set of high-level implementation-independent APIs for XML digital signatures services.” JSR 105’s APIs, which made it into Java SE 6, are implemented in terms of the six Java packages described in Table 10-2.
Table 10-2.XML Digital Signature API Packages
Package Description
javax.xml.crypto Common classes and interfaces for generating XML digital signatures, and for performing other XML cryptographic operations. For example, the KeySelectorclass is useful for obtaining an XML Signature’s public key, for use in validating the signature.
javax.xml.crypto.dom Document Object Model (DOM)-specific common classes and interfaces. Only developers who are using a DOM-based XML cryptographic implementation will need to work directly with this package.
javax.xml.crypto.dsig Classes and interfaces for generating and validating XML Signatures. Various interfaces such as SignedInfo,
CanonicalizationMethod, and SignatureMethodcorrespond to the equivalent W3C-defined elements.
C H A P T E R 1 0 ■ S E C U R I T Y A N D W E B S E R V I C E S 353
Continued
javax.xml.crypto.dsig.dom DOM-specific classes and interfaces for generating and validating XML Signatures. Only developers who are using a DOM-based XML cryptographic implementation will need to work directly with this package.
javax.xml.crypto.dsig.keyinfo Classes and interfaces for parsing and processing KeyInfo components and structures. KeyInfocorresponds to the equivalent W3C-defined KeyInfoelement.
javax.xml.crypto.dsig.spec Input parameter classes and interfaces for digest, signature, transform, or canonicalization algorithms that are used in XML digital signature processing. C14NmethodParameterSpecis an example.
The javax.xml.crypto.dsig.XMLSignatureFactoryclass is the entry point into these APIs. This class provides methods that do the following:
• Create an XML Signature’s elements as objects.
• Create an instance of javax.xml.crypto.dsig.XMLSignatureto contain these objects.
XMLSignatureand its signature are marshaled into an XML representation during a signing operation.
• Unmarshal an existing XML representation into an XMLSignatureobject before vali- dating the signature.
However, before an application can accomplish these tasks, it needs to obtain an instance of XMLSignatureFactory. Accomplish this task by invoking one of
XMLSignatureFactory’s getInstance()methods, where each method returns an instance that supports a specific type of XML mechanism (such as DOM). The objects that this factory produces will be based on the XML mechanism type and abide by the type’s interoperability requirements.
■ Note To discover DOM-interoperability requirements, check out the “Java XML Digital Signature API Specification (JSR 105)” Javadoc (http://java.sun.com/javase/6/docs/technotes/guides/
security/xmldsig/overview.html).
I have created an example that demonstrates the XML Digital Signature APIs. The XMLSigDemoapplication, shown in Listing 10-2, provides the capabilities for signing an arbitrary XML document and for validating a signed document’s XML Signature.
Table 10-2.Continued
Package Description
Listing 10-2.XMLSigDemo.java
// XMLSigDemo.java import java.io.*;
import java.security.*;
import java.util.*;
import javax.xml.crypto.*;
import javax.xml.crypto.dom.*;
import javax.xml.crypto.dsig.*;
import javax.xml.crypto.dsig.dom.*;
import javax.xml.crypto.dsig.keyinfo.*;
import javax.xml.crypto.dsig.spec.*;
import javax.xml.parsers.*;
import javax.xml.transform.*;
import javax.xml.transform.dom.*;
import javax.xml.transform.stream.*;
import org.w3c.dom.*;
public class XMLSigDemo {
public static void main (String [] args) throws Exception {
boolean sign = true;
if (args.length == 1)
sign = false; // validate instead of sign else
if (args.length != 2) {
System.out.println ("usage: java XMLSigDemo inFile [outFile]");
return;
}
if (sign)
signDoc (args [0], args [1]);
C H A P T E R 1 0 ■ S E C U R I T Y A N D W E B S E R V I C E S 355
else
validateSig (args [0]);
}
static void signDoc (String inFile, String outFile) throws Exception {
// Obtain the default implementation of DocumentBuilderFactory to parse // the XML document that is to be signed.
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance ();
// Because XML signatures use XML namespaces, the factory is told to be // namespace-aware.
dbf.setNamespaceAware (true);
// Use the factory to obtain a DocumentBuilder instance, which is used // to parse the document identified by inFile.
Document doc = dbf.newDocumentBuilder ().parse (new File (inFile));
// Generate a DSA KeyPair with a length of 512 bits. The private key is // used to generate the signature.
KeyPairGenerator kpg = KeyPairGenerator.getInstance ("DSA");
kpg.initialize (512);
KeyPair kp = kpg.generateKeyPair ();
// Create a DOM-specific XMLSignContext. This class contains context // information for generating XML Signatures. It is initialized with the // private key that will be used to sign the document and the root of // the document to be signed.
DOMSignContext dsc = new DOMSignContext (kp.getPrivate (),
doc.getDocumentElement ());
// The different parts of the Signature element are assembled into an // XMLSignature object. These objects are created and assembled using an // XMLSignatureFactory. Because DocumentBuilderFactory was used to parse // the XML document into a DOM object tree, a DOM implementation of // XMLSignatureFactory is obtained.
XMLSignatureFactory fac = XMLSignatureFactory.getInstance ("DOM");
// Create a Reference element to the content to be digested: An empty // string URI ("") implies the document root. SHA1 is used as the digest // method. A single enveloped Transform is required for an enveloped // signature, so that the Signature element and contained elements are // not included when calculating the signature.
Transform xfrm = fac.newTransform (Transform.ENVELOPED,
(TransformParameterSpec) null);
Reference ref;
ref = fac.newReference ("",
fac.newDigestMethod (DigestMethod.SHA1, null), Collections.singletonList (xfrm), null,
"MyRef");
// Create the SignedInfo object, which is the only object that is // signed -- a Reference element's identified data object is digested, // and it is the digest value that is part of the SignedInfo object that // is included in the signature. The CanonicalizationMethod chosen is // inclusive and preserves comments, the SignatureMethod is DSA, and the // list of References contains only one Reference.
CanonicalizationMethod cm;
cm = fac.newCanonicalizationMethod (CanonicalizationMethod.
INCLUSIVE_WITH_COMMENTS,
(C14NMethodParameterSpec) null);
SignatureMethod sm;
sm = fac.newSignatureMethod (SignatureMethod.DSA_SHA1, null);
SignedInfo si;
si = fac.newSignedInfo (cm, sm, Collections.singletonList (ref));
// Create the KeyInfo object, which allows the recipient to find the // public key needed to validate the signature.
KeyInfoFactory kif = fac.getKeyInfoFactory ();
KeyValue kv = kif.newKeyValue (kp.getPublic ());
KeyInfo ki = kif.newKeyInfo (Collections.singletonList (kv));
// Create the XMLSignature object, passing the SignedInfo and KeyInfo // values as arguments.
C H A P T E R 1 0 ■ S E C U R I T Y A N D W E B S E R V I C E S 357
XMLSignature signature = fac.newXMLSignature (si, ki);
// Generate the signature.
signature.sign (dsc);
System.out.println ("Signature generated!");
System.out.println ("Outputting to "+outFile);
// Transform the DOM-based XML content and Signature element into a // stream of content that is output to the file identified by outFile.
TransformerFactory tf = TransformerFactory.newInstance ();
Transformer trans = tf.newTransformer ();
trans.transform (new DOMSource (doc),
new StreamResult (new FileOutputStream (outFile)));
}
@SuppressWarnings ("unchecked")
static void validateSig (String inFile) throws Exception {
// Obtain the default implementation of DocumentBuilderFactory to parse // the XML document that contains the signature.
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance ();
// Because XML signatures use XML namespaces, the factory is told to be // namespace-aware.
dbf.setNamespaceAware (true);
// Use the factory to obtain a DocumentBuilder instance, which is used // to parse the document identified by inFile.
Document doc = dbf.newDocumentBuilder ().parse (new File (inFile));
// Return a list of all Signature element nodes in the DOM object tree.
// There must be at least one Signature element -- the signDoc() method // results in exactly one Signature element.
NodeList nl = doc.getElementsByTagNameNS (XMLSignature.XMLNS,
"Signature");
if (nl.getLength () == 0)
throw new Exception ("Missing Signature element");
// Create a DOM-specific XMLValidateContext. This class contains context // information for validating XML Signatures. It is initialized with the // public key that will be used to validate the document, and a
// reference to the Signature element to be validated. The public key // will be obtained by invoking keyValueKeySelector's select() method // (behind the scenes).
DOMValidateContext dvc;
dvc = new DOMValidateContext (new KeyValueKeySelector (), nl.item (0));
// The different parts of the Signature element are unmarshalled into an // XMLSignature object. The Signature element is unmarshalled using an // XMLSignatureFactory. Because DocumentBuilderFactory was used to parse // the XML document (containing the Signature element) into a DOM object // tree, a DOM implementation of XMLSignatureFactory is obtained.
XMLSignatureFactory fac = XMLSignatureFactory.getInstance ("DOM");
// Unmarshal the XML Signature from the DOM tree.
XMLSignature signature = fac.unmarshalXMLSignature (dvc);
// Validate the XML Signature.
boolean coreValidity = signature.validate (dvc);
if (coreValidity) {
System.out.println ("Signature is valid!");
return;
}
System.out.println ("Signature is invalid!");
// Identify the cause or causes of failure.
System.out.println ("Checking Reference digest for validity...");
List<Reference> refs;
refs = (List<Reference>) signature.getSignedInfo ().getReferences ();
C H A P T E R 1 0 ■ S E C U R I T Y A N D W E B S E R V I C E S 359
for (Reference r: refs)
System.out.println (" Reference '"+r.getId ()+"' digest is "+
(r.validate (dvc) ? "" : "not ")+"valid");
System.out.println ("Checking SignatureValue element for validity...");
System.out.println (" SignatureValue element's value is "+
(signature.getSignatureValue ().validate (dvc)
? "" : "not ")+"valid");
}
private static class KeyValueKeySelector extends KeySelector {
// Search the Signature element's KeyInfo element's KeyValue elements // for the public key that will be used for validation. No determination // is made if the key can be trusted.
public KeySelectorResult select (KeyInfo keyInfo,
KeySelector.Purpose purpose, AlgorithmMethod method, XMLCryptoContext context) throws KeySelectorException
{
if (keyInfo == null)
throw new KeySelectorException ("Null KeyInfo object!");
SignatureMethod sm = (SignatureMethod) method;
List list = keyInfo.getContent ();
for (int i = 0; i < list.size (); i++) {
XMLStructure xmlStructure = (XMLStructure) list.get (i);
if (xmlStructure instanceof KeyValue) {
PublicKey pk = null;
try {
pk = ((KeyValue) xmlStructure).getPublicKey ();
}
catch (KeyException ke) {
throw new KeySelectorException (ke);
}
// Make sure algorithm is compatible with signature method.
if (algEquals (sm.getAlgorithm (), pk.getAlgorithm ())) {
final PublicKey pk2 = pk;
return new KeySelectorResult () {
public Key getKey () {
return pk2;
} };
} } }
throw new KeySelectorException ("No KeyValue element found!");
} }
static boolean algEquals (String algURI, String algName) {
if (algName.equalsIgnoreCase ("DSA") &&
algURI.equalsIgnoreCase (SignatureMethod.DSA_SHA1)) return true;
if (algName.equalsIgnoreCase ("RSA") &&
algURI.equalsIgnoreCase (SignatureMethod.RSA_SHA1)) return true;
return false;
} }
XMLSigDemo.javais based on the code found in the “XML Digital Signature API Exam- ples” section of The Java Web Services Tutorial (http://java.sun.com/webservices/docs/
2.0/tutorial/doc/XMLDigitalSignatureAPI8.html#wp511424). You will want to check out this resource for more information about how the code works.
C H A P T E R 1 0 ■ S E C U R I T Y A N D W E B S E R V I C E S 361
■ Note The Java Web Services Tutorial’s influence is also evidenced by java.net author Young Yang in his
“XML Signature with JSR-105 in Java SE 6” article (http://today.java.net/pub/a/today/2006/
11/21/xml-signature-with-jsr-105.html?page=1). Young’s Signapplication demonstrates how to create an enveloping signature.
After compiling XMLSigDemo.java, you will need an XML document to sign. For exam- ple, you might want to try out XMLSigDemowith the simple purchase order document, po.xml, presented in Listing 10-3.
Listing 10-3.po.xml
<?xml version="1.0" encoding="UTF-8"?>
<po>
<items>
<item>
<code>hw-1021</code>
<desc>Hammer</desc>
<qty>5</qty>
<unitcost>11.99</unitcost>
</item>
<item>
<code>hw-2103</code>
<desc>Solar lights</desc>
<qty>10</qty>
<unitcost>24.99</unitcost>
</item>
</items>
</po>
To sign this purchase order, invoke the following:
java XMLSigDemo po.xml pos.xml
The sin pos.xmlstands for signed. (You can choose your own name in place of pos.xml.) If all goes well, XMLSigDemooutputs the following:
Signature generated!
Outputting to pos.xml
Also, the signed document is stored in pos.xml. Its contents should be similar to Listing 10-4’s contents.
■ Note Listing 10-4 has been reformatted for this book. The contents of pos.xmlwill vary from one sign- ing operation to another, because XMLSigDemogenerates a different public/private key pair for each run.
Listing 10-4.pos.xml
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<po>
<items>
<item>
<code>hw-1021</code>
<desc>Hammer</desc>
<qty>5</qty>
<unitcost>11.99</unitcost>
</item>
<item>
<code>hw-2103</code>
<desc>Solar lights</desc>
<qty>10</qty>
<unitcost>24.99</unitcost>
</item>
</items>
<Signature xmlns="http://www.w3.org/2000/09/xmldsig#">
<SignedInfo>
<CanonicalizationMethod Algorithm="http://www.w3.org/TR/2001/REC-xml- c14n-20010315#WithComments"/>
<SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig
#dsa-sha1"/>
<Reference Id="MyRef" URI="">
<Transforms>
<Transform Algorithm="http://www.w3.org/2000/09/xmldsig#
enveloped-signature"/>
</Transforms>
<DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/>
<DigestValue>nquYM0ZPk5K6di76vnt63xvR1jI=</DigestValue>
</Reference>
</SignedInfo>
C H A P T E R 1 0 ■ S E C U R I T Y A N D W E B S E R V I C E S 363
<SignatureValue>ftXiy7gIDtU6O1BibABWfc+VteJw2O8xKMTALt14lm091ATeU88+jA==
</SignatureValue>
<KeyInfo>
<KeyValue>
<DSAKeyValue>
<P>/KaCzo4Syrom78z3EQ5SbbB4sF7ey80etKII864WF64B81uRpH5t9jQTxeEu0Im bzRMqzVDZkVG9xD7nN1kuFw==</P>
<Q>li7dzDacuo67Jg7mtqEm2TRuOMU=</Q>
<G>Z4Rxsnqc9E7pGknFFH2xqaryRPBaQ01khpMdLRQnG541Awtx/XPaF5Bpsy4pNWM OHCBiNU0NogpsQW5QvnlMpA==</G>
<Y>ajryQOwA2H77GAt6LNhGwPALyGLMu/e7T70ytj0bxp0RQndX++ydqzKXW6P0VZj 1X9lRW3rVxE0RBxp4yb7eMQ==</Y>
</DSAKeyValue>
</KeyValue>
</KeyInfo>
</Signature>
</po>
Listing 10-4 reveals that XMLSigDemocreated an enveloped signature. You can validate this signature by invoking java XMLSigDemo pos.xml. If the file has not been tampered with, XMLSigDemowill output Signature is valid!.
There are three ways to tamper with this file:
Modify the data object over which the digest value is created. For example, suppose you change the 5to a 6in the hammer item’s quantity tag, and then invoke java XMLSigDemo pos.xml. XMLSigDemoresponds with this output:
Signature is invalid!
Checking Reference digest for validity...
Reference 'MyRef' digest is not valid
Checking SignatureValue element for validity...
SignatureValue element's value is valid
Modify the signature value. For example, you might swap a couple of consecutive characters in the SignatureValueelement. Assuming an exception is not thrown (because the signature is no longer base64-encoded), XMLSigDemoresponds with this output:
Signature is invalid!
Checking Reference digest for validity...
Reference 'MyRef' digest is valid
Checking SignatureValue element for validity...
SignatureValue element's value is not valid
Modify both the digest value and the signature value. In this case, you would observe both Reference 'MyRef' digest is not validand SignatureValue element's value is not validmessages.
■ Tip The XML Digital Signature APIs include extensive logging support that provides additional information to help you debug validation failures. Sean Mullan demonstrates how to take advantage of this support in the
“Logging and Debugging” section of his “Programming With the Java XML Digital Signature API” article (http://java.sun.com/developer/technicalArticles/xml/dig_signature_api/).