Author: Jeffrey Vagle
The specification
Since XML signatures may be applied arbitrarily within an XML document, they can be used to sign more than one type of resource. Within XML documents, signatures are related to the data object that they sign through Uniform Resource Identifiers (URI) or local references. This means that the signed data object may either reside locally within the document or may be found elsewhere, as defined by a valid URI. XML digital signatures use the structure of the XML document to define these relationships.
XML signatures can be generally applied in three ways: detached, enveloping, and enveloped. Detached XML signatures can sign content external to the XML document itself, or they can be applied within the same XML document, where the XML signature and the data are sibling elements within that document. In either case, the data being signed is identified via a URI or XML transform. Enveloping XML signatures sign data contained within the Object
element
of that signature. As in the detached example, a URI or XML transform is used to identify the data. Finally, the enveloped XML signature signs data that contains the signature itself as an element. Those of you familiar with digital signatures may be scratching your head at this one: how can a digital signature be applied to data that includes in it that very signature? Wouldn’t signing the data object result in a change to that object, thus invalidating the signature? You can avoid this problem through careful definition of the signature calculation to exclude any portion of the signature itself in the signature creation.
Using combinations of the above approaches, XML signatures can be used to selectively sign only those portions of an XML document that need signatures, as opposed to the entire XML document. This gives us a great deal of flexilibility when it comes to signature usage and applications. For example, if an XML document is passed between multiple parties, each party may sign only those sections of the XML document that is relevant to it, or signatures may be nested as part of this process.
The structure of XML signatures is as follows:
<Signature>
<SignedInfo>
(CanonicalizationMethod)
(SignatureMethod)
(<Reference (URI=)? >
(Transforms)?
(DigestMethod)
(DigestValue)
</Reference>)+
</SignedInfo>
(SignatureValue)
(KeyInfo)?
(Object)*
</Signature>
Note that the Reference
element identifies the data or object being signed, the SignatureValue
element carries the actual signature data, and the KeyInfo
element may include certificate or public key data within the Signature
element. If the XML signature is to be enveloping, the Object
element may include the data to be signed (located via Reference
).
Example: Creating an XML signature
Our examples will utilize the Apache Project’s XML Security Java libraries, which contain implementations of XML Signature and XML Encryption. In addition to these libraries, you’ll need to obtain a Java Cryptography Extension (JCE) implementation. As part of Apache’s XML Security Java libraries you have the option of obtaining the BouncyCastle JCE provider during the Ant build/install process. Depending on where you live, you probably have other JCE options as well. Be sure to familiarize yourself with your local laws concerning cryptography export restrictions before you use it. Installation details for the XML Security package and supporting libraries are beyond the scope of this article; refer to each package’s documentation. Finally, I’ll assume you have a moderate familiarity with XML concepts.
In order to employ digital signatures of any kind, we need to set ourselves up with public/private keypairs. There are more than a few ways to go about this, and the details behind this topic can easily be found elsewhere. We discussed approaches to this topic using Java keystores in an earlier article, and we’ll use those methods here.
For simplicity’s sake, we leave details such as try/catch blocks and initialization out of our code examples.
The first step we’ll take is to set up a few variables which define our keystore-related details:
String
keyStore = "/path/to/keystorefile";
String keyStoreType = "jks";
String keyStorePass = "password";
String keyAlias = "MyKeyName";
We assume here that the keystore password and the private key password are the same. Be sure to change the values for keyStore
, keyStorePass
, and keyAlias
to values correct for your location. We now instantiate and load the Java KeyStore, obtaining the private key to be used for signatures:
java.security.KeyStore
ks = java.security.KeyStore.getInstance(keyStoreType);
java.io.FileInputStream in = new
java.io.FileInputStream(keyStore);
ks.load(in, keyStorePass.toCharArray());
java.security.PrivateKey privKey =
(java.security.PrivateKey)ks.getKey(keyAlias,
keyStorePass.toCharArray());
We’re ready to begin building our example XML document. The Java SDK provides a convenient DocumentBuilderFactory
class which we’ll use for this purpose.
javax.xml.parsers.DocumentBuilderFactory
docFactory = javax.xml.parsers.DocumentBuilderFactory.newInstance();
docFactory.setNamespaceAware(true);
javax.xml.parsers.DocumentBuilder docBuilder =
docFactory.newDocumentBuilder();
org.w3c.dom.Document doc = docBuilder.newDocument();
Note that since XML signatures use XML namespaces, we tell our DocumentBuilderFactory
to be namespace-aware.
Now we’ll create a simple XML document, containing only one Element with a child node for signature. In addition, we’ll define a file to which we’ll write our XML document.
org.w3c.dom.Element
element = doc.createElement("Foo");
org.w3c.dom.Text textNode = doc.createTextNode("This is a
test.n");
element.appendChild(textNode);
doc.appendChild(element);
java.io.File xmlFile = new File("/path/to/xmlfile.txt");
We’ve created our XML document, so now we’re ready to create an XML signature, defining the base URI with our XML filename, and using RSA with SHA-1 as the signature method.
String
baseURI = xmlFile.toURL().toString();
org.apache.xml.security.signature.XMLSignature
xmlSig = new org.apache.xml.security.signature.XMLSignature(doc,
baseURI,
org.apache.xml.security.signature.XMLSignature.ALGO_ID_SIGNATURE_RSA_SHA1);
For this example we’ll create an enveloped XML signature. That is, our signature will be part of the XML document as a child of the document’s root element. To do so, we’ll add the element associated with the XML signature to the document itself:
org.w3c.dom.Element
sigElement = xmlSig.getElement();
element.appendChild(sigElement);
We also need to define the rules for XML document transformation and canonicalization, necessary for parsing and signature verification by others.
org.apache.xml.security.transforms.Transforms
transforms = new org.apache.xml.security.transforms.Transforms(doc);
transforms.addTransform(org.apache.xml.security.transforms.Transforms.TRANSFORM_ENVELOPED_SIGNATURE);
transforms.addTransform(org.apache.xml.security.transforms.Transforms.TRANSFORM_C14N_WITH_COMMENTS);
xmlSig.addDocument("", transforms,
org.apache.xml.security.utils.Constants.ALGO_ID_DIGEST_SHA1);
Optionally, we can include the public key certificate
information associated with the private key we used to create this digital signature.
String
pkCertAlias = "MyCertName";
java.security.cert.X509Certificate pkCert =
(java.security.cert.X509Certificate)ks.getCertificate(pkCertAlias);
xmlSig.addKeyInfo(pkCert);
All we have to do now is sign the document and write it to a file, using a convenience class found in the Apache XML Security package:
xmlSig.sign(privKey);
java.io.FileOutputStream out = new
java.io.FileOutputStream(xmlFile);
org.apache.xml.security.utils.XMLUtils.outputDOMc14nWithComments(doc,
xmlFile);
out.close();
Verifying an XML Signature
To demonstrate the verification of an XML digital signature, we’ll use the output file from the above XML digital signature creation example. From this file, we’ll rebuild the original XML document.
java.io.File
xmlFile = new java.io.File("/path/to/xmlfile.txt");
java.xml.parsers.DocumentBuilderFactory docFactory = new
java.xml.parsers.DocumentBuilderFactory.newInstance();
javax.xml.parsers.DocumentBuilder docBuilder =
docFactory.newDocumentBuilder();
java.io.FileInputStream in = new java.io.FileInputStream(xmlFile);
org.w3c.dom.Document doc = docBuilder.parse(in);
Once we have the Document
in hand, we can extract the XML signature. Admittedly, there are more elegant and general means of getting at the signature element, but we’re aiming for
simplicity here.
org.w3c.dom.NodeList
xmlSigs = getElementsByTagName("Signature");
org.w3c.dom.Element xmlSigElement =
(org.w3c.dom.Element)xmlSigs.item(0);
String baseURI = xmlFile.toURL().toString();
org.apache.xml.security.signature.XMLSignature xmlSig = new
org.apache.xml.security.signature.XMLSignature(xmlSigElement, baseURI);
Since we included the X.509 certificate in our XML signature’s keyinfo, we can obtain it now.
org.apache.xml.security.keys.KeyInfo
keyInfo = xmlSig.getKeyInfo();
java.security.cert.X509Certificate pkCert =
keyInfo.getX509Certificate();
We now have all the pieces to verify the document’s XML signature.
boolean
valid = xmlSig.checkSignatureValue(pkCert);
if (valid)
System.out.println("Signature is valid.");
else
System.out.println("Signature is not valid.");