|
Articles Index
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.sun.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.
 |
|
Contains common classes that are used to perform XML cryptographic
operations. |
|
Contains DOM-specific classes for the javax.xml.crypto
package. |
|
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. |
|
Contains DOM-specific classes for the javax.xml.crypto.dsig
package. |
|
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. |
|
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
KeySelectors 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.
 |
[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. |
[java] FINE: Expected digest: ... [java] FINE: Actual digest:
...
|
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
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.
|