Signing XML files in Java

The following is an example for signing XML documents in Java without using the Java Key Store.

A key to this article was this reference from http://codeartisan.blogspot.com/2009/05/public-key-cryptography-in-java.html to be able to use the files generate with openssl.

Ok. So the first step is to generate some files.
To do that we will use openssl.
You need:
a) server.key file which is the private key
b) server.crt file which is the certificate file

The openssl command is:

sudo openssl req -new -newkey rsa:1024 -days 365 -nodes -x509 -keyout server.key -out server.crt

In Java to read the private key we need it on a PKCS8 DER format. So we convert the key with:

openssl pkcs8 -topk8 -inform PEM -outform DER -in server.key -out server.der -nocrypt

As a mentioned. Reading the private key is done with the code snippet from Jon Moore:

import java.io.*;
import java.security.*;
import java.security.spec.*;

public class PrivateKeyReader {

  public static PrivateKey get(String filename)
    throws Exception {
    
    File f = new File(filename);
    FileInputStream fis = new FileInputStream(f);
    DataInputStream dis = new DataInputStream(fis);
    byte[] keyBytes = new byte[(int)f.length()];
    dis.readFully(keyBytes);
    dis.close();

    PKCS8EncodedKeySpec spec =
      new PKCS8EncodedKeySpec(keyBytes);
    KeyFactory kf = KeyFactory.getInstance("RSA");
    return kf.generatePrivate(spec);
  }
}

Reading the certificate is also easy:

	
public static X509Certificate getCertificate(String certPath) throws CertificateException, IOException {
	CertificateFactory fact = CertificateFactory.getInstance("X.509");
    FileInputStream is = new FileInputStream (certPath);
    X509Certificate cer = (X509Certificate) fact.generateCertificate(is);
	return cer;	
}
  

Lets use an input file like:

<?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>

Using the above snippets and the standard Java Examples from http://www.oracle.com/technetwork/articles/javase/dig-signature-api-140772.html, we can build a simple method to sing xml documents.

	public static void signXmlDocument(String privateKeyInDerFormat, String certificateCrtFile, String pathToInputXml,String pathToOutputXml) throws Exception {
		// Create a DOM XMLSignatureFactory that will be used to
		// generate the enveloped signature.
		XMLSignatureFactory fac = XMLSignatureFactory.getInstance("DOM");	  
		PrivateKey privateKey = PrivateKeyReader.get(privateKeyInDerFormat);
		// 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));
		X509Certificate cert = (X509Certificate)getCertificate(certificateCrtFile);
		
		// 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));
		
		// Instantiate the document to be signed.
		DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
		dbf.setNamespaceAware(true);
		Document doc = dbf.newDocumentBuilder().parse(new FileInputStream(pathToInputXml));
		
		// Create a DOMSignContext and specify the RSA PrivateKey and
		// location of the resulting XMLSignature's parent element.
		DOMSignContext dsc = new DOMSignContext(privateKey, 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);
		// Output the resulting document.
		OutputStream os = new FileOutputStream(pathToOutputXml);
		TransformerFactory tf = TransformerFactory.newInstance();
		Transformer trans = tf.newTransformer();
		trans.transform(new DOMSource(doc), new StreamResult(os));
	}

The kind of xml documents generated will be like the following:

<?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>QRBOPy6CkHBkh0LeVSGvLft2fZJQRiMoxprSoohpQX5v2LbsswWKp/BOrCAUAfm6nuKniYdZVgRy
cYLBm4mpXcTfcaTxR+hE7ZP/eolQPptJNgI/nslUF03aM8H1P7tfhjvBAzUmh3l3YO9QU1OQksCd
O72L++kf0gaycv2JPLQ=</SignatureValue><KeyInfo><X509Data><X509SubjectName>1.2.840.113549.1.9.1=#16136f72656c6c6162616340676d61696c2e636f6d,CN=CommonName,OU=MauCorp,O=Mau,L=SJ,ST=SJ,C=CR</X509SubjectName><X509Certificate>MIIC0DCCAjmgAwIBAgIJAISHqcaCRj6dMA0GCSqGSIb3DQEBCwUAMIGAMQswCQYDVQQGEwJDUjEL
MAkGA1UECAwCU0oxCzAJBgNVBAcMAlNKMQwwCgYDVQQKDANNYXUxEDAOBgNVBAsMB01hdUNvcnAx
EzARBgNVBAMMCkNvbW1vbk5hbWUxIjAgBgkqhkiG9w0BCQEWE29yZWxsYWJhY0BnbWFpbC5jb20w
HhcNMTgwMTAyMjIzMjA4WhcNMTkwMTAyMjIzMjA4WjCBgDELMAkGA1UEBhMCQ1IxCzAJBgNVBAgM
AlNKMQswCQYDVQQHDAJTSjEMMAoGA1UECgwDTWF1MRAwDgYDVQQLDAdNYXVDb3JwMRMwEQYDVQQD
DApDb21tb25OYW1lMSIwIAYJKoZIhvcNAQkBFhNvcmVsbGFiYWNAZ21haWwuY29tMIGfMA0GCSqG
SIb3DQEBAQUAA4GNADCBiQKBgQCZ6Mr7YrW14az7O93AoMQzEfObwamBCWe9Zu7BW9uJrWY1v+eh
C9SEAVmV6OqLJPvyQasAjKMQdVRFU2PPknNO3kcN6dhQCCc3/FmzScbLucjnD2P+ko87HZFoRtyr
4Xul0zPesq8WrzWrfNQkB0RF5lq4RrcX2Zr9qRyqn3ceUQIDAQABo1AwTjAdBgNVHQ4EFgQU6XW7
bj3KyazORikfBPXOi4Np2ggwHwYDVR0jBBgwFoAU6XW7bj3KyazORikfBPXOi4Np2ggwDAYDVR0T
BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOBgQCJo21PsOsdLrI8TAZ1BWYDIXgXIiiHI7jfQNTlMpfv
gyFhmIwlQvw0NNM1n3IUCFblUkJh+m1r5gp8pIM3I12BYQwGuGW4jqb04ZHa00PRRix8Tua1Ow43
vvWwneJMMgO7yqX1QMbH/XJY0vl8Na5BniJpkohn89l9nhxVlfAZtg==</X509Certificate></X509Data></KeyInfo></Signature></PurchaseOrder>