20

I'm trying to digitally sign an XML document using Java. I've got an implementation working with some references I've found that use various implementations in the javax.xml.crypto.dsig package.

However, my current implementation is like many of the examples I've looked at - it's rather verbose and involves using no less than 23 different API classes from the java.xml.crypto.dsig, javax.xml.transform, and java.security packages, among others. It feels like I've entered factory factory factory land, and it took me several hours just to figure out what was going on.

My question is, is there an easier way to do this? If I've got public/private key files and I want to add a <Signature/> to an XML document, is there a library out there that just lets me call something like:

OutputStream signFile(InputStream xmlFile, File privateKey)

...without all of the XMLSignatureFactory/CanonicalizationMethod/DOMSignContext craziness?

I'm not very well-versed in cryptography, and the Java-provided API seems rather daunting for developers like myself trying to become familiar with digital signing. If all of this is necessary or there's currently no friendlier API out there, that's fine and I'm willing to accept that as an answer. I'd just like to know if I'm unnecessarily taking the hard road here.

Perception
  • 79,279
  • 19
  • 185
  • 195
Rob Hruska
  • 118,520
  • 32
  • 167
  • 192
  • As an alternate solution, I was able to make a one-liner system call and use http://www.aleksey.com/xmlsec/ to sign the XML. I'm not posting this as an answer since it's not within the bounds of the question (i.e. "in Java"). – Rob Hruska Jan 22 '10 at 17:34
  • The "factory factory factory" link is broken. Edit: so are a few other links – byxor Jan 31 '20 at 17:02

3 Answers3

11

Building from the Apache Santuario CreateSignature example, the shortest thing I could come up with is this. Without the main() and its accompanying output(), it's 20 lines

import java.io.*;
import java.security.Key;
import java.security.KeyStore;
import java.security.cert.X509Certificate;
import javax.xml.parsers.DocumentBuilderFactory;

import org.apache.commons.io.IOUtils;
import org.apache.xml.security.Init;
import org.apache.xml.security.c14n.Canonicalizer;
import org.apache.xml.security.signature.XMLSignature;
import org.apache.xml.security.transforms.Transforms;
import org.apache.xml.security.utils.Constants;
import org.apache.xml.security.utils.ElementProxy;
import org.w3c.dom.Document;

public class CreateSignature {

    private static final String PRIVATE_KEY_ALIAS = "test-alias";
    private static final String PRIVATE_KEY_PASS = "test";
    private static final String KEY_STORE_PASS = "test";
    private static final String KEY_STORE_TYPE = "JKS";

    public static void main(String... unused) throws Exception {
        final InputStream fileInputStream = new FileInputStream("test.xml");
        try {
            output(signFile(fileInputStream, new File("keystore.jks")), "signed-test.xml");
        }
        finally {
            IOUtils.closeQuietly(fileInputStream);
        }
    }

    public static ByteArrayOutputStream signFile(InputStream xmlFile, File privateKeyFile) throws Exception {
        final Document doc = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(xmlFile);
        Init.init();
        ElementProxy.setDefaultPrefix(Constants.SignatureSpecNS, "");
        final KeyStore keyStore = loadKeyStore(privateKeyFile);
        final XMLSignature sig = new XMLSignature(doc, null, XMLSignature.ALGO_ID_SIGNATURE_RSA);
        final Transforms transforms = new Transforms(doc);
        transforms.addTransform(Transforms.TRANSFORM_ENVELOPED_SIGNATURE);
        sig.addDocument("", transforms, Constants.ALGO_ID_DIGEST_SHA1);
        final Key privateKey = keyStore.getKey(PRIVATE_KEY_ALIAS, PRIVATE_KEY_PASS.toCharArray());
        final X509Certificate cert = (X509Certificate)keyStore.getCertificate(PRIVATE_KEY_ALIAS);
        sig.addKeyInfo(cert);
        sig.addKeyInfo(cert.getPublicKey());
        sig.sign(privateKey);
        doc.getDocumentElement().appendChild(sig.getElement());
        final ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
        outputStream.write(Canonicalizer.getInstance(Canonicalizer.ALGO_ID_C14N_WITH_COMMENTS).canonicalizeSubtree(doc));
        return outputStream;
    }

    private static KeyStore loadKeyStore(File privateKeyFile) throws Exception {
        final InputStream fileInputStream = new FileInputStream(privateKeyFile);
        try {
            final KeyStore keyStore = KeyStore.getInstance(KEY_STORE_TYPE);
            keyStore.load(fileInputStream, KEY_STORE_PASS.toCharArray());
            return keyStore;
        }
        finally {
            IOUtils.closeQuietly(fileInputStream);
        }
    }

    private static void output(ByteArrayOutputStream signedOutputStream, String fileName) throws IOException {
        final OutputStream fileOutputStream = new FileOutputStream(fileName);
        try {
            fileOutputStream.write(signedOutputStream.toByteArray());
            fileOutputStream.flush();
        }
        finally {
            IOUtils.closeQuietly(fileOutputStream);
        }
    }
}
Kirby
  • 15,127
  • 10
  • 89
  • 104
  • 1
    Was trying your proposal out, however I seem to be unable to change the C14N algo from Canonicalizer.ALGO_ID_C14N_WITH_COMMENTS to Canonicalizer.ALGO_ID_C14N_EXCL_OMIT_COMMENTS. Even when I change this in the code the output XML signature still shows the CanonicalizationMethod as . Any ideas as to what could be going wrong here ? – mithrandir May 26 '14 at 17:46
11

Have look at Apache XML Security. To use the package to generate and verify a signature, checkout the samples in src_samples/org/apache/xml/security/samples/signature/.

Rob Hruska
  • 118,520
  • 32
  • 167
  • 192
Pascal Thivent
  • 562,542
  • 136
  • 1,062
  • 1,124
3

I looked at all of the options for signing XML files and decided to go with a non-standard approach. The standards were all way too verbose. Also, I didn't need compatibility with the standards---I just needed signatures on a block of XML.

Probably the easiest way to "sign" a block of XML is to use GPG with a detached signature.

vy32
  • 28,461
  • 37
  • 122
  • 246
  • Nice idea, probably the simplest one provided so far. It doesn't offer all of the configurability of javax.xml.crypto or Apache's Santuario, but that's really what makes those other ones so complex. – Rob Hruska Jan 13 '10 at 15:00
  • Thanks. One system that does this takes this approach takes the XML block, computes a signature using an RSA public key and OpenSSL, and then embeds that signature at the end of the XML block in the text file. You can find the code in my afsign.cpp program that's part of AFFLIB, which can be downloaded from http://afflib.org/ – vy32 Jan 13 '10 at 15:42
  • Accepting this one since it's the simpler solution. Pascal's solution is also reasonable, although it's still requires a fair amount of code. – Rob Hruska Jan 22 '10 at 17:32
  • Although simple and good, this can't be done in some environments, where you must include signature in a specific way (like SOAP servers) – Filippo Mazza Aug 18 '12 at 14:17
  • I'm looking for a standard approach using Java/Sun packages. Any links? – kbec Feb 04 '13 at 09:14