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
Video Game 10.29
My Name
One Network Drive Burlington MA United States 01803
|
The resulting enveloped XML signature, indented and formatted for readability, appears in XML Sample 2. XML Sample 2
Video Game 10.29
My Name
One Network Drive Burlington MA United States 01803
tVicGh6V+8cHbVYFIU91o5+L3OQ=
dJDHiGQMaKN8iPuWApAL57eVnxz2BQtyujwfPSgE7HyKoxYtoRB97ocxZ 8ZU440wHtE39ZwRGIjvwor3WfURxnIgnI1CChMXXwoGpHH//Zc0z4ejaz DuCNEq4Mm4OUVTiEVuwcWAOMkfDHaM82awYQiOGcwMbZe38UX0oPJ2DOE=
CN=My Name,O=Test Certificates Inc.,C=US MIIB9zCCAWCgAwIBAgIERZwdkzANBgkqhkiG9w0BAQUFADBAMQswCQYD VQQGEwJVUzEfMB0GA1UEChMWVGVzdCBDZXJ0aWZpY2F0ZXMgSW5jLjEQ MA4GA1UEAxMHTXkgTmFtZTAeFw0wNzAxMDMyMTE4MTFaFw0zMTA4MjUy ...
|
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
tVicGh6V+8cHbVYFIU91o5+L3OQ=
|
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
dJDHiGQMaKN8iPuWApAL57eVnxz2BQtyujwfPSgE7HyKoxYtoRB97ocxZ 8ZU440wHtE39ZwRGIjvwor3WfURxnIgnI1CChMXXwoGpHH//Zc0z4ejaz DuCNEq4Mm4OUVTiEVuwcWAOMkfDHaM82awYQiOGcwMbZe38UX0oPJ2DOE=
|
The optional KeyInfo element contains information about the key that is needed to validate the signature, as in XML Sample 5. XML Sample 5
CN=My Name,O=Test Certificates Inc.,C=US
MIIB9zCCAWCgAwIBAgIERZwdkzANBgkqhkiG9w0BAQUFADBAMQswCQYD VQQGEwJVUzEfMB0GA1UEChMWVGVzdCBDZXJ0aWZpY2F0ZXMgSW5jLjEQ MA4GA1UEAxMHTXkgTmFtZTAeFw0wNzAxMDMyMTE4MTFaFw0zMTA4MjUy ...
|
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 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. | [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 |
1 comment:
Thanks for explaining the API. You have given an excellent overview about how digital signatures are implemented in java. I am pleased that you have also provided the samples too to explain all the things in a more clear way.
digital signatures
Post a Comment