Java XML signature example
June 19, 2010 04:34:01 Last update: June 19, 2010 04:39:13
Java SE 6 contains built-in utilities to generate XML signatures. This is an example that generates XML signatures using a Java keystore. It has options to generate signature for the whole document, for an element with a specific ID, or for elements matched by an XPATH expression.
The XML document used to test is taken from Getting Started with XML Security:
This is the Java code:
However, it looks like the XPATH transform is not working. The digest generated with XPATH filter is exactly the same as that without it (i.e., the whole document)!
Another reference:
Programming With the Java XML Digital Signature API
The XML document used to test is taken from Getting Started with XML Security:
<?xml version="1.0"?> <PatientRecord> <Name>John Doe</Name> <Account id="acct">123456</Account> <Visit date="10pm March 10, 2002"> <Diagnosis>Broken second metacarpal</Diagnosis> </Visit> </PatientRecord>
This is the Java code:
import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.OutputStream; import java.security.*; import java.security.cert.X509Certificate; import java.util.*; import javax.xml.crypto.*; import javax.xml.crypto.dsig.*; import javax.xml.crypto.dom.*; import javax.xml.crypto.dsig.dom.DOMSignContext; import javax.xml.crypto.dsig.keyinfo.*; import javax.xml.crypto.dsig.spec.*; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.transform.*; import javax.xml.transform.dom.DOMSource; import javax.xml.transform.stream.StreamResult; import javax.xml.xpath.*; import org.w3c.dom.*; /** * Sign XML file. */ public class SignFileExample { private static final String KEY_STORE_TYPE = "JKS"; private static final String KEY_STORE_NAME = "mykeystore"; private static final String KEY_STORE_PASS = "keystore-pass"; private static final String PRIVATE_KEY_PASS = "privatekey-pass"; private static final String KEY_ALIAS = "myprivatekey"; private static final String PATH = "/PatientRecord/Account"; private static final String ID = "acct"; private static enum SignatureType { SIGN_BY_ID, SIGN_BY_PATH, SIGN_WHOLE_DOCUMENT }; public static void main(String[] args) throws Exception { if (args.length < 2) { usage(); return; } String inputFile = args[0]; String outputFile = args[1]; SignatureType sigType = SignatureType.SIGN_WHOLE_DOCUMENT; if (args.length >= 3) { if ("id".equals(args[2])) { sigType = SignatureType.SIGN_BY_ID; } else if ("path".equals(args[2])) { sigType = SignatureType.SIGN_BY_PATH; } } // Instantiate the document to be signed DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance(); dbFactory.setNamespaceAware(true); Document doc = dbFactory .newDocumentBuilder() .parse(new FileInputStream(inputFile)); // prepare signature factory String providerName = System.getProperty( "jsr105Provider", "org.jcp.xml.dsig.internal.dom.XMLDSigRI" ); final XMLSignatureFactory sigFactory = XMLSignatureFactory.getInstance( "DOM", (Provider) Class.forName(providerName).newInstance() ); Node nodeToSign = null; Node sigParent = null; String referenceURI = null; XPathExpression expr = null; NodeList nodes; List transforms = null; XPathFactory factory = XPathFactory.newInstance(); XPath xpath = factory.newXPath(); switch (sigType) { case SIGN_BY_ID: expr = xpath.compile( String.format("//*[@id='%s']", ID) ); nodes = (NodeList) expr.evaluate(doc, XPathConstants.NODESET); if (nodes.getLength() == 0) { System.out.println("Can't find node with id: " + ID); return; } nodeToSign = nodes.item(0); sigParent = nodeToSign.getParentNode(); referenceURI = "#" + ID; /* * This is not needed since the signature is alongside the signed element, not enclosed in it. transforms = Collections.singletonList( sigFactory.newTransform( Transform.ENVELOPED, (TransformParameterSpec) null ) ); */ break; case SIGN_BY_PATH: // Find the node to be signed by PATH expr = xpath.compile(PATH); nodes = (NodeList) expr.evaluate(doc, XPathConstants.NODESET); if (nodes.getLength() < 1) { System.out.println("Invalid document, can't find node by PATH: " + PATH); return; } nodeToSign = nodes.item(0); sigParent = nodeToSign.getParentNode(); referenceURI = ""; // Empty string means whole document transforms = new ArrayList<Transform>() {{ add(sigFactory.newTransform( Transform.XPATH, new XPathFilterParameterSpec(PATH) ) ); add(sigFactory.newTransform( Transform.ENVELOPED, (TransformParameterSpec) null ) ); }}; break; default: sigParent = doc.getDocumentElement(); referenceURI = ""; // Empty string means whole document transforms = Collections.singletonList( sigFactory.newTransform( Transform.ENVELOPED, (TransformParameterSpec) null ) ); break; } // Retrieve signing key KeyStore keyStore = KeyStore.getInstance(KEY_STORE_TYPE); keyStore.load( new FileInputStream(KEY_STORE_NAME), KEY_STORE_PASS.toCharArray() ); PrivateKey privateKey = (PrivateKey) keyStore.getKey( KEY_ALIAS, PRIVATE_KEY_PASS.toCharArray() ); X509Certificate cert = (X509Certificate) keyStore.getCertificate(KEY_ALIAS); PublicKey publicKey = cert.getPublicKey(); // Create a Reference to the enveloped document Reference ref = sigFactory.newReference( referenceURI, sigFactory.newDigestMethod(DigestMethod.SHA1, null), transforms, null, null ); // Create the SignedInfo SignedInfo signedInfo = sigFactory.newSignedInfo( sigFactory.newCanonicalizationMethod( CanonicalizationMethod.INCLUSIVE_WITH_COMMENTS, (C14NMethodParameterSpec) null ), sigFactory.newSignatureMethod( SignatureMethod.RSA_SHA1, null ), Collections.singletonList(ref) ); // Create a KeyValue containing the RSA PublicKey KeyInfoFactory keyInfoFactory = sigFactory.getKeyInfoFactory(); KeyValue keyValue = keyInfoFactory.newKeyValue(publicKey); // Create a KeyInfo and add the KeyValue to it KeyInfo keyInfo = keyInfoFactory.newKeyInfo(Collections.singletonList(keyValue)); // Create a DOMSignContext and specify the RSA PrivateKey and // location of the resulting XMLSignature's parent element DOMSignContext dsc = new DOMSignContext( privateKey, sigParent ); // Create the XMLSignature (but don't sign it yet) XMLSignature signature = sigFactory.newXMLSignature(signedInfo, keyInfo); // Marshal, generate (and sign) the enveloped signature signature.sign(dsc); // output the resulting document OutputStream os = new FileOutputStream(args[1]); Transformer trans = TransformerFactory.newInstance() .newTransformer(); trans.transform(new DOMSource(doc), new StreamResult(os)); } private static void usage() { System.out.println("Usage: java SignFile <inputFile> <outputFile> [id|path|whole]"); } }
However, it looks like the XPATH transform is not working. The digest generated with XPATH filter is exactly the same as that without it (i.e., the whole document)!
Another reference:
Programming With the Java XML Digital Signature API
1 comment 
Easy email testing with http://www.ximailstop.com