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>