Programming With the Java XML Digital Signature API
By Sean Mullan, March 2007
One of the significant new features of the Java Platform, Standard Edition 6 (Java SE 6) is the Java XML Digital Signature API. This API allows you to generate and validate XML signatures. XML signatures are a standard for digital signatures in the XML data format, and they allow you to authenticate and protect the integrity of data in XML and web service transactions.
This article will give you an overview of XML signatures and show you how to use the API in your applications.
Overview of XML Signatures
What is a digital signature? RFC 2828 defines a digital signature as "a value computed with a cryptographic algorithm and appended to a data object in such a way that any recipient of the data can use the signature to verify the data's origin and integrity." JDK 6 includes a cryptographic digital signature API that is described in more detail in a lesson on the security trail in the Java Tutorial.
An XML signature is a digital signature with several key properties. It defines a process and a format for generating digital signatures in the XML format, and it has many additional features. For instance, it allows you to sign more than one piece of data -- in binary or XML -- and to use any underlying cryptographic signature algorithm.
An XML signature can sign arbitrary data, whether it is XML or binary. It can also sign only a portion or a subset of an XML document rather than the entire document. The data to be signed is identified by Uniform Resource Identifiers (URIs). XML signatures are often described as being of one or more of three types:
- A detached signature is over data that is external to the
Signature
element. This could be data outside of the document, such as a web page retrieved by way of HTTP, but it could also be data that is in the same document, such as a sibling element of the signature. - An enveloping signature is over data that is inside the
Signature
element. - An enveloped signature is a signature that is over data that contains the
Signature
element itself, such as the entire document.
Perhaps the best way to describe an XML signature is to step through the contents of an example in detail. The example that this article will use is an enveloped XML signature generated over the contents of an XML document, a sample purchase order. The article will also use this sample in the subsequent sections on using the API. XML Sample 1 shows the contents of the purchase order before it is signed.
XML Sample 1
<?xml version="1.0" encoding="UTF-8"?>
<PurchaseOrder>
<Item number="130046593231">
<Description>Video Game</Description>
<Price>10.29</Price>
</Item>
<Buyer id="8492340">
<Name>My Name</Name>
<Address>
<Street>One Network Drive</Street>
<Town>Burlington</Town>
<State>MA</State>
<Country>United States</Country>
<PostalCode>01803</PostalCode>
</Address>
</Buyer>
</PurchaseOrder>
The resulting enveloped XML signature, indented and formatted for readability, appears in XML Sample 2.
XML Sample 2
<?xml version="1.0" encoding="UTF-8"?>
<PurchaseOrder>
<Item number="130046593231">
<Description>Video Game</Description>
<Price>10.29</Price>
</Item>
<Buyer id="8492340">
<Name>My Name</Name>
<Address>
<Street>One Network Drive</Street>
<Town>Burlington</Town>
<State>MA</State>
<Country>United States</Country>
<PostalCode>01803</PostalCode>
</Address>
</Buyer>
<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#rsa-sha1"/>
<Reference 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>tVicGh6V+8cHbVYFIU91o5+L3OQ=</DigestValue>
</Reference>
</SignedInfo>
<SignatureValue>
dJDHiGQMaKN8iPuWApAL57eVnxz2BQtyujwfPSgE7HyKoxYtoRB97ocxZ
8ZU440wHtE39ZwRGIjvwor3WfURxnIgnI1CChMXXwoGpHH//Zc0z4ejaz
DuCNEq4Mm4OUVTiEVuwcWAOMkfDHaM82awYQiOGcwMbZe38UX0oPJ2DOE=
</SignatureValue>
<KeyInfo>
<X509Data>
<X509SubjectName>
CN=My Name,O=Test Certificates Inc.,C=US
</X509SubjectName>
<X509Certificate>
MIIB9zCCAWCgAwIBAgIERZwdkzANBgkqhkiG9w0BAQUFADBAMQswCQYD
VQQGEwJVUzEfMB0GA1UEChMWVGVzdCBDZXJ0aWZpY2F0ZXMgSW5jLjEQ
MA4GA1UEAxMHTXkgTmFtZTAeFw0wNzAxMDMyMTE4MTFaFw0zMTA4MjUy
...
</X509Certificate>
</X509Data>
</KeyInfo>
</Signature>
</PurchaseOrder>
Note that the Signature
element has been inserted inside the content that it is signing, thereby making it an enveloped signature. XML Sample 3 shows the SignedInfo
element that contains the information that is actually signed
XML Sample 3
<SignedInfo>
<CanonicalizationMethod
Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315"/>
<SignatureMethod
Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1"/>
<Reference 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>tVicGh6V+8cHbVYFIU91o5+L3OQ=</DigestValue>
</Reference>
</SignedInfo>
The CanonicalizationMethod
element defines as a URI the algorithm used to canonicalize the SignedInfo
element before it is signed or validated. Canonicalization is the process of converting XML content to a physical representation, called the canonical form, in order to eliminate subtle changes that can invalidate a signature over that data. Canonicalization is necessary due to the nature of XML and the way it is parsed by different processors and intermediaries, which can change the data in such a way that the signature is no longer valid but the signed data is still logically equivalent. Canonicalization eliminates these permissible syntactic variances by converting the XML to a canonical form before generating or validating the signature.
The SignatureMethod
element defines as a URI the digital signature algorithm used to generate the signature, in this case the PKCS#1 RSA-SHA1 algorithm as described in RFC 2437. One or more Reference
elements identify the data that is signed. Each Reference
element identifies the data by way of a URI. The example in XML Sample 3 contains a single Reference
element, and the URI is the empty String, "", which indicates the root of the document -- in other words, the whole document. The Reference
URIs could also point to external data, such as "http://java[dot]sun[dot]com"
, or to references within the same document, such as "#purchaseOrder"
.
The optional Transforms
element contains a list of one or more Transform
elements, each of which describes a transformation algorithm used to transform the data before it is digested and signed, or validated. This example contains one Transform
element for the enveloped transform algorithm. The enveloped transform is required for enveloped signatures so that the Signature
element itself is removed before calculating the signature value. Otherwise, the signature would include itself in the data to be signed, which is not correct. Another example of a useful transform algorithm is the XPath Filter transform, which allows you to specify an XPath expression that selects a subset of nodes to be signed.
The DigestMethod
element defines as a URI the algorithm used to digest the data, in this case, SHA1. The DigestValue
element contains the actual base64-encoded digest value.
The SignatureValue
element contains the base64-encoded signature value of the signature over the SignedInfo
element, as XML Sample 4 shows.
XML Sample 4
<SignatureValue>
dJDHiGQMaKN8iPuWApAL57eVnxz2BQtyujwfPSgE7HyKoxYtoRB97ocxZ
8ZU440wHtE39ZwRGIjvwor3WfURxnIgnI1CChMXXwoGpHH//Zc0z4ejaz
DuCNEq4Mm4OUVTiEVuwcWAOMkfDHaM82awYQiOGcwMbZe38UX0oPJ2DOE=
</SignatureValue>
The optional KeyInfo
element contains information about the key that is needed to validate the signature, as in XML Sample 5.
XML Sample 5
<KeyInfo>
<X509Data>
<X509SubjectName>CN=My Name,O=Test Certificates Inc.,C=US</X509SubjectName>
<X509Certificate>
MIIB9zCCAWCgAwIBAgIERZwdkzANBgkqhkiG9w0BAQUFADBAMQswCQYD
VQQGEwJVUzEfMB0GA1UEChMWVGVzdCBDZXJ0aWZpY2F0ZXMgSW5jLjEQ
MA4GA1UEAxMHTXkgTmFtZTAeFw0wNzAxMDMyMTE4MTFaFw0zMTA4MjUy
...
</X509Certificate>
</X509Data>
</KeyInfo>
The KeyInfo
element can contain various kinds of content, such as X.509 certificates and Pretty Good Privacy (PGP) key identifiers. See the KeyInfo section of the XML Signature standard for more information on KeyInfo
and the types of information it may contain. In this example, KeyInfo
contains an X509Data
element that contains an X509SubjectName
element identifying the subject Distinguished Name
of the signer's X.509 certificate and an X509Certificate
element containing the signer's base64-encoded certificate. This certificate contains the public key needed to validate the signature. The KeyInfo section of the XML Signature Recommendation provides more information on the different KeyInfo
types.
It is important to note that the XML signature standard does not define how the recipient establishes trust in the key that is needed to validate the signature. The KeyInfo
element is merely a collection of information that the recipient can use to help find and subsequently establish trust in that key.
API Architecture
The Java XML Digital Signature API was defined under the Java Community Process program as JSR 105. The API is designed to support all of the required or recommended features of the W3C Recommendation for XML-Signature Syntax and Processing. The API is based on the Java Cryptography Service Provider Architecture. This allows you to develop a service provider implementation of the API. Service providers implement a specific XML mechanism that identifies the XML-parsing mechanism that the implementation uses. The service provider in Sun's implementation of Java SE 6 supports the Document Object Model (DOM) mechanism. See the XML Digital Signature API overview for more information on service providers.
The API contains six new packages, as Table 1 indicates.
Table 1. New Packages in the Java XML Digital Signature API
Package | Contents |
---|---|
javax.xml.crypto |
Contains common classes that are used to perform XML cryptographic operations. |
javax.xml.crypto.dom |
Contains DOM-specific classes for the javax.xml.crypto package. |
javax.xml.crypto.dsig |
Contains classes that represent the core elements defined in the XML digital signature specification. Of primary significance is the XMLSignature class, which allows you to sign and validate an XML digital signature. The XMLSignatureFactory class is an abstract factory that is used to create objects that implement these interfaces.
|
javax.xml.crypto.dsig.dom |
Contains DOM-specific classes for the javax.xml.crypto.dsig package.
|
javax.xml.crypto.dsig.keyinfo |
Contains classes that represent the KeyInfo structures defined in the XML digital signature recommendation. The KeyInfoFactory class is an abstract factory that is used to create objects that implement these interfaces.
|
javax.xml.crypto.dsig.spec |
Contains classes representing input parameters for the digest, signature, transform, or canonicalization algorithms used in the processing of XML signatures. |
Generating an XML Signature
This section will show you how to use the API to generate an XML signature over the contents of the PurchaseOrder
element that the article introduced earlier.
For this example, you will use DOM to parse the XML data that you will be signing. Code Sample 1 shows a few of the key steps in generating an XML signature:
Code Sample 1
// Create a DOM XMLSignatureFactory that will be used to
// generate the enveloped signature.
XMLSignatureFactory fac = XMLSignatureFactory.getInstance("DOM");
// Create a Reference to the enveloped document (in this case,
// you are signing the whole document, so a URI of "" signifies
// that, and also specify the SHA1 digest algorithm and
// the ENVELOPED Transform.
Reference ref = fac.newReference
("", fac.newDigestMethod(DigestMethod.SHA1, null),
Collections.singletonList
(fac.newTransform
(Transform.ENVELOPED, (TransformParameterSpec) null)),
null, null);
// Create the SignedInfo.
SignedInfo si = fac.newSignedInfo
(fac.newCanonicalizationMethod
(CanonicalizationMethod.INCLUSIVE,
(C14NMethodParameterSpec) null),
fac.newSignatureMethod(SignatureMethod.RSA_SHA1, null),
Collections.singletonList(ref));
The first step in the generation of an XML signature is to instantiate an XMLSignatureFactory
mechanism. The getInstance
method of the XMLSignatureFactory
class looks for a service provider that supports DOM and returns an XMLSignatureFactory
implementation from the provider with the highest preference. The XMLSignatureFactory
is a key class in the API and, as shown in Code Sample 1, is used to assemble the different components of the XMLSignature
.
The second block of code in Code Sample 1 creates the Reference
object, which identifies the data that will be digested and signed. The Reference
object is assembled by creating and passing as parameters each of its components: the URI, the DigestMethod
, and a list of Transforms
.
The third block of code in Code Sample 1 creates the SignedInfo
object that the signature is calculated over. Like the Reference
object, the SignedInfo
object is assembled by creating and passing as parameters each of its components: the CanonicalizationMethod
, the SignatureMethod
, and a list of References
.
Code Sample 2 shows the steps involved in constructing the KeyInfo
object.
Code Sample 2
// Load the KeyStore and get the signing key and certificate.
KeyStore ks = KeyStore.getInstance("JKS");
ks.load(new FileInputStream("mykeystore.jks"), "changeit".toCharArray());
KeyStore.PrivateKeyEntry keyEntry =
(KeyStore.PrivateKeyEntry) ks.getEntry
("mykey", new KeyStore.PasswordProtection("changeit".toCharArray()));
X509Certificate cert = (X509Certificate) keyEntry.getCertificate();
// Create the KeyInfo containing the X509Data.
KeyInfoFactory kif = fac.getKeyInfoFactory();
List x509Content = new ArrayList();
x509Content.add(cert.getSubjectX500Principal().getName());
x509Content.add(cert);
X509Data xd = kif.newX509Data(x509Content);
KeyInfo ki = kif.newKeyInfo(Collections.singletonList(xd));
For this example, the signing key and certificate are stored in a KeyStore
file. The first block of code retrieves the signer's X.509 certificate from the keystore. The second block of code creates the KeyInfo
object, using a KeyInfoFactory
, which is a factory for assembling KeyInfo
objects. The KeyInfo
object consists of an X509Data
object containing the certificate and the subject Distinguished Name.
Now you instantiate the document to be signed, create the XMLSignature
object, and generate the signature, as Code Sample 3 shows.
Code Sample 3
// Instantiate the document to be signed.
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
dbf.setNamespaceAware(true);
Document doc = dbf.newDocumentBuilder().parse
(new FileInputStream("purchaseOrder.xml"));
// Create a DOMSignContext and specify the RSA PrivateKey and
// location of the resulting XMLSignature's parent element.
DOMSignContext dsc = new DOMSignContext
(keyEntry.getPrivateKey(), doc.getDocumentElement());
// Create the XMLSignature, but don't sign it yet.
XMLSignature signature = fac.newXMLSignature(si, ki);
// Marshal, generate, and sign the enveloped signature.
signature.sign(dsc);
The Document
now contains the Signature
element. You can verify this by using the JAXP Transformer API to dump the contents of the document to a file, as Code Sample 4 shows.
Code Sample 4
// Output the resulting document.
OutputStream os = new FileOutputStream("signedPurchaseOrder.xml");
TransformerFactory tf = TransformerFactory.newInstance();
Transformer trans = tf.newTransformer();
trans.transform(new DOMSource(doc), new StreamResult(os));
Validating an XML Signature
You will now learn to use the API to validate an XML signature over the contents of the PurchaseOrder
element that you just signed. Code Sample 5 shows the key steps in validating an XML signature.
Code Sample 5
// Find Signature element.
NodeList nl =
doc.getElementsByTagNameNS(XMLSignature.XMLNS, "Signature");
if (nl.getLength() == 0) {
throw new Exception("Cannot find Signature element");
}
// Create a DOMValidateContext and specify a KeySelector
// and document context.
DOMValidateContext valContext = new DOMValidateContext
(new X509KeySelector(), nl.item(0));
// Unmarshal the XMLSignature.
XMLSignature signature = fac.unmarshalXMLSignature(valContext);
// Validate the XMLSignature.
boolean coreValidity = signature.validate(valContext);
First, you must find the location of the Signature
element that you wish to validate. One way to do this is to use the DOM getElementsByTagNameNS
method as shown in Code Sample 5. The second block of code creates a DOMValidateContext
object containing a KeySelector
object and a reference to the Signature
element. The purpose of the KeySelector
object is to obtain the public key using the information in the KeyInfo
element and hand it back to be used as the validation key. The next section will discuss KeySelector
s in more detail. The last two lines of code unmarshal and validate the signature. The validate
method returns true
if the signature is valid and false
if it is invalid.
If the signature is invalid, some additional code is necessary to determine the cause of the failure, as Code Sample 6 shows.
Code Sample 6
// 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);
if (sv == false) {
// 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("ref["+j+"] validity status: " + refValid);
}
}
} else {
System.out.println("Signature passed core validation");
}
The code in Code Sample 6 determines the cause of an invalid signature as one of two possibilities:
- An invalid signature. The cryptographic verification of the signature failed. This can be caused by an incorrect validation key or a change to the
SignedInfo
contents since the signature was generated. - An invalid reference or references. The verification of the digest of a reference failed. This can be caused by a change to the referenced data since the signature was generated.
Before moving on to the next section, it is important to note that transforms can change the contents of the data that is referenced before it is signed. Therefore, it may be important to show the contents of exactly what has been signed to the validating user. You can do this by enabling reference caching in the DOMValidateContext
object before validating the signature and invoking the getDigestInputStream
method of the Reference
objects contained in the signature, as Code Sample 7 shows.
Code Sample 7
valContext.setProperty("javax.xml.crypto.dsig.cacheReference", Boolean.TRUE);
// Unmarshal the XMLSignature.
XMLSignature signature = fac.unmarshalXMLSignature(valContext);
// Validate the XMLSignature.
boolean coreValidity = signature.validate(valContext);
Iterator i = signature.getSignedInfo().getReferences().iterator();
for (int j=0; i.hasNext(); j++) {
InputStream is = ((Reference) i.next()).getDigestInputStream();
// Display the data.
}
These and other security concerns are discussed in more detail in the security considerations section of the XML Signature Recommendation.
The KeySelector
Class
A KeySelector
is an abstract class that is responsible for finding and returning a key using the data contained in a KeyInfo
object. In Code Sample 5, you passed an X509KeySelector
object, which is a very simple implementation of KeySelector
that looks for and returns a public key of an X.509 certificate, as Code Sample 8 shows.
Code Sample 8
public class X509KeySelector extends KeySelector {
public KeySelectorResult select(KeyInfo keyInfo,
KeySelector.Purpose purpose,
AlgorithmMethod method,
XMLCryptoContext context)
throws KeySelectorException {
Iterator ki = keyInfo.getContent().iterator();
while (ki.hasNext()) {
XMLStructure info = (XMLStructure) ki.next();
if (!(info instanceof X509Data))
continue;
X509Data x509Data = (X509Data) info;
Iterator xi = x509Data.getContent().iterator();
while (xi.hasNext()) {
Object o = xi.next();
if (!(o instanceof X509Certificate))
continue;
final PublicKey key = ((X509Certificate)o).getPublicKey();
// Make sure the algorithm is compatible
// with the method.
if (algEquals(method.getAlgorithm(), key.getAlgorithm())) {
return new KeySelectorResult() {
public Key getKey() { return key; }
};
}
}
}
throw new KeySelectorException("No key found!");
}
static boolean algEquals(String algURI, String algName) {
if ((algName.equalsIgnoreCase("DSA") &&
algURI.equalsIgnoreCase(SignatureMethod.DSA_SHA1)) ||
(algName.equalsIgnoreCase("RSA") &&
algURI.equalsIgnoreCase(SignatureMethod.RSA_SHA1))) {
return true;
} else {
return false;
}
}
}
This is a very simple implementation of a KeySelector
that returns the public key from the first X.509 certificate it finds in the X509Data
. It is for demonstration purposes only and should not be used in real-world applications. A more complete X.509 key selector implementation would check other types of X509Data
and establish trust in the validation key by using a keystore of trusted keys, or by finding and validating a certificate chain from a trust anchor to the certificate containing the public key. See the Java PKI Programmer's Guide for more information about trust anchors and Java APIs that you can use to establish trust in keys.
Logging and Debugging
The Java SE 6 implementation of the XML Signature API has extensive logging support that, when enabled, will provide you with additional information to help you debug validation failures. The log messages use the JDK logging facility, java.util.logging
.
To enable XML signature logging, you must first configure the logging facility so that the XML signature-logging messages are emitted. You can do this by editing the JRE's default logging.properties
file directly, or by creating your own file and setting it with the java.util.logging.config.file
property, for example:
java -Djava.util.logging.config.file=logging.properties ...
where logging.properties
contains the following code:
handlers= java.util.logging.ConsoleHandler
.level= INFO
java.util.logging.ConsoleHandler.level = FINER
java.util.logging.ConsoleHandler.formatter = java.util.logging.SimpleFormatter
org.jcp.xml.dsig.internal.level = FINER
com.sun.org.apache.xml.internal.security.level = FINER
This will emit log messages of level FINER
and higher to the console. All other components will emit log messages of level INFO
and higher.
This article will not describe every log message in detail, but Table 2 lists some of the most helpful messages.
Table 2: Some Useful Log Messages
Log Message | Explanation |
---|---|
[java] FINER: Pre-digested input: ... |
This message displays the content of the referenced data just before it was digested. This is useful for debugging reference validation failures. |
|
These messages display the expected and actual base64-encoded digest values of a Reference element. This is also useful for debugging reference validation failures.
|
[java] FINE: Canonicalized SignedInfo: ... |
This message displays the canonicalized SignedInfo element before it is signed. This is useful for debugging canonicalization and signature verification failures.
|
Conclusion
The purpose of the article was to get you started with using the API and to show you the basic steps in generating and validating an XML signature. To learn more about the Java XML Digital Signature API, consult the documentation and references in the "For More Information" section.
The Java XML Digital Signature API is available in Java SE 6, as well as in the GlassFish project. Project WSIT, also known as Project Tango, uses the Java XML Signature API to implement the Web Services Security (WSS) specification.
For More Information
- Java XML Signatures: This article discusses XML digital signatures and the Java XML Signature API and discusses ways to speed up performance using cryptographic hardware accelerators.
- JSR 105 Java Community Process
- Java XML Digital Signature Overview and Tutorial
- Java XML Digital Signature API Specification
- Java PKI Programmer's Guide
- Sean Mullan's Blog
About the Author
Sean Mullan is a senior engineer at Sun Microsystems. He is the co-specification lead of JSR 105 and is a member of the Java security team, working on XML security, Public Key Infrastructure (PKI), and other Java SE security technologies.